@@ -47,14 +47,18 @@ public final class TaskExecutionTimeTrackingEsThreadPoolExecutor extends EsThrea
4747 private final boolean trackOngoingTasks ;
4848 // The set of currently running tasks and the timestamp of when they started execution in the Executor.
4949 private final Map <Runnable , Long > ongoingTasks = new ConcurrentHashMap <>();
50- private volatile long lastPollTimeAPM = System .nanoTime ();
51- private volatile long lastTotalExecutionTimeAPM = 0 ;
52- private volatile long lastPollTimeNanosAllocation = System .nanoTime ();
53- private volatile long lastTotalExecutionTimeAllocation = 0 ;
5450 private final ExponentialBucketHistogram queueLatencyMillisHistogram = new ExponentialBucketHistogram (QUEUE_LATENCY_HISTOGRAM_BUCKETS );
55- private final boolean trackQueueLatencyEWMA ;
51+ private final boolean trackQueueLatencyAverage ;
5652 private final ExponentiallyWeightedMovingAverage queueLatencyMillisEWMA ;
5753
54+ public enum UtilizationTrackingPurpose {
55+ APM ,
56+ ALLOCATION ,
57+ }
58+
59+ private volatile UtilizationTracker apmUtilizationTracker ;
60+ private volatile UtilizationTracker allocationUtilizationTracker ;
61+
5862 TaskExecutionTimeTrackingEsThreadPoolExecutor (
5963 String name ,
6064 int corePoolSize ,
@@ -73,8 +77,10 @@ public final class TaskExecutionTimeTrackingEsThreadPoolExecutor extends EsThrea
7377 this .runnableWrapper = runnableWrapper ;
7478 this .executionEWMA = new ExponentiallyWeightedMovingAverage (trackingConfig .getExecutionTimeEwmaAlpha (), 0 );
7579 this .trackOngoingTasks = trackingConfig .trackOngoingTasks ();
76- this .trackQueueLatencyEWMA = trackingConfig .trackQueueLatencyEWMA ();
80+ this .trackQueueLatencyAverage = trackingConfig .trackQueueLatencyAverage ();
7781 this .queueLatencyMillisEWMA = new ExponentiallyWeightedMovingAverage (trackingConfig .getQueueLatencyEwmaAlpha (), 0 );
82+ this .apmUtilizationTracker = new UtilizationTracker ();
83+ this .allocationUtilizationTracker = new UtilizationTracker ();
7884 }
7985
8086 public List <Instrument > setupMetrics (MeterRegistry meterRegistry , String threadPoolName ) {
@@ -102,7 +108,7 @@ public List<Instrument> setupMetrics(MeterRegistry meterRegistry, String threadP
102108 ThreadPool .THREAD_POOL_METRIC_PREFIX + threadPoolName + THREAD_POOL_METRIC_NAME_UTILIZATION ,
103109 "fraction of maximum thread time utilized for " + threadPoolName ,
104110 "fraction" ,
105- () -> new DoubleWithAttributes (pollUtilization (true , false ), Map .of ())
111+ () -> new DoubleWithAttributes (pollUtilization (UtilizationTrackingPurpose . APM ), Map .of ())
106112 )
107113 );
108114 }
@@ -143,44 +149,30 @@ public int getCurrentQueueSize() {
143149 return getQueue ().size ();
144150 }
145151
146- public double getQueuedTaskLatencyMillisEWMA () {
147- if (trackQueueLatencyEWMA == false ) {
152+ public double getQueuedTaskLatencyMillis () {
153+ if (trackQueueLatencyAverage == false ) {
148154 return 0 ;
149155 }
150156 return queueLatencyMillisEWMA .getAverage ();
151157 }
152158
153159 /**
154160 * Returns the fraction of the maximum possible thread time that was actually used since the last time this method was called.
155- * One of the two boolean parameters must be true, while the other false. There are two periodic pulling mechanisms that access
156- * utilization reporting .
161+ * There are two periodic pulling mechanisms that access utilization reporting: {@link UtilizationTrackingPurpose} distinguishes the
162+ * caller .
157163 *
158164 * @return the utilization as a fraction, in the range [0, 1]. This may return >1 if a task completed in the time range but started
159165 * earlier, contributing a larger execution time.
160166 */
161- public double pollUtilization (boolean forAPM , boolean forAllocation ) {
162- assert forAPM ^ forAllocation : "Can only collect one or the other, APM: " + forAPM + ", Allocation: " + forAllocation ;
163-
164- final long currentTotalExecutionTimeNanos = totalExecutionTime .sum ();
165- final long currentPollTimeNanos = System .nanoTime ();
166-
167- final long totalExecutionTimeSinceLastPollNanos = currentTotalExecutionTimeNanos - (forAPM
168- ? lastTotalExecutionTimeAPM
169- : lastTotalExecutionTimeAllocation );
170- final long timeSinceLastPoll = currentPollTimeNanos - (forAPM ? lastPollTimeAPM : lastPollTimeNanosAllocation );
171- final long maximumExecutionTimeSinceLastPollNanos = timeSinceLastPoll * getMaximumPoolSize ();
172- final double utilizationSinceLastPoll = (double ) totalExecutionTimeSinceLastPollNanos / maximumExecutionTimeSinceLastPollNanos ;
173-
174- if (forAPM ) {
175- lastTotalExecutionTimeAPM = currentTotalExecutionTimeNanos ;
176- lastPollTimeAPM = currentPollTimeNanos ;
177- } else {
178- assert forAllocation ;
179- lastTotalExecutionTimeAllocation = currentTotalExecutionTimeNanos ;
180- lastPollTimeNanosAllocation = currentPollTimeNanos ;
167+ public double pollUtilization (UtilizationTrackingPurpose utilizationTrackingPurpose ) {
168+ switch (utilizationTrackingPurpose ) {
169+ case APM :
170+ return apmUtilizationTracker .pollUtilization ();
171+ case ALLOCATION :
172+ return allocationUtilizationTracker .pollUtilization ();
173+ default :
174+ throw new IllegalStateException ("No operation defined for [" + utilizationTrackingPurpose + "]" );
181175 }
182-
183- return utilizationSinceLastPoll ;
184176 }
185177
186178 @ Override
@@ -197,7 +189,7 @@ protected void beforeExecute(Thread t, Runnable r) {
197189 var queueLatencyMillis = TimeUnit .NANOSECONDS .toMillis (taskQueueLatency );
198190 queueLatencyMillisHistogram .addObservation (queueLatencyMillis );
199191
200- if (trackQueueLatencyEWMA ) {
192+ if (trackQueueLatencyAverage ) {
201193 queueLatencyMillisEWMA .addValue (queueLatencyMillis );
202194 }
203195 }
@@ -241,8 +233,8 @@ protected void appendThreadPoolExecutorDetails(StringBuilder sb) {
241233 .append ("total task execution time = " )
242234 .append (TimeValue .timeValueNanos (getTotalTaskExecutionTime ()))
243235 .append (", " );
244- if (trackQueueLatencyEWMA ) {
245- sb .append ("task queue EWMA = " ).append (TimeValue .timeValueMillis ((long ) getQueuedTaskLatencyMillisEWMA ())).append (", " );
236+ if (trackQueueLatencyAverage ) {
237+ sb .append ("task queue EWMA = " ).append (TimeValue .timeValueMillis ((long ) getQueuedTaskLatencyMillis ())).append (", " );
246238 }
247239 }
248240
@@ -269,6 +261,33 @@ public double getQueueLatencyEwmaAlpha() {
269261
270262 // Used for testing
271263 public boolean trackingQueueLatencyEwma () {
272- return trackQueueLatencyEWMA ;
264+ return trackQueueLatencyAverage ;
265+ }
266+
267+ /**
268+ * Supports periodic polling for thread pool utilization. Tracks state since the last polling request so that the average utilization
269+ * since the last poll can be calculated for the next polling request.
270+ *
271+ * Uses the difference of {@link #totalExecutionTime} since the last polling request to determine how much activity has occurred.
272+ */
273+ private class UtilizationTracker {
274+ volatile long lastPollTime = System .nanoTime ();;
275+ volatile long lastTotalExecutionTime = 0 ;
276+
277+ public double pollUtilization () {
278+ final long currentTotalExecutionTimeNanos = totalExecutionTime .sum ();
279+ final long currentPollTimeNanos = System .nanoTime ();
280+
281+ final long totalExecutionTimeSinceLastPollNanos = currentTotalExecutionTimeNanos - lastTotalExecutionTime ;
282+ final long timeSinceLastPoll = currentPollTimeNanos - lastPollTime ;
283+
284+ final long maximumExecutionTimeSinceLastPollNanos = timeSinceLastPoll * getMaximumPoolSize ();
285+ final double utilizationSinceLastPoll = (double ) totalExecutionTimeSinceLastPollNanos / maximumExecutionTimeSinceLastPollNanos ;
286+
287+ lastTotalExecutionTime = currentTotalExecutionTimeNanos ;
288+ lastPollTime = currentPollTimeNanos ;
289+
290+ return utilizationSinceLastPoll ;
291+ }
273292 }
274293}
0 commit comments