2
votes

So I have a Spring Boot application using spring-boot-actuator. By defining the spring.metrics.export.statsd.host property, a StatsdMetricWriter is instantiated automagically and, for counters and gauges, everything is working fine.

For timers however, things are a bit awkward. For Java8, Spring Boot is automagically creating a BufferGaugeService instance - which results in timer values being reported basically like a gauge: the last value every 5s (or whatever it is, one might configure it as well). This basically renders timer metrics useless, as all the wonderful things StatsD does with those is skewed.

Now one could fall back to using the pre-Java8 default DefaultGaugeService, but then again, for counters the BufferCounterService is just fine. looking at MetricRepositoryAutoConfiguration, setting this up manually seems to be non-trivial and brittle regarding future updates.

Any advice on how to proceed here? Or is there some blue print providing some inspiration?

Current situation aside: are there some plans to come up with a BufferTimerService or a TimerService in the first place?

2

2 Answers

1
votes

For what it's worth, I found a rather unobtrusive solution. Not sure if it's the optimal way, but it works flawlessly:

@Autowired
@Bean
public static BeanPostProcessor wrapGaugeService(ApplicationContext applicationContext) {
    return new BeanPostProcessor() {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) {
            return bean;
        }
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) {
            if (!GaugeService.class.isInstance(bean)) {
                return bean;
            }
            final GaugeService gaugeService = GaugeService.class.cast(bean);
            final MetricWriter metricWriter = applicationContext.getBean(MetricWriter.class);
            return (GaugeService) (metricName, value) -> {
                if (metricName.startsWith("timer.")) {
                    metricWriter.set(new Metric<>(metricName, value));
                } else {
                    gaugeService.submit(metricName, value);
                }
            };
        }
    };
}

For timer values, we're mimicing Spring Boots's pre-Java8 behavior. This wraps the GaugeService provided by Spring Boot: for "timer.*" values, it calls the MetricWriter (this should be the StatsdMetricWriter, else this whole setup makes no sense), otherwise it passes along the gauge value to the GaugeService.

0
votes

Thanks for providing your solution!
I have Dropwizard implementation and following override helped to catch request time metrics in timer instead of gauges.

@Bean
DropwizardMetricServices dropwizardMetricServices(MetricRegistry metricRegistry,
                                                  ObjectProvider<ReservoirFactory> resFactoryProvider) {
    return new DropwizardMetricServices(metricRegistry, resFactoryProvider.getIfAvailable()) {
        @Override
        public void submit(String name, double value) {
            if (name.startsWith("response.")) {
                super.submit("timer." + name, value);
            } else {
                super.submit(name, value);
            }
        }
    };
}

Regarding TimerService, this spring boot issue may be relevant.

As I see there are plans for major improvements in spring boot metrics for 2.0 described under: Metrics theme and Improvements to Actuator Metrics