10

I'm trying to export all of the metrics which are visible at the endpoint /metrics to a StatsdMetricWriter.

I've got the following configuration class so far:

package com.tonyghita.metricsdriven.service.config;

import com.codahale.metrics.MetricRegistry;
import com.ryantenney.metrics.spring.config.annotation.EnableMetrics;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.ExportMetricReader;
import org.springframework.boot.actuate.autoconfigure.ExportMetricWriter;
import org.springframework.boot.actuate.metrics.reader.MetricReader;
import org.springframework.boot.actuate.metrics.reader.MetricRegistryMetricReader;
import org.springframework.boot.actuate.metrics.statsd.StatsdMetricWriter;
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
@EnableMetrics(proxyTargetClass = true)
public class MetricsConfig {
    private static final Logger LOGGER = LoggerFactory.getLogger(MetricsConfig.class);

    @Value("${statsd.host:localhost}")
    private String host = "localhost";

    @Value("${statsd.port:8125}")
    private int port;

    @Autowired
    private MetricRegistry metricRegistry;

    @Bean
    @ExportMetricReader
    public MetricReader metricReader() {
        return new MetricRegistryMetricReader(metricRegistry);
    }

    @Bean
    @ExportMetricWriter
    public MetricWriter metricWriter() {
        LOGGER.info("Configuring StatsdMetricWriter to export to {}:{}", host, port);
        return new StatsdMetricWriter(host, port);
    }
}

Which writes all of the metrics which I've added to Statsd, but I'd like to also send the system/JVM metrics that are visible on the /metrics endpoint.

What am I missing?

Tony Ghita
  • 275
  • 1
  • 4
  • 13
  • Maybe [this PR](https://github.com/spring-projects/spring-boot/pull/3719) (pending merge) would help – Stephane Nicoll Aug 27 '15 at 08:56
  • Hope that gets merged @StéphaneNicoll! That change adds convenience around setting up the Statsd writer, but my question is more along the lines of how to set up an `@ExportMetricReader` bean that exports the metrics available at the `/metrics` endpoint. – Tony Ghita Aug 31 '15 at 03:48

4 Answers4

6

I had the same problem and found a solution here: https://github.com/tzolov/export-metrics-example

Just add a MetricsEndpointMetricReader to your config and everything available at th e/metrics endpoint will be published to the StatsdMetricWriter.

Here is a complete example config for spring boot 1.3.x and dropwizard metrics-jvm 3.1.x:

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.jvm.GarbageCollectorMetricSet;
import com.codahale.metrics.jvm.MemoryUsageGaugeSet;
import com.codahale.metrics.jvm.ThreadStatesGaugeSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.ExportMetricWriter;
import org.springframework.boot.actuate.endpoint.MetricsEndpoint;
import org.springframework.boot.actuate.endpoint.MetricsEndpointMetricReader;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.statsd.StatsdMetricWriter;
import org.springframework.boot.actuate.metrics.writer.Delta;
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MetricsConfiguration {

  @Bean
  public MetricRegistry metricRegistry() {
    final MetricRegistry metricRegistry = new MetricRegistry();

    metricRegistry.register("jvm.memory",new MemoryUsageGaugeSet());
    metricRegistry.register("jvm.thread-states",new ThreadStatesGaugeSet());
    metricRegistry.register("jvm.garbage-collector",new GarbageCollectorMetricSet());

    return metricRegistry;
  }

  /*
   * Reading all metrics that appear on the /metrics endpoint to expose them to metrics writer beans.
   */
  @Bean
  public MetricsEndpointMetricReader metricsEndpointMetricReader(final MetricsEndpoint metricsEndpoint) {
    return new MetricsEndpointMetricReader(metricsEndpoint);
  }

  @Bean
  @ConditionalOnProperty(prefix = "statsd", name = {"prefix", "host", "port"})
  @ExportMetricWriter
  public MetricWriter statsdMetricWriter(@Value("${statsd.prefix}") String statsdPrefix,
                                     @Value("${statsd.host}") String statsdHost,
                                     @Value("${statsd.port}") int statsdPort) {
    return new StatsdMetricWriter(statsdPrefix, statsdHost, statsdPort);
  }

}
mxsb
  • 216
  • 3
  • 4
5

From what I've seen in spring-boot code, only calls to CounterService and GaugeService implementations are forwarded to dropwizard's MetricRegistry.

Therefore, as you already observed, only counter.* and gauge.* metrics from the /metrics endpoint will end up in Statsd.

System and JVM metrics are exposed through custom SystemPublicMetrics class, which doesn't use counter or gauge service.

I'm not sure if there is a simpler solution (maybe someone from Spring team will comment), but one way to do it (not spring-boot specific) would be to use a scheduled task that periodically writes system stats to the MetricRegistry.

mzc
  • 3,265
  • 1
  • 20
  • 25
  • 1
    Thanks for your answer @mzc. I would've loved to use the existing `SystemPublicMetrics`, but as you pointed out, it is not so straightforward. Fortunately, there is a `'io.dropwizard.metrics:metrics-jvm'` library that handles exporting jvm metrics nicely. I'd still like to export those nice tomcat metrics though, so it may be valuable to explore that solution you proposed. – Tony Ghita Aug 31 '15 at 03:45
5

To register JVM metrics you can use the JVM related MetricSets supplied by codehale.metrics.jvm library. You can just add the whole set without supplying whether they are gauges or counters.

Here is my example code where I am registering jvm related metrics:

@Configuration
@EnableMetrics(proxyTargetClass = true)
public class MetricsConfig {

@Autowired
private StatsdProperties statsdProperties;

@Autowired
private MetricsEndpoint metricsEndpoint;

@Autowired
private DataSourcePublicMetrics dataSourcePublicMetrics;

@Bean
@ExportMetricReader
public MetricReader metricReader() {
    return new MetricRegistryMetricReader(metricRegistry());
}

public MetricRegistry metricRegistry() {
    final MetricRegistry metricRegistry = new MetricRegistry();

    //jvm metrics
    metricRegistry.register("jvm.gc",new GarbageCollectorMetricSet());
    metricRegistry.register("jvm.mem",new MemoryUsageGaugeSet());
    metricRegistry.register("jvm.thread-states",new ThreadStatesGaugeSet());

    return metricRegistry;
}

@Bean
@ConditionalOnProperty(prefix = "metrics.writer.statsd", name = {"host", "port"})
@ExportMetricWriter
public MetricWriter statsdMetricWriter() {
    return new StatsdMetricWriter(
            statsdProperties.getPrefix(),
            statsdProperties.getHost(),
            statsdProperties.getPort()
    );
}

}

Note: I am using spring boot version 1.3.0.M4

Yonatan Wilkof
  • 1,017
  • 12
  • 15
  • Thanks for your answer @YonatanWilcof. Looks like we're on the same version of Spring Boot. I was able to get the JVM metrics like you described by adding `'io.dropwizard.metrics:metrics-jvm:3.1.2'` to my dependencies, and registering `GarbageCollectorMetricSet`, `MemoryUsageGaugeSet`, and `ThreadStatesGaugeSet` to the `metricRegistry`. I was hoping to be able to use the existing `SystemPublicMetrics` mechanism, but this also gets the job done. Looks like there is also a `metrics-jdbi` library! Time to dig in to that :) – Tony Ghita Aug 31 '15 at 03:41
  • I think the last piece for me would be to figure out how to add tomcat/endpoint stats to the registry. – Tony Ghita Aug 31 '15 at 14:41
  • @Yonatan Wilkof: which jar does the 'StatsdProperties' class gets imported from, I don't seem to have it within 'io.dropwizard.metrics', 'com.ryantenney.metrics' and 'spring-boot-starter-actuator'. Do I need to add something else to my pom.xml? Thanks. – Simeon Leyzerzon Aug 31 '15 at 15:55
  • it does not belong to a library, just a class that is mapped with properties. It's inspired by the PR https://github.com/spring-projects/spring-boot/pull/3719 posted as a comment to the original question. – Yonatan Wilkof Aug 31 '15 at 17:03
4

Enjoy! (see the public metrics logged in console as dropwizard metrics)

@Configuration
@EnableMetrics
@EnableScheduling
public class MetricsReporter extends MetricsConfigurerAdapter {

    @Autowired private SystemPublicMetrics systemPublicMetrics;
    private MetricRegistry metricRegistry;

    @Scheduled(fixedDelay = 5000)
    void exportPublicMetrics() {
        for (Metric<?> metric : systemPublicMetrics.metrics()) {
            Counter counter = metricRegistry.counter(metric.getName());
            counter.dec(counter.getCount());
            counter.inc(Double.valueOf(metric.getValue().toString()).longValue());
        }
    }

    @Override
    public void configureReporters(MetricRegistry metricRegistry) {
        this.metricRegistry = metricRegistry;
        ConsoleReporter.forRegistry(metricRegistry).build().start(10, TimeUnit.SECONDS);
    }

}
CelinHC
  • 1,857
  • 2
  • 27
  • 36