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 directmicrometer-observation
dependency fromspring-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 Runnable
s
* 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.