io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics Maven / Gradle / Ivy
Show all versions of micrometer-core Show documentation
/*
* Copyright 2017 VMware, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micrometer.core.instrument.binder.jvm;
import io.micrometer.core.instrument.*;
import io.micrometer.core.instrument.binder.BaseUnits;
import io.micrometer.core.instrument.binder.MeterBinder;
import io.micrometer.core.instrument.internal.TimedExecutor;
import io.micrometer.core.instrument.internal.TimedExecutorService;
import io.micrometer.core.instrument.internal.TimedScheduledExecutorService;
import io.micrometer.core.instrument.util.StringUtils;
import io.micrometer.core.lang.NonNullApi;
import io.micrometer.core.lang.NonNullFields;
import io.micrometer.core.lang.Nullable;
import io.micrometer.core.util.internal.logging.InternalLogger;
import io.micrometer.core.util.internal.logging.InternalLoggerFactory;
import java.lang.reflect.Field;
import java.util.concurrent.*;
import static java.util.Arrays.asList;
/**
* Monitors the status of executor service pools. Does not record timings on operations
* executed in the {@link ExecutorService}, as this requires the instance to be wrapped.
* Timings are provided separately by wrapping the executor service with
* {@link TimedExecutorService}.
*
* Supports {@link ThreadPoolExecutor} and {@link ForkJoinPool} types of
* {@link ExecutorService}. Some libraries may provide a wrapper type for
* {@link ExecutorService}, like {@link TimedExecutorService}. Make sure to pass the
* underlying, unwrapped ExecutorService to this MeterBinder, if it is wrapped in another
* type.
*
* @author Jon Schneider
* @author Clint Checketts
* @author Johnny Lim
*/
@NonNullApi
@NonNullFields
public class ExecutorServiceMetrics implements MeterBinder {
private static boolean allowIllegalReflectiveAccess = true;
private static final InternalLogger log = InternalLoggerFactory.getInstance(ExecutorServiceMetrics.class);
private static final String DEFAULT_EXECUTOR_METRIC_PREFIX = "";
@Nullable
private final ExecutorService executorService;
private final Iterable tags;
private final String metricPrefix;
public ExecutorServiceMetrics(@Nullable ExecutorService executorService, String executorServiceName,
Iterable tags) {
this(executorService, executorServiceName, DEFAULT_EXECUTOR_METRIC_PREFIX, tags);
}
/**
* Create an {@code ExecutorServiceMetrics} instance.
* @param executorService executor service
* @param executorServiceName executor service name which will be used as
* {@literal name} tag
* @param metricPrefix metrics prefix which will be used to prefix metric name
* @param tags additional tags
* @since 1.5.0
*/
public ExecutorServiceMetrics(@Nullable ExecutorService executorService, String executorServiceName,
String metricPrefix, Iterable tags) {
this.executorService = executorService;
this.tags = Tags.concat(tags, "name", executorServiceName);
this.metricPrefix = sanitizePrefix(metricPrefix);
}
/**
* Record metrics on the use of an {@link Executor}.
* @param registry The registry to bind metrics to.
* @param executor The executor to instrument.
* @param executorName Will be used to tag metrics with "name".
* @param tags Tags to apply to all recorded metrics.
* @return The instrumented executor, proxied.
*/
public static Executor monitor(MeterRegistry registry, Executor executor, String executorName, Iterable tags) {
return monitor(registry, executor, executorName, DEFAULT_EXECUTOR_METRIC_PREFIX, tags);
}
/**
* Record metrics on the use of an {@link Executor}.
* @param registry The registry to bind metrics to.
* @param executor The executor to instrument.
* @param executorName Will be used to tag metrics with "name".
* @param metricPrefix The prefix to use with meter names. This differentiates
* executor metrics that may have different tag sets.
* @param tags Tags to apply to all recorded metrics.
* @return The instrumented executor, proxied.
* @since 1.5.0
*/
public static Executor monitor(MeterRegistry registry, Executor executor, String executorName, String metricPrefix,
Iterable tags) {
if (executor instanceof ExecutorService) {
return monitor(registry, (ExecutorService) executor, executorName, metricPrefix, tags);
}
return new TimedExecutor(registry, executor, executorName, sanitizePrefix(metricPrefix), tags);
}
/**
* Record metrics on the use of an {@link Executor}.
* @param registry The registry to bind metrics to.
* @param executor The executor to instrument.
* @param executorName Will be used to tag metrics with "name".
* @param tags Tags to apply to all recorded metrics.
* @return The instrumented executor, proxied.
*/
public static Executor monitor(MeterRegistry registry, Executor executor, String executorName, Tag... tags) {
return monitor(registry, executor, executorName, DEFAULT_EXECUTOR_METRIC_PREFIX, tags);
}
/**
* Record metrics on the use of an {@link Executor}.
* @param registry The registry to bind metrics to.
* @param executor The executor to instrument.
* @param executorName Will be used to tag metrics with "name".
* @param metricPrefix The prefix to use with meter names. This differentiates
* executor metrics that may have different tag sets.
* @param tags Tags to apply to all recorded metrics.
* @return The instrumented executor, proxied.
* @since 1.5.0
*/
public static Executor monitor(MeterRegistry registry, Executor executor, String executorName, String metricPrefix,
Tag... tags) {
return monitor(registry, executor, executorName, metricPrefix, asList(tags));
}
/**
* Record metrics on the use of an {@link ExecutorService}.
* @param registry The registry to bind metrics to.
* @param executor The executor to instrument.
* @param executorServiceName Will be used to tag metrics with "name".
* @param tags Tags to apply to all recorded metrics.
* @return The instrumented executor, proxied.
*/
public static ExecutorService monitor(MeterRegistry registry, ExecutorService executor, String executorServiceName,
Iterable tags) {
return monitor(registry, executor, executorServiceName, DEFAULT_EXECUTOR_METRIC_PREFIX, tags);
}
/**
* Record metrics on the use of an {@link ExecutorService}.
* @param registry The registry to bind metrics to.
* @param executor The executor to instrument.
* @param executorServiceName Will be used to tag metrics with "name".
* @param metricPrefix The prefix to use with meter names. This differentiates
* executor metrics that may have different tag sets.
* @param tags Tags to apply to all recorded metrics.
* @return The instrumented executor, proxied.
* @since 1.5.0
*/
public static ExecutorService monitor(MeterRegistry registry, ExecutorService executor, String executorServiceName,
String metricPrefix, Iterable tags) {
if (executor instanceof ScheduledExecutorService) {
return monitor(registry, (ScheduledExecutorService) executor, executorServiceName, metricPrefix, tags);
}
new ExecutorServiceMetrics(executor, executorServiceName, metricPrefix, tags).bindTo(registry);
return new TimedExecutorService(registry, executor, executorServiceName, sanitizePrefix(metricPrefix), tags);
}
/**
* Record metrics on the use of an {@link ExecutorService}.
* @param registry The registry to bind metrics to.
* @param executor The executor to instrument.
* @param executorServiceName Will be used to tag metrics with "name".
* @param tags Tags to apply to all recorded metrics.
* @return The instrumented executor, proxied.
*/
public static ExecutorService monitor(MeterRegistry registry, ExecutorService executor, String executorServiceName,
Tag... tags) {
return monitor(registry, executor, executorServiceName, DEFAULT_EXECUTOR_METRIC_PREFIX, tags);
}
/**
* Record metrics on the use of an {@link ExecutorService}.
* @param registry The registry to bind metrics to.
* @param executor The executor to instrument.
* @param executorServiceName Will be used to tag metrics with "name".
* @param metricPrefix The prefix to use with meter names. This differentiates
* executor metrics that may have different tag sets.
* @param tags Tags to apply to all recorded metrics.
* @since 1.5.0
* @return The instrumented executor, proxied.
*/
public static ExecutorService monitor(MeterRegistry registry, ExecutorService executor, String executorServiceName,
String metricPrefix, Tag... tags) {
return monitor(registry, executor, executorServiceName, metricPrefix, asList(tags));
}
/**
* Record metrics on the use of a {@link ScheduledExecutorService}.
* @param registry The registry to bind metrics to.
* @param executor The scheduled executor to instrument.
* @param executorServiceName Will be used to tag metrics with "name".
* @param tags Tags to apply to all recorded metrics.
* @return The instrumented scheduled executor, proxied.
* @since 1.3.0
*/
public static ScheduledExecutorService monitor(MeterRegistry registry, ScheduledExecutorService executor,
String executorServiceName, Iterable tags) {
return monitor(registry, executor, executorServiceName, DEFAULT_EXECUTOR_METRIC_PREFIX, tags);
}
/**
* Record metrics on the use of a {@link ScheduledExecutorService}.
* @param registry The registry to bind metrics to.
* @param executor The scheduled executor to instrument.
* @param executorServiceName Will be used to tag metrics with "name".
* @param metricPrefix The prefix to use with meter names. This differentiates
* executor metrics that may have different tag sets.
* @param tags Tags to apply to all recorded metrics.
* @return The instrumented scheduled executor, proxied.
* @since 1.5.0
*/
public static ScheduledExecutorService monitor(MeterRegistry registry, ScheduledExecutorService executor,
String executorServiceName, String metricPrefix, Iterable tags) {
new ExecutorServiceMetrics(executor, executorServiceName, metricPrefix, tags).bindTo(registry);
return new TimedScheduledExecutorService(registry, executor, executorServiceName, sanitizePrefix(metricPrefix),
tags);
}
/**
* Record metrics on the use of a {@link ScheduledExecutorService}.
* @param registry The registry to bind metrics to.
* @param executor The scheduled executor to instrument.
* @param executorServiceName Will be used to tag metrics with "name".
* @param tags Tags to apply to all recorded metrics.
* @return The instrumented scheduled executor, proxied.
* @since 1.3.0
*/
public static ScheduledExecutorService monitor(MeterRegistry registry, ScheduledExecutorService executor,
String executorServiceName, Tag... tags) {
return monitor(registry, executor, executorServiceName, DEFAULT_EXECUTOR_METRIC_PREFIX, tags);
}
/**
* Record metrics on the use of a {@link ScheduledExecutorService}.
* @param registry The registry to bind metrics to.
* @param executor The scheduled executor to instrument.
* @param executorServiceName Will be used to tag metrics with "name".
* @param metricPrefix The prefix to use with meter names. This differentiates
* executor metrics that may have different tag sets.
* @param tags Tags to apply to all recorded metrics.
* @return The instrumented scheduled executor, proxied.
* @since 1.5.0
*/
public static ScheduledExecutorService monitor(MeterRegistry registry, ScheduledExecutorService executor,
String executorServiceName, String metricPrefix, Tag... tags) {
return monitor(registry, executor, executorServiceName, metricPrefix, asList(tags));
}
private static String sanitizePrefix(String metricPrefix) {
if (StringUtils.isBlank(metricPrefix))
return "";
if (!metricPrefix.endsWith("."))
return metricPrefix + ".";
return metricPrefix;
}
@Override
public void bindTo(MeterRegistry registry) {
if (executorService == null) {
return;
}
String className = executorService.getClass().getName();
if (executorService instanceof ThreadPoolExecutor) {
monitor(registry, (ThreadPoolExecutor) executorService);
}
else if (executorService instanceof ForkJoinPool) {
monitor(registry, (ForkJoinPool) executorService);
}
else if (allowIllegalReflectiveAccess) {
if (className.equals("java.util.concurrent.Executors$DelegatedScheduledExecutorService")) {
monitor(registry, unwrapThreadPoolExecutor(executorService, executorService.getClass()));
}
else if (className.equals("java.util.concurrent.Executors$FinalizableDelegatedExecutorService")) {
monitor(registry,
unwrapThreadPoolExecutor(executorService, executorService.getClass().getSuperclass()));
}
else {
log.warn("Failed to bind as {} is unsupported.", className);
}
}
else {
log.warn("Failed to bind as {} is unsupported or reflective access is not allowed.", className);
}
}
/**
* Every ScheduledThreadPoolExecutor created by {@link Executors} is wrapped. Also,
* {@link Executors#newSingleThreadExecutor()} wrap a regular
* {@link ThreadPoolExecutor}.
*/
@Nullable
private ThreadPoolExecutor unwrapThreadPoolExecutor(ExecutorService executor, Class wrapper) {
try {
Field e = wrapper.getDeclaredField("e");
e.setAccessible(true);
return (ThreadPoolExecutor) e.get(executor);
}
catch (NoSuchFieldException | IllegalAccessException | RuntimeException e) {
// Cannot use InaccessibleObjectException since it was introduced in Java 9,
// so catch all RuntimeExceptions instead
// Do nothing. We simply can't get to the underlying ThreadPoolExecutor.
log.info("Cannot unwrap ThreadPoolExecutor for monitoring from {} due to {}: {}", wrapper.getName(),
e.getClass().getName(), e.getMessage());
}
return null;
}
private void monitor(MeterRegistry registry, @Nullable ThreadPoolExecutor tp) {
if (tp == null) {
return;
}
FunctionCounter.builder(metricPrefix + "executor.completed", tp, ThreadPoolExecutor::getCompletedTaskCount)
.tags(tags).description("The approximate total number of tasks that have completed execution")
.baseUnit(BaseUnits.TASKS).register(registry);
Gauge.builder(metricPrefix + "executor.active", tp, ThreadPoolExecutor::getActiveCount).tags(tags)
.description("The approximate number of threads that are actively executing tasks")
.baseUnit(BaseUnits.THREADS).register(registry);
Gauge.builder(metricPrefix + "executor.queued", tp, tpRef -> tpRef.getQueue().size()).tags(tags)
.description("The approximate number of tasks that are queued for execution").baseUnit(BaseUnits.TASKS)
.register(registry);
Gauge.builder(metricPrefix + "executor.queue.remaining", tp, tpRef -> tpRef.getQueue().remainingCapacity())
.tags(tags)
.description("The number of additional elements that this queue can ideally accept without blocking")
.baseUnit(BaseUnits.TASKS).register(registry);
Gauge.builder(metricPrefix + "executor.pool.size", tp, ThreadPoolExecutor::getPoolSize).tags(tags)
.description("The current number of threads in the pool").baseUnit(BaseUnits.THREADS)
.register(registry);
Gauge.builder(metricPrefix + "executor.pool.core", tp, ThreadPoolExecutor::getCorePoolSize).tags(tags)
.description("The core number of threads for the pool").baseUnit(BaseUnits.THREADS).register(registry);
Gauge.builder(metricPrefix + "executor.pool.max", tp, ThreadPoolExecutor::getMaximumPoolSize).tags(tags)
.description("The maximum allowed number of threads in the pool").baseUnit(BaseUnits.THREADS)
.register(registry);
}
private void monitor(MeterRegistry registry, ForkJoinPool fj) {
FunctionCounter.builder(metricPrefix + "executor.steals", fj, ForkJoinPool::getStealCount).tags(tags)
.description("Estimate of the total number of tasks stolen from "
+ "one thread's work queue by another. The reported value "
+ "underestimates the actual total number of steals when the pool " + "is not quiescent")
.register(registry);
Gauge.builder(metricPrefix + "executor.queued", fj, ForkJoinPool::getQueuedTaskCount).tags(tags)
.description("An estimate of the total number of tasks currently held in queues by worker threads")
.register(registry);
Gauge.builder(metricPrefix + "executor.active", fj, ForkJoinPool::getActiveThreadCount).tags(tags)
.description("An estimate of the number of threads that are currently stealing or executing tasks")
.register(registry);
Gauge.builder(metricPrefix + "executor.running", fj, ForkJoinPool::getRunningThreadCount).tags(tags)
.description(
"An estimate of the number of worker threads that are not blocked waiting to join tasks or for other managed synchronization threads")
.register(registry);
}
/**
* Disable illegal reflective accesses.
*
* Java 9+ warns illegal reflective accesses, but some metrics from this binder depend
* on reflective access to {@link Executors}'s internal implementation details. This
* method allows to disable the feature to avoid the warnings.
* @since 1.6.0
*/
public static void disableIllegalReflectiveAccess() {
allowIllegalReflectiveAccess = false;
}
}