package org.threadly.concurrent.statistics;
import java.util.List;
import java.util.Map;
import org.threadly.concurrent.NoThreadScheduler;
import org.threadly.concurrent.TaskPriority;
import org.threadly.concurrent.collections.ConcurrentArrayList;
import org.threadly.concurrent.statistics.PriorityStatisticManager.TaskStatWrapper;
import org.threadly.util.Clock;
import org.threadly.util.Pair;
/**
* An implementation of {@link NoThreadScheduler} which tracks run and usage statistics. This is
* designed for testing and troubleshooting. It has a little more overhead from the normal
* {@link NoThreadScheduler}.
*
* 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
*/
public class NoThreadSchedulerStatisticTracker extends NoThreadScheduler
implements StatisticPriorityScheduler {
protected final PriorityStatisticManager statsManager;
/**
* Constructs a new {@link NoThreadSchedulerStatisticTracker} 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 NoThreadSchedulerStatisticTracker() {
this(null, DEFAULT_LOW_PRIORITY_MAX_WAIT_IN_MS);
}
/**
* Constructs a new {@link NoThreadSchedulerStatisticTracker} scheduler with specified default
* priority behavior.
* <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 NoThreadSchedulerStatisticTracker(TaskPriority defaultPriority,
long maxWaitForLowPriorityInMs) {
this(defaultPriority, maxWaitForLowPriorityInMs, 1000);
}
/**
* Constructs a new {@link NoThreadSchedulerStatisticTracker} 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 NoThreadSchedulerStatisticTracker(int maxStatisticWindowSize) {
this(null, DEFAULT_LOW_PRIORITY_MAX_WAIT_IN_MS, maxStatisticWindowSize);
}
/**
* Constructs a new {@link NoThreadSchedulerStatisticTracker} 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 NoThreadSchedulerStatisticTracker(int maxStatisticWindowSize, boolean accurateTime) {
this(null, DEFAULT_LOW_PRIORITY_MAX_WAIT_IN_MS, maxStatisticWindowSize, accurateTime);
}
/**
* Constructs a new {@link NoThreadSchedulerStatisticTracker} 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
*/
public NoThreadSchedulerStatisticTracker(TaskPriority defaultPriority,
long maxWaitForLowPriorityInMs,
int maxStatisticWindowSize) {
this(defaultPriority, maxWaitForLowPriorityInMs, maxStatisticWindowSize, false);
}
/**
* Constructs a new {@link NoThreadSchedulerStatisticTracker} scheduler, specifying all available
* construction options.
*
* @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 NoThreadSchedulerStatisticTracker(TaskPriority defaultPriority,
long maxWaitForLowPriorityInMs,
int maxStatisticWindowSize, boolean accurateTime) {
super(defaultPriority, maxWaitForLowPriorityInMs);
this.statsManager = new PriorityStatisticManager(maxStatisticWindowSize, accurateTime);
}
/**
* Wraps the provided task in our statistic wrapper. If the task is {@code null}, this will
* return {@code null} so that the parent class can do error checking.
*
* @param task Runnable to wrap
* @param priority Priority for runnable to execute
* @return Runnable which is our wrapped implementation
*/
private Runnable wrap(Runnable task, TaskPriority priority) {
if (priority == null) {
priority = getDefaultPriority();
}
if (task == null) {
return null;
} else {
return new TaskStatWrapper(statsManager, priority, task);
}
}
@Override
protected OneTimeTaskWrapper doSchedule(Runnable task, long delayInMillis, TaskPriority priority) {
return super.doSchedule(new TaskStatWrapper(statsManager, priority, task),
delayInMillis, priority);
}
@Override
public void scheduleWithFixedDelay(Runnable task, long initialDelay,
long recurringDelay, TaskPriority priority) {
super.scheduleWithFixedDelay(wrap(task, priority), initialDelay, recurringDelay, priority);
}
@Override
public void scheduleAtFixedRate(Runnable task, long initialDelay,
long period, TaskPriority priority) {
super.scheduleAtFixedRate(wrap(task, priority), initialDelay, period, priority);
}
@Override
public List<Long> getExecutionDelaySamples() {
return statsManager.getExecutionDelaySamples();
}
@Override
public List<Long> getExecutionDelaySamples(TaskPriority priority) {
return statsManager.getExecutionDelaySamples(priority);
}
@Override
public double getAverageExecutionDelay() {
return statsManager.getAverageExecutionDelay();
}
@Override
public double getAverageExecutionDelay(TaskPriority priority) {
return statsManager.getAverageExecutionDelay(priority);
}
@Override
public Map<Double, Long> getExecutionDelayPercentiles(double... percentiles) {
return statsManager.getExecutionDelayPercentiles(percentiles);
}
@Override
public Map<Double, Long> getExecutionDelayPercentiles(TaskPriority priority,
double... percentiles) {
return statsManager.getExecutionDelayPercentiles(priority, percentiles);
}
@Override
public List<Long> getExecutionDurationSamples() {
return statsManager.getExecutionDurationSamples();
}
@Override
public List<Long> getExecutionDurationSamples(TaskPriority priority) {
return statsManager.getExecutionDurationSamples(priority);
}
@Override
public double getAverageExecutionDuration() {
return statsManager.getAverageExecutionDuration();
}
@Override
public double getAverageExecutionDuration(TaskPriority priority) {
return statsManager.getAverageExecutionDuration(priority);
}
@Override
public Map<Double, Long> getExecutionDurationPercentiles(double... percentiles) {
return statsManager.getExecutionDurationPercentiles(percentiles);
}
@Override
public Map<Double, Long> getExecutionDurationPercentiles(TaskPriority priority,
double... percentiles) {
return statsManager.getExecutionDurationPercentiles(priority, percentiles);
}
@Override
public List<Pair<Runnable, StackTraceElement[]>> getLongRunningTasks(long durationLimitMillis) {
return statsManager.getLongRunningTasks(durationLimitMillis);
}
@Override
public int getLongRunningTasksQty(long durationLimitMillis) {
return statsManager.getLongRunningTasksQty(durationLimitMillis);
}
@Override
public void resetCollectedStats() {
statsManager.resetCollectedStats();
}
@Override
public long getTotalExecutionCount() {
return statsManager.getTotalExecutionCount();
}
@Override
public long getTotalExecutionCount(TaskPriority priority) {
return statsManager.getTotalExecutionCount(priority);
}
@Override
protected TaskWrapper getNextReadyTask() {
TaskWrapper result = super.getNextReadyTask();
// may not be a wrapper for internal tasks like shutdown
if (result != null && result.getContainedRunnable() instanceof TaskStatWrapper) {
long taskDelay = Clock.lastKnownForwardProgressingMillis() - result.getPureRunTime();
TaskStatWrapper statWrapper = (TaskStatWrapper)result.getContainedRunnable();
ConcurrentArrayList<Long> priorityStats =
statsManager.getExecutionDelaySamplesInternal(statWrapper.priority);
synchronized (priorityStats.getModificationLock()) {
priorityStats.add(taskDelay);
statsManager.trimWindow(priorityStats);
}
}
return result;
}
}