Metrics reporting from Guava Caches

Codahale Metrics is a wonderful library to collect and report various types of metrics from your Java server-side applications. Google Guava is the swiss army knife of libraries that every Java developer should have in their toolbox.

If you use both these libraries, then at some point of time, you may find the need to capture statistics from Guava Caches and report them via Metrics. When you do, perhaps this class would be useful.

package net.antrix.utils;

import static com.codahale.metrics.MetricRegistry.name;

import java.util.HashMap;
import java.util.Map;

import com.codahale.metrics.Gauge;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricSet;
import com.google.common.cache.Cache;

public class GuavaCacheMetrics extends HashMap< String, Metric > implements MetricSet {

    /**
     * Wraps the provided Guava cache's statistics into Gauges suitable for reporting via Codahale Metrics
     * <p/>
     * The returned MetricSet is suitable for registration with a MetricRegistry like so:
     * <p/>
     * <code>registry.registerAll( GuavaCacheMetrics.metricsFor( "MyCache", cache ) );</code>
     * 
     * @param cacheName This will be prefixed to all the reported metrics
     * @param cache The cache from which to report the statistics
     * @return MetricSet suitable for registration with a MetricRegistry
     */
    public static MetricSet metricsFor( String cacheName, final Cache cache ) {

        GuavaCacheMetrics metrics = new GuavaCacheMetrics();

        metrics.put( name( cacheName, "hitRate" ), new Gauge< Double >() {
            @Override
            public Double getValue() {
                return cache.stats().hitRate();
            }
        } );

        metrics.put( name( cacheName, "hitCount" ), new Gauge< Long >() {
            @Override
            public Long getValue() {
                return cache.stats().hitCount();
            }
        } );

        metrics.put( name( cacheName, "missCount" ), new Gauge< Long >() {
            @Override
            public Long getValue() {
                return cache.stats().missCount();
            }
        } );

        metrics.put( name( cacheName, "loadExceptionCount" ), new Gauge< Long >() {
            @Override
            public Long getValue() {
                return cache.stats().loadExceptionCount();
            }
        } );

        metrics.put( name( cacheName, "evictionCount" ), new Gauge< Long >() {
            @Override
            public Long getValue() {
                return cache.stats().evictionCount();
            }
        } );

        return metrics;
    }

    private GuavaCacheMetrics() {
    }

    @Override
    public Map< String, Metric > getMetrics() {
        return this;
    }

}

And here’s a Spock specification that verifies that it works.

package net.antrix.utils

import com.codahale.metrics.MetricRegistry
import com.google.common.cache.Cache
import com.google.common.cache.CacheBuilder
import spock.lang.Specification

class GuavaCacheMetricsTest extends Specification {

    def "ensure guava cache metrics are reported to the registry"() {

        given: "a guava cache registered with a Metrics registry"

            def cache = CacheBuilder.newBuilder().recordStats().build()
            def registry = new MetricRegistry()
            registry.registerAll(GuavaCacheMetrics.metricsFor("MyCache", cache))

        when: "various read/write operations are performed on the cache"

            cache.put("k1", "v1")
            cache.put("k2", "v2")

            cache.getIfPresent("k1")
            cache.getIfPresent("k2")
            cache.getIfPresent("k3")

            cache.get("k4", { "v4" })

            try {
                cache.get "k5", {
                    throw new Exception()
                }
            } catch (Exception expected) {
            }

        then: "the metrics registry records them correctly"

            def gauges = registry.gauges

            2 == gauges["MyCache.hitCount"].value
            3 == gauges["MyCache.missCount"].value
            0.4 == gauges["MyCache.hitRate"].value
            1 == gauges["MyCache.loadExceptionCount"].value
            0 == gauges["MyCache.evictionCount"].value
    }
}