Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Introduction

Metrics is a Java library which provides measuring instruments for Java applications.

It has several modules, and in this article, we will elaborate metrics-core module, metrics-healthchecks module, metrics-servlets module, and metrics-servlet module, and sketch out the rest, for your reference.

2. Module metrics-core

2.1. Maven Dependencies

To use the metrics-core module, there’s only one dependency required which needs to be added to the pom.xml file:

<dependency>
    <groupId>io.dropwizard.metrics</groupId>
    <artifactId>metrics-core</artifactId>
    <version>4.2.17</version>
</dependency>

And you can find its latest version here.

2.2. MetricRegistry

Simply put, we’ll use the MetricRegistry class to register one or several metrics.

We can use one metrics registry for all of our metrics, but if we want to use different reporting methods for different metrics, we can also divide our metrics into groups and use different metrics registries for each group.

Let’s create a MetricRegistry now:

MetricRegistry metricRegistry = new MetricRegistry();

And then we can register some metrics with this MetricRegistry:

Meter meter1 = new Meter();
metricRegistry.register("meter1", meter1);

Meter meter2 = metricRegistry.meter("meter2");

There are two basic ways of creating a new metric: instantiating one yourself or obtaining one from the metric registry. As you can see, we used both of them in the example above, we are instantiating the Meter object “meter1” and we are getting another Meter object “meter2” which is created by the metricRegistry.

In a metric registry, every metric has a unique name, as we used “meter1” and “meter2” as metric names above. MetricRegistry also provides a set of static helper methods to help us create proper metric names:

String name1 = MetricRegistry.name(Filter.class, "request", "count");
String name2 = MetricRegistry.name("CustomFilter", "response", "count");

If we need to manage a set of metric registries, we can use SharedMetricRegistries class, which is a singleton and thread-safe. We can add a metric register into it, retrieve this metric register from it, and remove it:

SharedMetricRegistries.add("default", metricRegistry);
MetricRegistry retrievedMetricRegistry = SharedMetricRegistries.getOrCreate("default");
SharedMetricRegistries.remove("default");

3. Metrics Concepts

The metrics-core module provides several commonly used metric types: Meter, Gauge, Counter, Histogram and Timer, and Reporter to output metrics’ values.

3.1. Meter

A Meter measures event occurrences count and rate:

Meter meter = new Meter();
long initCount = meter.getCount();
assertThat(initCount, equalTo(0L));

meter.mark();
assertThat(meter.getCount(), equalTo(1L));

meter.mark(20);
assertThat(meter.getCount(), equalTo(21L));

double meanRate = meter.getMeanRate();
double oneMinRate = meter.getOneMinuteRate();
double fiveMinRate = meter.getFiveMinuteRate();
double fifteenMinRate = meter.getFifteenMinuteRate(); 

The getCount() method returns event occurrences count, and mark() method adds 1 or n to the event occurrences count. The Meter object provides four rates which represent average rates for the whole Meter lifetime, for the recent one minute, for the recent five minutes and for the recent quarter, respectively.

3.2. Gauge

Gauge is an interface which is simply used to return a particular value. The metrics-core module provides several implementations of it: RatioGauge, CachedGauge, DerivativeGauge and JmxAttributeGauge.

RatioGauge is an abstract class and it measures the ratio of one value to another.

Let’s see how to use it. First, we implement a class AttendanceRatioGauge:

public class AttendanceRatioGauge extends RatioGauge {
    private int attendanceCount;
    private int courseCount;

    @Override
    protected Ratio getRatio() {
        return Ratio.of(attendanceCount, courseCount);
    }
    
    // standard constructors
}

And then we test it:

RatioGauge ratioGauge = new AttendanceRatioGauge(15, 20);

assertThat(ratioGauge.getValue(), equalTo(0.75));

CachedGauge is another abstract class which can cache value, therefore, it is quite useful when the values are expensive to calculate. To use it, we need to implement a class ActiveUsersGauge:

public class ActiveUsersGauge extends CachedGauge<List<Long>> {
    
    @Override
    protected List<Long> loadValue() {
        return getActiveUserCount();
    }
 
    private List<Long> getActiveUserCount() {
        List<Long> result = new ArrayList<Long>();
        result.add(12L);
        return result;
    }

    // standard constructors
}

Then we test it to see if it works as expected:

Gauge<List<Long>> activeUsersGauge = new ActiveUsersGauge(15, TimeUnit.MINUTES);
List<Long> expected = new ArrayList<>();
expected.add(12L);

assertThat(activeUsersGauge.getValue(), equalTo(expected));

We set the cache’s expiration time to 15 minutes when instantiating the ActiveUsersGauge.

DerivativeGauge is also an abstract class and it allows you to derive a value from other Gauge as its value.

Let’s look at an example:

public class ActiveUserCountGauge extends DerivativeGauge<List<Long>, Integer> {
    
    @Override
    protected Integer transform(List<Long> value) {
        return value.size();
    }

    // standard constructors
}

This Gauge derives its value from an ActiveUsersGauge, so we expect it to be the value from the base list’s size:

Gauge<List<Long>> activeUsersGauge = new ActiveUsersGauge(15, TimeUnit.MINUTES);
Gauge<Integer> activeUserCountGauge = new ActiveUserCountGauge(activeUsersGauge);

assertThat(activeUserCountGauge.getValue(), equalTo(1));

JmxAttributeGauge is used when we need to access other libraries’ metrics exposed via JMX.

3.3. Counter

The Counter is used for recording incrementations and decrementations:

Counter counter = new Counter();
long initCount = counter.getCount();
assertThat(initCount, equalTo(0L));

counter.inc();
assertThat(counter.getCount(), equalTo(1L));

counter.inc(11);
assertThat(counter.getCount(), equalTo(12L));

counter.dec();
assertThat(counter.getCount(), equalTo(11L));

counter.dec(6);
assertThat(counter.getCount(), equalTo(5L));

3.4. Histogram

Histogram is used for keeping track of a stream of Long values and it analyzes their statistical characteristics such as max, min, mean, median, standard deviation, 75th percentile and so on:

Histogram histogram = new Histogram(new UniformReservoir());
histogram.update(5);
long count1 = histogram.getCount();
assertThat(count1, equalTo(1L));

Snapshot snapshot1 = histogram.getSnapshot();
assertThat(snapshot1.getValues().length, equalTo(1));
assertThat(snapshot1.getValues()[0], equalTo(5L));

histogram.update(20);
long count2 = histogram.getCount();
assertThat(count2, equalTo(2L));

Snapshot snapshot2 = histogram.getSnapshot();
assertThat(snapshot2.getValues().length, equalTo(2));
assertThat(snapshot2.getValues()[1], equalTo(20L));
assertThat(snapshot2.getMax(), equalTo(20L));
assertThat(snapshot2.getMean(), equalTo(12.5));
assertEquals(10.6, snapshot2.getStdDev(), 0.1);
assertThat(snapshot2.get75thPercentile(), equalTo(20.0));
assertThat(snapshot2.get999thPercentile(), equalTo(20.0));

Histogram samples the data by using reservoir sampling, and when we instantiate a Histogram object, we need to set its reservoir explicitly.

Reservoir is an interface and metrics-core provides four implementations of them: ExponentiallyDecayingReservoir, UniformReservoir, SlidingTimeWindowReservoir, SlidingWindowReservoir.

In the section above, we mentioned that a metric can also be created by MetricRegistry, besides using a constructor. When we use metricRegistry.histogram(), it returns a Histogram instance with ExponentiallyDecayingReservoir implementation.

3.5. Timer

Timer is used for keeping track of multiple timing durations which are represented by Context objects, and it also provides their statistical data:

Timer timer = new Timer();
Timer.Context context1 = timer.time();
TimeUnit.SECONDS.sleep(5);
long elapsed1 = context1.stop();

assertEquals(5000000000L, elapsed1, 1000000);
assertThat(timer.getCount(), equalTo(1L));
assertEquals(0.2, timer.getMeanRate(), 0.1);

Timer.Context context2 = timer.time();
TimeUnit.SECONDS.sleep(2);
context2.close();

assertThat(timer.getCount(), equalTo(2L));
assertEquals(0.3, timer.getMeanRate(), 0.1);

3.6. Reporter

When we need to output our measurements, we can use Reporter. This is an interface, and the metrics-core module provides several implementations of it, such as ConsoleReporter, CsvReporter, Slf4jReporter, JmxReporter and so on.

Here we use ConsoleReporter as an example:

MetricRegistry metricRegistry = new MetricRegistry();

Meter meter = metricRegistry.meter("meter");
meter.mark();
meter.mark(200);
Histogram histogram = metricRegistry.histogram("histogram");
histogram.update(12);
histogram.update(17);
Counter counter = metricRegistry.counter("counter");
counter.inc();
counter.dec();

ConsoleReporter reporter = ConsoleReporter.forRegistry(metricRegistry).build();
reporter.start(5, TimeUnit.MICROSECONDS);
reporter.report();

Here is the sample output of the ConsoleReporter:

-- Histograms ------------------------------------------------------------------
histogram
count = 2
min = 12
max = 17
mean = 14.50
stddev = 2.50
median = 17.00
75% <= 17.00
95% <= 17.00
98% <= 17.00
99% <= 17.00
99.9% <= 17.00

-- Meters ----------------------------------------------------------------------
meter
count = 201
mean rate = 1756.87 events/second
1-minute rate = 0.00 events/second
5-minute rate = 0.00 events/second
15-minute rate = 0.00 events/second

4. Module metrics-healthchecks

Metrics has an extension metrics-healthchecks module for dealing with health checks.

4.1. Maven Dependencies

To use the metrics-healthchecks module, we need to add this dependency to the pom.xml file:

<dependency>
    <groupId>io.dropwizard.metrics</groupId>
    <artifactId>metrics-healthchecks</artifactId>
    <version>4.2.17</version>
</dependency>

And you can find its latest version here.

4.2. Usage

First, we need several classes which are responsible for specific health check operations, and these classes must implement HealthCheck.

For example, we use DatabaseHealthCheck and UserCenterHealthCheck:

public class DatabaseHealthCheck extends HealthCheck {
 
    @Override
    protected Result check() throws Exception {
        return Result.healthy();
    }
}
public class UserCenterHealthCheck extends HealthCheck {
 
    @Override
    protected Result check() throws Exception {
        return Result.healthy();
    }
}

Then, we need a HealthCheckRegistry (which is just like MetricRegistry), and register the DatabaseHealthCheck and UserCenterHealthCheck with it:

HealthCheckRegistry healthCheckRegistry = new HealthCheckRegistry();
healthCheckRegistry.register("db", new DatabaseHealthCheck());
healthCheckRegistry.register("uc", new UserCenterHealthCheck());

assertThat(healthCheckRegistry.getNames().size(), equalTo(2));

We can also unregister the HealthCheck:

healthCheckRegistry.unregister("uc");
 
assertThat(healthCheckRegistry.getNames().size(), equalTo(1));

We can run all the HealthCheck instances:

Map<String, HealthCheck.Result> results = healthCheckRegistry.runHealthChecks();
for (Map.Entry<String, HealthCheck.Result> entry : results.entrySet()) {
    assertThat(entry.getValue().isHealthy(), equalTo(true));
}

Finally, we can run a specific HealthCheck instance:

healthCheckRegistry.runHealthCheck("db");

5. Module metrics-servlets

Metrics provides us a handful of useful servlets which allow us to access metrics related data through HTTP requests.

5.1. Maven Dependencies

To use the metrics-servlets module, we need to add this dependency to the pom.xml file:

<dependency>
    <groupId>io.dropwizard.metrics</groupId>
    <artifactId>metrics-servlets</artifactId>
    <version>4.2.17</version>
</dependency>

And you can find its latest version here.

5.2. HealthCheckServlet Usage

HealthCheckServlet provides health check results. First, we need to create a ServletContextListener which exposes our HealthCheckRegistry:

public class MyHealthCheckServletContextListener
  extends HealthCheckServlet.ContextListener {
 
    public static HealthCheckRegistry HEALTH_CHECK_REGISTRY
      = new HealthCheckRegistry();

    static {
        HEALTH_CHECK_REGISTRY.register("db", new DatabaseHealthCheck());
    }

    @Override
    protected HealthCheckRegistry getHealthCheckRegistry() {
        return HEALTH_CHECK_REGISTRY;
    }
}

Then, we add both this listener and HealthCheckServlet into the web.xml file:

<listener>
    <listener-class>com.baeldung.metrics.servlets.MyHealthCheckServletContextListener</listener-class>
</listener>
<servlet>
    <servlet-name>healthCheck</servlet-name>
    <servlet-class>com.codahale.metrics.servlets.HealthCheckServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>healthCheck</servlet-name>
    <url-pattern>/healthcheck</url-pattern>
</servlet-mapping>

Now we can start the web application, and send a GET request to “http://localhost:8080/healthcheck” to get health check results. Its response should be like this:

{
  "db": {
    "healthy": true
  }
}

5.3. ThreadDumpServlet Usage

ThreadDumpServlet provides information about all live threads in the JVM, their states, their stack traces, and the state of any locks they may be waiting for.
If we want to use it, we simply need to add these into the web.xml file:

<servlet>
    <servlet-name>threadDump</servlet-name>
    <servlet-class>com.codahale.metrics.servlets.ThreadDumpServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>threadDump</servlet-name>
    <url-pattern>/threaddump</url-pattern>
</servlet-mapping>

Thread dump data will be available at “http://localhost:8080/threaddump”.

5.4. PingServlet Usage

PingServlet can be used to test if the application is running. We add these into the web.xml file:

<servlet>
    <servlet-name>ping</servlet-name>
    <servlet-class>com.codahale.metrics.servlets.PingServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>ping</servlet-name>
    <url-pattern>/ping</url-pattern>
</servlet-mapping>

And then send a GET request to “http://localhost:8080/ping”. The response’s status code is 200 and its content is “pong”.

5.5. MetricsServlet Usage

MetricsServlet provides metrics data. First, we need to create a ServletContextListener which exposes our MetricRegistry:

public class MyMetricsServletContextListener
  extends MetricsServlet.ContextListener {
    private static MetricRegistry METRIC_REGISTRY
     = new MetricRegistry();

    static {
        Counter counter = METRIC_REGISTRY.counter("m01-counter");
        counter.inc();

        Histogram histogram = METRIC_REGISTRY.histogram("m02-histogram");
        histogram.update(5);
        histogram.update(20);
        histogram.update(100);
    }

    @Override
    protected MetricRegistry getMetricRegistry() {
        return METRIC_REGISTRY;
    }
}

Both this listener and MetricsServlet need to be added into web.xml:

<listener>
    <listener-class>com.codahale.metrics.servlets.MyMetricsServletContextListener</listener-class>
</listener>
<servlet>
    <servlet-name>metrics</servlet-name>
    <servlet-class>com.codahale.metrics.servlets.MetricsServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>metrics</servlet-name>
    <url-pattern>/metrics</url-pattern>
</servlet-mapping>

This will be exposed in our web application at “http://localhost:8080/metrics”. Its response should contain various metrics data:

{
  "version": "3.0.0",
  "gauges": {},
  "counters": {
    "m01-counter": {
      "count": 1
    }
  },
  "histograms": {
    "m02-histogram": {
      "count": 3,
      "max": 100,
      "mean": 41.66666666666666,
      "min": 5,
      "p50": 20,
      "p75": 100,
      "p95": 100,
      "p98": 100,
      "p99": 100,
      "p999": 100,
      "stddev": 41.69998667732268
    }
  },
  "meters": {},
  "timers": {}
}

5.6. AdminServlet Usage

AdminServlet aggregates HealthCheckServlet, ThreadDumpServlet, MetricsServlet, and PingServlet.

Let’s add these into the web.xml:

<servlet>
    <servlet-name>admin</servlet-name>
    <servlet-class>com.codahale.metrics.servlets.AdminServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>admin</servlet-name>
    <url-pattern>/admin/*</url-pattern>
</servlet-mapping>

It can now be accessed at “http://localhost:8080/admin”. We will get a page containing four links, one for each of those four servlets.

Note that, if we want to do health check and access metrics data, those two listeners are still needed.

6. Module metrics-servlet

The metrics-servlet module provides a Filter which has several metrics: meters for status codes, a counter for the number of active requests, and a timer for request duration.

6.1. Maven Dependencies

To use this module, let’s first add the dependency into the pom.xml:

<dependency>
    <groupId>io.dropwizard.metrics</groupId>
    <artifactId>metrics-servlet</artifactId>
    <version>4.2.17</version>
</dependency>

And you can find its latest version here.

6.2. Usage

To use it, we need to create a ServletContextListener which exposes our MetricRegistry to the InstrumentedFilter:

public class MyInstrumentedFilterContextListener
  extends InstrumentedFilterContextListener {
 
    public static MetricRegistry REGISTRY = new MetricRegistry();

    @Override
    protected MetricRegistry getMetricRegistry() {
        return REGISTRY;
    }
}

Then, we add these into the web.xml:

<listener>
     <listener-class>
         com.baeldung.metrics.servlet.MyInstrumentedFilterContextListener
     </listener-class>
</listener>

<filter>
    <filter-name>instrumentFilter</filter-name>
    <filter-class>
        com.codahale.metrics.servlet.InstrumentedFilter
    </filter-class>
</filter>
<filter-mapping>
    <filter-name>instrumentFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Now the InstrumentedFilter can work. If we want to access its metrics data, we can do it through its MetricRegistry REGISTRY.

7. Other Modules

Except for the modules we introduced above, Metrics has some other modules for different purposes:

  • metrics-jvm: provides several useful metrics for instrumenting JVM internals
  • metrics-ehcache: provides InstrumentedEhcache, a decorator for Ehcache caches
  • metrics-httpclient: provides classes for instrumenting Apache HttpClient (4.x version)
  • metrics-log4j: provides InstrumentedAppender, a Log4j Appender implementation for log4j 1.x which records the rate of logged events by their logging level
  • metrics-log4j2: is similar to metrics-log4j, just for log4j 2.x
  • metrics-logback: provides InstrumentedAppender, a Logback Appender implementation which records the rate of logged events by their logging level
  • metrics-json: provides HealthCheckModule and MetricsModule for Jackson

What’s more, other than these main project modules, some other third party libraries provide integration with other libraries and frameworks.

8. Conclusion

Instrumenting applications is a common requirement, so in this article, we introduced Metrics, hoping that it can help you to solve your problem.

As always, the complete source code for the example is available over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.