package org.threadly.util.debug;
import java.util.Iterator;
import org.threadly.concurrent.collections.ConcurrentArrayList;
/**
* This class functions very similar to the {@link Profiler}. The difference between the two is
* while the {@link Profiler} profiles all running threads on the VM. This implementation only
* profiles threads which you explicitly add to be profiled.
*
* @since 1.0.0
*/
public class ControlledThreadProfiler extends Profiler {
private static final short TRACKED_THREAD_BUFFER = 10; // used to make adding/removing tracked threads more efficient
protected final ControlledThreadProfileStorage controledThreadStore;
/**
* Constructs a new profiler instance. The only way to get results from this instance is to
* call {@code #dump()} with a provided output stream to get the results to.
*/
public ControlledThreadProfiler() {
this(DEFAULT_POLL_INTERVAL_IN_MILLIS);
}
/**
* Constructs a new profiler instance. The only way to get results from this instance is to call
* {@code #dump()} with a provided output stream to get the results to.
*
* @param pollIntervalInMs frequency to check running threads
*/
public ControlledThreadProfiler(int pollIntervalInMs) {
super(new ControlledThreadProfileStorage(pollIntervalInMs));
controledThreadStore = (ControlledThreadProfileStorage)super.pStore;
}
/**
* Adds a thread to be checked by the running profiler.
* <p>
* If the thread is already included, or if the thread is {@code null}, this is a no-op.
*
* @param t Thread to add to the list of tracked threads
*/
public void addProfiledThread(Thread t) {
if (t == null) {
return; // don't add
}
synchronized (controledThreadStore.profiledThreads.getModificationLock()) {
SelectedThreadSample threadSample = new SelectedThreadSample(t);
if (! controledThreadStore.profiledThreads.contains(threadSample)) {
controledThreadStore.profiledThreads.add(threadSample);
}
}
}
/**
* Removed a thread from the set of tracked threads. It is good practice to remove a thread
* from the profiler if it is no longer alive. This profiler makes NO attempt to automatically
* remove dead threads.
*
* @param t Thread to remove from tracked set
* @return {@code true} if the thread was found and removed.
*/
public boolean removedProfiledThread(Thread t) {
return controledThreadStore.profiledThreads.remove(new SelectedThreadSample(t));
}
/**
* Call to check how many threads are currently being checked by the profiler. Keep in mind
* that threads that are not alive may be skipped by the profiler, but will be included in this
* count.
*
* @return count of tracked threads.
*/
public int getProfiledThreadCount() {
return controledThreadStore.profiledThreads.size();
}
/**
* Extending class of {@link ProfileStorage} this overrides
* {@link #getProfileThreadsIterator()}. It controls it so that not all VM threads are returned
* in the iterator, and instead it only iterates over the threads which are stored internally.
*
* @since 3.5.0
*/
protected static class ControlledThreadProfileStorage extends ProfileStorage {
protected final ConcurrentArrayList<SelectedThreadSample> profiledThreads;
public ControlledThreadProfileStorage(int pollIntervalInMs) {
super(pollIntervalInMs);
profiledThreads = new ConcurrentArrayList<>(0, TRACKED_THREAD_BUFFER);
}
@Override
protected Iterator<? extends ThreadSample> getProfileThreadsIterator() {
return profiledThreads.iterator();
}
}
/**
* A wrapper for a thread to implement {@link ThreadSample}. This wrapper allows us to just use
* a simple collection for storing threads (and thus to iterate over), and then lazily
* generate the stack trace when {@link ThreadSample#getStackTrace()} is invoked.
*
* @since 3.8.0
*/
protected static class SelectedThreadSample implements ThreadSample {
private final Thread t;
protected SelectedThreadSample(Thread t) {
this.t = t;
}
@Override
public Thread getThread() {
return t;
}
@Override
public StackTraceElement[] getStackTrace() {
return t.getStackTrace();
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (o instanceof ThreadSample) {
return ((ThreadSample)o).getThread() == t;
} else {
return false;
}
}
@Override
public int hashCode() {
return t.hashCode();
}
}
}