See https://github.com/micrometer-metrics/micrometer/issues/3250 for background.

Micrometer allows to instrument JDK ExecutorService instances using its ExecutorServiceMetrics class. Micrometer users would like to leverage this feature for our ThreadPoolTaskExecutor, but doing so erases its Spring-specific support (like graceful shutdown) as it exposes it as a ScheduledExecutorService.

Micrometer itself cannot depend on Spring Framework for this, so we should investigate several solutions:

  • instrument ThreadPoolTaskExecutor directly (at the cost of a direct micrometer-observation dependency from spring-context
  • provide an optional wrapper/JDK proxy mechanism that instrument this class with an optional dependency on micrometer-observation

Comment From: bclozel

I had a proper look at implementing this and I'm actually doubting we need to do anything now.

Spring Framework already instruments scheduled tasks, providing detailed information about the class and method being executed and when.

In the case of a ThreadPoolTaskExecutor, we only know that we get Runnable and we cannot get any more information about it. There is not much value provided in timing a Runnable and not being able to give any meaningful information about it. My feedback is actually very similar to an earlier comment of mine.

In Micrometer, ExecutorServiceMetrics is both: * wrapping the executor to time executed Runnables * creating meters based on executor data

The main pain point for Micrometer users is that once wrapped, an ThreadPoolTaskExecutor is just a java.util.concurrent.Executor and we can't expose it as a bean anymore as it's missing the specific lifecycle contracts from a Spring ThreadPoolTaskExecutor.

I think that to solve this issue, instrumentation should be done directly when submitting the task:

MailMessage message = //...
Observation observation = Observation.createNotStarted("mail.send", this.observationRegistry);
observation.highCardinalityKeyValue("mail.to", message.getTo());
taskExecutor.submit(observation.wrap(() -> sendMail(message)));

If we were to instrument tasks at the Framework level, we would do so through a task decorator. This task decorator can be then configured on the ThreadPoolTaskExecutor. It would not do much, unfortunately. Its entire implementation is this:

import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;

import org.springframework.core.task.TaskDecorator;

public class ObservationTaskDecorator implements TaskDecorator {

    private final ObservationRegistry observationRegistry;

    private final TaskObservationConvention observationConvention = new DefaultTaskObservationConvention();

    public ObservationTaskDecorator(ObservationRegistry observationRegistry) {
        this.observationRegistry = observationRegistry;
    }

    @Override
    public Runnable decorate(Runnable runnable) {
        Observation observation = Observation.createNotStarted("tasks.execution", this.observationRegistry);
        return observation.wrap(runnable);
    }

}

I think that the main issue is that ExecutorServiceMetrics is both wrapping the original executor and binding metrics to the meter registry. If developers could only bind metrics to the meter registry, they would then be able to use the Observation API to instrument custom tasks with useful information, or use a custom task decorator (similar to the one listed here, but with additional information).

I'm declining this issue for now as a result.