package org.threadly.concurrent.statistics;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import org.threadly.concurrent.ConfigurableThreadFactory;
import org.threadly.concurrent.SingleThreadScheduler;
import org.threadly.concurrent.TaskPriority;
import org.threadly.concurrent.statistics.PriorityStatisticManager.TaskStatWrapper;
import org.threadly.util.Pair;
/**
* An implementation of {@link SingleThreadScheduler} which tracks run and usage statistics. This
* is designed for testing and troubleshooting. It has a little more overhead from the normal
* {@link SingleThreadScheduler}.
* <p>
* It helps give insight in how long tasks are running, how well the thread pool is being
* utilized, as well as execution frequency.
*
* @since 4.5.0 (earlier forms existed since 1.0.0)
*/
public class SingleThreadSchedulerStatisticTracker extends SingleThreadScheduler
implements StatisticPriorityScheduler {
private final NoThreadSchedulerStatisticTracker statisticTracker;
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided. This defaults to using a daemon thread for the scheduler.
* <p>
* This defaults to inaccurate time. Meaning that durations and delays may under report (but
* NEVER OVER what they actually were). This has the least performance impact. If you want more
* accurate time consider using one of the constructors that accepts a boolean for accurate time.
*/
public SingleThreadSchedulerStatisticTracker() {
this((TaskPriority)null, DEFAULT_LOW_PRIORITY_MAX_WAIT_IN_MS);
}
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided. This defaults to using a daemon thread for the scheduler.
* <p>
* This defaults to inaccurate time. Meaning that durations and delays may under report (but
* NEVER OVER what they actually were). This has the least performance impact. If you want more
* accurate time consider using one of the constructors that accepts a boolean for accurate time.
*
* @param defaultPriority Default priority for tasks which are submitted without any specified priority
* @param maxWaitForLowPriorityInMs time low priority tasks to wait if there are high priority tasks ready to run
*/
public SingleThreadSchedulerStatisticTracker(TaskPriority defaultPriority,
long maxWaitForLowPriorityInMs) {
this(defaultPriority, maxWaitForLowPriorityInMs, true);
}
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided.
* <p>
* This defaults to inaccurate time. Meaning that durations and delays may under report (but
* NEVER OVER what they actually were). This has the least performance impact. If you want more
* accurate time consider using one of the constructors that accepts a boolean for accurate time.
*
* @param daemonThread {@code true} if scheduler thread should be a daemon thread
*/
public SingleThreadSchedulerStatisticTracker(boolean daemonThread) {
this((TaskPriority)null, DEFAULT_LOW_PRIORITY_MAX_WAIT_IN_MS, daemonThread);
}
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided.
* <p>
* This defaults to inaccurate time. Meaning that durations and delays may under report (but
* NEVER OVER what they actually were). This has the least performance impact. If you want more
* accurate time consider using one of the constructors that accepts a boolean for accurate time.
*
* @param defaultPriority Default priority for tasks which are submitted without any specified priority
* @param maxWaitForLowPriorityInMs time low priority tasks to wait if there are high priority tasks ready to run
* @param daemonThread {@code true} if scheduler thread should be a daemon thread
*/
public SingleThreadSchedulerStatisticTracker(TaskPriority defaultPriority,
long maxWaitForLowPriorityInMs,
boolean daemonThread) {
this(defaultPriority, maxWaitForLowPriorityInMs, daemonThread, 1000);
}
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided.
* <p>
* This defaults to inaccurate time. Meaning that durations and delays may under report (but
* NEVER OVER what they actually were). This has the least performance impact. If you want more
* accurate time consider using one of the constructors that accepts a boolean for accurate time.
*
* @param threadFactory factory to make thread for scheduler
*/
public SingleThreadSchedulerStatisticTracker(ThreadFactory threadFactory) {
this(null, DEFAULT_LOW_PRIORITY_MAX_WAIT_IN_MS, threadFactory);
}
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided.
* <p>
* This defaults to inaccurate time. Meaning that durations and delays may under report (but
* NEVER OVER what they actually were). This has the least performance impact. If you want more
* accurate time consider using one of the constructors that accepts a boolean for accurate time.
*
* @param defaultPriority Default priority for tasks which are submitted without any specified priority
* @param maxWaitForLowPriorityInMs time low priority tasks to wait if there are high priority tasks ready to run
* @param threadFactory factory to make thread for scheduler
*/
public SingleThreadSchedulerStatisticTracker(TaskPriority defaultPriority,
long maxWaitForLowPriorityInMs,
ThreadFactory threadFactory) {
this(defaultPriority, maxWaitForLowPriorityInMs, threadFactory, 1000);
}
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided. This defaults to using a daemon thread for the scheduler.
* <p>
* This defaults to inaccurate time. Meaning that durations and delays may under report (but
* NEVER OVER what they actually were). This has the least performance impact. If you want more
* accurate time consider using one of the constructors that accepts a boolean for accurate time.
*
* @param maxStatisticWindowSize maximum number of samples to keep internally
*/
public SingleThreadSchedulerStatisticTracker(int maxStatisticWindowSize) {
this(null, DEFAULT_LOW_PRIORITY_MAX_WAIT_IN_MS, maxStatisticWindowSize);
}
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided. This defaults to using a daemon thread for the scheduler.
* <p>
* This defaults to inaccurate time. Meaning that durations and delays may under report (but
* NEVER OVER what they actually were). This has the least performance impact. If you want more
* accurate time consider using one of the constructors that accepts a boolean for accurate time.
*
* @param defaultPriority Default priority for tasks which are submitted without any specified priority
* @param maxWaitForLowPriorityInMs time low priority tasks to wait if there are high priority tasks ready to run
* @param maxStatisticWindowSize maximum number of samples to keep internally
*/
public SingleThreadSchedulerStatisticTracker(TaskPriority defaultPriority,
long maxWaitForLowPriorityInMs,
int maxStatisticWindowSize) {
this(defaultPriority, maxWaitForLowPriorityInMs, true, maxStatisticWindowSize);
}
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided.
* <p>
* This defaults to inaccurate time. Meaning that durations and delays may under report (but
* NEVER OVER what they actually were). This has the least performance impact. If you want more
* accurate time consider using one of the constructors that accepts a boolean for accurate time.
*
* @param daemonThread {@code true} if scheduler thread should be a daemon thread
* @param maxStatisticWindowSize maximum number of samples to keep internally
*/
public SingleThreadSchedulerStatisticTracker(boolean daemonThread, int maxStatisticWindowSize) {
this(null, DEFAULT_LOW_PRIORITY_MAX_WAIT_IN_MS, daemonThread, maxStatisticWindowSize);
}
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided.
* <p>
* This defaults to inaccurate time. Meaning that durations and delays may under report (but
* NEVER OVER what they actually were). This has the least performance impact. If you want more
* accurate time consider using one of the constructors that accepts a boolean for accurate time.
*
* @param defaultPriority Default priority for tasks which are submitted without any specified priority
* @param maxWaitForLowPriorityInMs time low priority tasks to wait if there are high priority tasks ready to run
* @param daemonThread {@code true} if scheduler thread should be a daemon thread
* @param maxStatisticWindowSize maximum number of samples to keep internally
*/
public SingleThreadSchedulerStatisticTracker(TaskPriority defaultPriority,
long maxWaitForLowPriorityInMs, boolean daemonThread,
int maxStatisticWindowSize) {
this(defaultPriority, maxWaitForLowPriorityInMs, daemonThread, maxStatisticWindowSize, false);
}
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided.
* <p>
* This defaults to inaccurate time. Meaning that durations and delays may under report (but
* NEVER OVER what they actually were). This has the least performance impact. If you want more
* accurate time consider using one of the constructors that accepts a boolean for accurate time.
*
* @param threadFactory factory to make thread for scheduler
* @param maxStatisticWindowSize maximum number of samples to keep internally
*/
public SingleThreadSchedulerStatisticTracker(ThreadFactory threadFactory,
int maxStatisticWindowSize) {
this(null, DEFAULT_LOW_PRIORITY_MAX_WAIT_IN_MS, threadFactory, maxStatisticWindowSize);
}
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided.
* <p>
* This defaults to inaccurate time. Meaning that durations and delays may under report (but
* NEVER OVER what they actually were). This has the least performance impact. If you want more
* accurate time consider using one of the constructors that accepts a boolean for accurate time.
*
* @param defaultPriority Default priority for tasks which are submitted without any specified priority
* @param maxWaitForLowPriorityInMs time low priority tasks to wait if there are high priority tasks ready to run
* @param threadFactory factory to make thread for scheduler
* @param maxStatisticWindowSize maximum number of samples to keep internally
*/
public SingleThreadSchedulerStatisticTracker(TaskPriority defaultPriority,
long maxWaitForLowPriorityInMs,
ThreadFactory threadFactory,
int maxStatisticWindowSize) {
this(defaultPriority, maxWaitForLowPriorityInMs, threadFactory, maxStatisticWindowSize, false);
}
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided. This defaults to using a daemon thread for the scheduler.
*
* @param maxStatisticWindowSize maximum number of samples to keep internally
* @param accurateTime {@code true} to ensure that delays and durations are not under reported
*/
public SingleThreadSchedulerStatisticTracker(int maxStatisticWindowSize, boolean accurateTime) {
this(null, DEFAULT_LOW_PRIORITY_MAX_WAIT_IN_MS, maxStatisticWindowSize, accurateTime);
}
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided. This defaults to using a daemon thread for the scheduler.
*
* @param defaultPriority Default priority for tasks which are submitted without any specified priority
* @param maxWaitForLowPriorityInMs time low priority tasks to wait if there are high priority tasks ready to run
* @param maxStatisticWindowSize maximum number of samples to keep internally
* @param accurateTime {@code true} to ensure that delays and durations are not under reported
*/
public SingleThreadSchedulerStatisticTracker(TaskPriority defaultPriority,
long maxWaitForLowPriorityInMs,
int maxStatisticWindowSize, boolean accurateTime) {
this(defaultPriority, maxWaitForLowPriorityInMs, true, maxStatisticWindowSize, accurateTime);
}
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided.
*
* @param daemonThread {@code true} if scheduler thread should be a daemon thread
* @param maxStatisticWindowSize maximum number of samples to keep internally
* @param accurateTime {@code true} to ensure that delays and durations are not under reported
*/
public SingleThreadSchedulerStatisticTracker(boolean daemonThread, int maxStatisticWindowSize,
boolean accurateTime) {
this(null, DEFAULT_LOW_PRIORITY_MAX_WAIT_IN_MS,
daemonThread, maxStatisticWindowSize, accurateTime);
}
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided.
*
* @param defaultPriority Default priority for tasks which are submitted without any specified priority
* @param maxWaitForLowPriorityInMs time low priority tasks to wait if there are high priority tasks ready to run
* @param daemonThread {@code true} if scheduler thread should be a daemon thread
* @param maxStatisticWindowSize maximum number of samples to keep internally
* @param accurateTime {@code true} to ensure that delays and durations are not under reported
*/
public SingleThreadSchedulerStatisticTracker(TaskPriority defaultPriority,
long maxWaitForLowPriorityInMs, boolean daemonThread,
int maxStatisticWindowSize, boolean accurateTime) {
this(defaultPriority, maxWaitForLowPriorityInMs,
new ConfigurableThreadFactory(SingleThreadScheduler.class.getSimpleName() + "-",
true, daemonThread, Thread.NORM_PRIORITY, null, null),
maxStatisticWindowSize, accurateTime);
}
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided.
*
* @param threadFactory factory to make thread for scheduler
* @param maxStatisticWindowSize maximum number of samples to keep internally
* @param accurateTime {@code true} to ensure that delays and durations are not under reported
*/
public SingleThreadSchedulerStatisticTracker(ThreadFactory threadFactory,
int maxStatisticWindowSize, boolean accurateTime) {
this(null, DEFAULT_LOW_PRIORITY_MAX_WAIT_IN_MS, threadFactory, maxStatisticWindowSize, accurateTime);
}
/**
* Constructs a new {@link SingleThreadScheduler}. No threads will start until the first task
* is provided.
*
* @param defaultPriority Default priority for tasks which are submitted without any specified priority
* @param maxWaitForLowPriorityInMs time low priority tasks to wait if there are high priority tasks ready to run
* @param threadFactory factory to make thread for scheduler
* @param maxStatisticWindowSize maximum number of samples to keep internally
* @param accurateTime {@code true} to ensure that delays and durations are not under reported
*/
public SingleThreadSchedulerStatisticTracker(TaskPriority defaultPriority,
long maxWaitForLowPriorityInMs,
ThreadFactory threadFactory,
int maxStatisticWindowSize, boolean accurateTime) {
super(defaultPriority,
new StatisticTrackerSchedulerManager(defaultPriority,
maxWaitForLowPriorityInMs, threadFactory,
maxStatisticWindowSize, accurateTime));
this.statisticTracker = ((StatisticTrackerSchedulerManager)super.sManager).getStatisticTracker();
}
@Override
public List<Runnable> shutdownNow() {
// we must unwrap our statistic tracker runnables
List<Runnable> wrappedRunnables = super.shutdownNow();
List<Runnable> result = new ArrayList<>(wrappedRunnables.size());
Iterator<Runnable> it = wrappedRunnables.iterator();
while (it.hasNext()) {
Runnable r = it.next();
if (r instanceof TaskStatWrapper) {
TaskStatWrapper tw = (TaskStatWrapper)r;
if (! (tw.task instanceof Future) || ! ((Future<?>)tw.task).isCancelled()) {
result.add(tw.task);
}
} else {
// this typically happens in unit tests, but could happen by an extending class
result.add(r);
}
}
return result;
}
@Override
public List<Long> getExecutionDelaySamples() {
return statisticTracker.getExecutionDelaySamples();
}
@Override
public List<Long> getExecutionDelaySamples(TaskPriority priority) {
return statisticTracker.getExecutionDelaySamples(priority);
}
@Override
public double getAverageExecutionDelay() {
return statisticTracker.getAverageExecutionDelay();
}
@Override
public double getAverageExecutionDelay(TaskPriority priority) {
return statisticTracker.getAverageExecutionDelay(priority);
}
@Override
public Map<Double, Long> getExecutionDelayPercentiles(double... percentiles) {
return statisticTracker.getExecutionDelayPercentiles(percentiles);
}
@Override
public Map<Double, Long> getExecutionDelayPercentiles(TaskPriority priority,
double... percentiles) {
return statisticTracker.getExecutionDelayPercentiles(priority, percentiles);
}
@Override
public List<Long> getExecutionDurationSamples() {
return statisticTracker.getExecutionDurationSamples();
}
@Override
public List<Long> getExecutionDurationSamples(TaskPriority priority) {
return statisticTracker.getExecutionDurationSamples(priority);
}
@Override
public double getAverageExecutionDuration() {
return statisticTracker.getAverageExecutionDuration();
}
@Override
public double getAverageExecutionDuration(TaskPriority priority) {
return statisticTracker.getAverageExecutionDuration(priority);
}
@Override
public Map<Double, Long> getExecutionDurationPercentiles(double... percentiles) {
return statisticTracker.getExecutionDurationPercentiles(percentiles);
}
@Override
public Map<Double, Long> getExecutionDurationPercentiles(TaskPriority priority,
double... percentiles) {
return statisticTracker.getExecutionDurationPercentiles(priority, percentiles);
}
@Override
public List<Pair<Runnable, StackTraceElement[]>> getLongRunningTasks(long durationLimitMillis) {
return statisticTracker.getLongRunningTasks(durationLimitMillis);
}
@Override
public int getLongRunningTasksQty(long durationLimitMillis) {
return statisticTracker.getLongRunningTasksQty(durationLimitMillis);
}
@Override
public void resetCollectedStats() {
statisticTracker.resetCollectedStats();
}
@Override
public long getTotalExecutionCount() {
return statisticTracker.getTotalExecutionCount();
}
@Override
public long getTotalExecutionCount(TaskPriority priority) {
return statisticTracker.getTotalExecutionCount(priority);
}
/**
* Implementation of {@link SchedulerManager} which uses a
* {@link NoThreadSchedulerStatisticTracker} internally, and can be queried for a reference of
* this tracker. Allowing easy access to the internal statistics.
*
* @since 4.5.0
*/
protected static class StatisticTrackerSchedulerManager extends SchedulerManager {
public StatisticTrackerSchedulerManager(TaskPriority defaultPriority,
long maxWaitForLowPriorityInMs,
ThreadFactory threadFactory,
int maxStatisticWindowSize, boolean accurateTime) {
super(new NoThreadSchedulerStatisticTracker(defaultPriority, maxWaitForLowPriorityInMs,
maxStatisticWindowSize, accurateTime),
threadFactory);
}
/**
* Get instance of internal {@link NoThreadSchedulerStatisticTracker}.
*
* @return Statistic tracker instance that is used for task management and tracking.
*/
public NoThreadSchedulerStatisticTracker getStatisticTracker() {
return (NoThreadSchedulerStatisticTracker)super.scheduler;
}
}
}