micrometer-metrics/micrometer

Differences between monitoring ThreadPoolExecutor and ForkJoinPool

Open

#7089 opened on Jan 16, 2026

View on GitHub
 (5 comments) (0 reactions) (0 assignees)Java (4,220 stars) (935 forks)batch import
enhancementhelp wanted

Description

While I was reviewing https://github.com/micrometer-metrics/micrometer/pull/7083, I saw that if I use ExecutorServiceMetrics.monitor for a ThreadPoolExecutor (Executors.newFixedThreadPool) and a ForkJoinPool I get a few unexpected differences in the outputs.

executor.active and executor.queued have units (threads and tasks) for ThreadPoolExecutor while for ForkJoinPool they do not, this means that changing this:

- ExecutorServiceMetrics.monitor(registry, Executors.newFixedThreadPool(2), name, tags);
+ ExecutorServiceMetrics.monitor(registry, new ForkJoinPool(2), name, tags);

will result in different time series (executor_active_threads vs. executor_active) in the Prometheus output:

- executor_active_threads{name="test"} 0.0
+ executor_active{name="test"} 0.0
- executor_queued_tasks{name="test"} 0.0
+ executor_queued{name="test"} 0.0

(Fixing this is a breaking change.)

public class Demo {
    public static void main(String[] args) {
        generateAndPrintMetricsFor(Executors.newFixedThreadPool(2));
        generateAndPrintMetricsFor(new ForkJoinPool(2));
    }

    private static void generateAndPrintMetricsFor(ExecutorService executorServiceToMonitor) {
        PrometheusMeterRegistry registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
        ExecutorService executorService = ExecutorServiceMetrics.monitor(registry, executorServiceToMonitor, "test", Tags.empty());
        List<Future<?>> futures = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            futures.add(executorService.submit(Demo::sleep));
        }
        for (Future<?> future : futures) {
            try {
                future.get(1_000, TimeUnit.MILLISECONDS);
            }
            catch (Exception e) {
                System.out.println(e.getMessage());
            }
            finally {
                future.cancel(true);
            }
        }

        System.out.println("===== Metrics for " + executorService.getClass().getSimpleName());
        System.out.println(registry.scrape());
        executorService.shutdown();
    }

    private static void sleep() {
        try {
            Thread.sleep(100);
        }
        catch (InterruptedException e) {
            // noop
        }
    }
}

Contributor guide