Reputation: 2934
I am trying to add time metrics to my spring-boot web application. Right now, the application uses micrometer, prometheus, and spring-boot-actuator.
I can connect to my app at http://localhost:8080/actuator/prometheus and see the list of default metrics like:
# HELP jvm_gc_memory_allocated_bytes_total Incremented for an increase in the size of the young generation memory pool after one GC to before the next
# TYPE jvm_gc_memory_allocated_bytes_total counter
jvm_gc_memory_allocated_bytes_total{application="my app",} 0.0
# HELP jvm_classes_loaded_classes The number of classes that are currently loaded in the Java virtual machine
# TYPE jvm_classes_loaded_classes gauge
jvm_classes_loaded_classes{application="my app",} 7581.0
...
Awesome!
But now I'd like to attach timing metrics to the methods of one of my controllers. I'd hoped it was as simple as adding an @Timed
annotation to the class:
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import io.micrometer.core.annotation.Timed;
@RestController
@Timed
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
@GetMapping("/greeting")
public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
doWork();
return buildResponse(name);
}
private void doWork() {
try {
Thread.sleep((long) (1000 * Math.random()));
} catch (InterruptedException e) {
}
}
private Greeting buildResponse(String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}
However, that doesn't appear to add any metrics to the http://localhost:8080/actuator/prometheus endpoint.
What steps do I need to take in order to have the endpoint report timing metrics of the GreetingController
class, and particularly the doWork
and buildResponse
methods?
-- update --
I've managed to get the results I want but it took a little more work than I was expecting, and I'm hoping that there is an easier way. I explicitly added Timer
objects to each method I wanted to measure like so:
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
@RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
@Autowired
MeterRegistry meterRegistry;
@GetMapping("/greeting")
public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
doWork();
return buildResponse(name);
}
private void doWork() {
Timer timer = meterRegistry.timer(this.getClass().getSimpleName() + ".doWork");
timer.record(() -> {
try {
Thread.sleep((long) (1000 * Math.random()));
} catch (InterruptedException e) {
}
});
}
private Greeting buildResponse(String name) {
AtomicReference<Greeting> response = new AtomicReference<>();
Timer timer = meterRegistry.timer(this.getClass().getSimpleName() + ".buildResponse");
timer.record(() -> {
response.set(new Greeting(counter.incrementAndGet(), String.format(template, name)));
});
return response.get();
}
}
But at least this gets me the results I was looking for:
# HELP GreetingController_buildResponse_seconds
# TYPE GreetingController_buildResponse_seconds summary
GreetingController_buildResponse_seconds_count{application="my app",} 10.0
GreetingController_buildResponse_seconds_sum{application="my app",} 0.001531409
# HELP GreetingController_buildResponse_seconds_max
# TYPE GreetingController_buildResponse_seconds_max gauge
GreetingController_buildResponse_seconds_max{application="my app",} 2.52253E-4
# HELP GreetingController_doWork_seconds_max
# TYPE GreetingController_doWork_seconds_max gauge
GreetingController_doWork_seconds_max{application="my app",} 0.941169892
# HELP GreetingController_doWork_seconds
# TYPE GreetingController_doWork_seconds summary
GreetingController_doWork_seconds_count{application="my app",} 10.0
GreetingController_doWork_seconds_sum{application="my app",} 4.767700907
Is there a cleaner way?
Upvotes: 3
Views: 2591
Reputation: 1027
in my case prometheusMeterRegistry were created to early and it was necessary to apply following hack
@Configuration
@ConditionalOnProperty(value = "app.metrics.enabled", matchIfMissing = true, havingValue = "true")
static class AppMetricsConfig {
@Bean
InitializingBean forcePrometheusPostProcessor(BeanPostProcessor meterRegistryPostProcessor,
PrometheusMeterRegistry registry) {
return () -> meterRegistryPostProcessor.postProcessAfterInitialization(registry, "");
}
/**
* This is required so that we can use the @Timed annotation
* on methods that we want to time.
* See: https://micrometer.io/docs/concepts#_the_timed_annotation
*/
@Bean
public TimedAspect timedAspect(HikariDataSource dataSource, MeterRegistry registry) {
return new TimedAspect(registry);
}
}
see more details https://github.com/micrometer-metrics/micrometer/issues/513
Upvotes: 0
Reputation: 6391
Register TimedAspect as a bean in the configuration class:
@Bean
public TimedAspect timedAspect(MeterRegistry meterRegistry) {
return new TimedAspect(meterRegistry);
}
Upvotes: 3