Skip to content

Instantly share code, notes, and snippets.

@nicholashagen
Created October 29, 2013 20:37
Show Gist options
  • Save nicholashagen/7222157 to your computer and use it in GitHub Desktop.
Save nicholashagen/7222157 to your computer and use it in GitHub Desktop.
Metrics library rundown and overview

Metrics Library

About

  • Originally developed at Yammer by Coda Hale
  • Released as an open source metrics library implementation under Coda Hale
  • Considered one of the more prominent instrumentation libraries
  • Composed of core metrics library and set of growing plugins
  • Core library supports variety of metric gathering tools and health checks
  • Plugins support exposing core metrics (jmx, graphite, etc)
  • Plugins support hooking into other third party libraries (spring, jetty, ehcache, jvm, etc)

Core Constructs

Metric Registry

  • MetricRegistry provides a collection of all data mapped by a unique name (often dot notation)
  • Create names via MetricRegistry.name
  • MetricRegistry should typically per-application (especially in the context of app container)

Create a registry:

MetricRegistry registry = new MetricRegistry();

Lookup shared registries:

MetricRegistry registry = SharedMetricRegistries.getOrCreate("com.company.app");

Create a unique name:

String name1 = MetricRegistry.name(MyClass.class, "requests", "size");
String name2 = MetricRegistry.name(MyClass.class, "requests", "duration");

Health Check Registry

  • HealthCheckRegistry provides a collection of health checks mapped by a unique name
  • Health checks may be run manually or automatically by an exposing plugin
HealthCheckRegistry registry = new HealthCheckRegistry();

Metric Types

Gauge

  • Used to retrieve a specific value when requested
  • Provides instantanous measurements of a discrete value
  • Gauges are read when data is read or exported from the registry
  • Example: a memory pool gauge may retrieve the current used memory value
final int value = 0;
registry.register("com.company.app.my-gauge", new Gauge<Integer>() {
	public Integer getValue() { 
		return Integer.valueOf(value); 
	}
});

The following gauges are supported as useful implementations:

  • JMX Gauges: new JmxAttributeGauge("com.company.app:type=MyData,name=data", "Value")
  • Ratio Gauge; new RatioGauge() { public Ratio getValue() { return Ratio.of(hits, duration); }}
  • Cached Gauge: new CachedGauge<Integer>(10, TimeUnit.MINUTES) { public Integer getValue() { /* long task */ }}
  • Derivative Gauge: new DerivateGauge<Data, Integer>(myDataGauge) { protected Integer transform(MyData data) { ... }}

Counter

  • Provides a sole incrementing/decrementing value
  • Example: Monitor queues for the remaining number of jobs
  • Example: Monitor servlets to count the number of total incoming requests.
// setup
Counter activeRequests = registry.counter(MetricRegistry.name(MyServlet.class, "active-requests"));
// process each request
activeRequests.inc();
servlet.process();
activeRequests.dec();

Histogram

  • Measure the statistical distribution of values in a stream of data
  • Includes mean and median averages, various percentiles, and the standard deviation
  • Reservoir sampling to manage the data that represent only a snapshot of the overall data.
  • Configurable to use a variety of reservoirs
    • Uniform randomly selects values from the entire data set per Vitter R algorithm
    • Exponentially decaying representative of last few minutes of data (lends towards recent data)
    • Sliding windows that represent last X number of measurements
    • Sliding time windows that represent last X number of seconds (not bounded, so consider exponentially decaying)
// setup
Counter totalBytes = registry.histogram(MetricRegistry.name(MyServlet.class, "bytes"));
// process each request
servlet.process();
totalBytes.inc(response.getContentLength());

Meter

  • Measures the rate of events over time
  • Measure the average mean throughput
  • Measure the 1, 5, and 15-minute expontentially-weighted moving averages.
  • Example: measure the number of requests per second
// setup
Meter requests = registry.counter(MetricRegistry.name(MyServlet.class, "requests"));
// process each request
servlet.process();
requests.mark();

Timer

  • Provides a duration of a particular event via Histogram
  • Provides the frequency of the event via Meter
  • Example: Measure requests/sec along with the actual duration of the requests as a statistical data set
// setup
Timer timer = registry.counter(MetricRegistry.name(MyServlet.class, "requests"));
// process each request
Timer.Context context = timer.time();
servlet.process();
context.stop();

Metric Sets

  • Provides a logical grouping of metrics
  • Useful for library authors and grouping common metrics

Reporters

Reporters expose data from the Metric Registry

JMX

  • Expose data as JMX managed beans
  • Convert rates and durations to specific time units
JmxReporter.forRegistry(registry).build().start();

Console

  • Periodically report metrics to output stream
  • Convert rates and durations to specific time units
ConsoleReporter.forRegistry(registry)
	.convertRatesTo(TimeUnit.SECONDS)
	.convertDurationsTo(TimeUnit.MILLISECONDS)
	.outputTo(System.out)
	.build()
	.start(5, TimeUnit.MINUTES);

CSV Files

  • Periodically report metrics to a set of CSV files
  • One CSV file per metric is written with new row per reporting period
  • Convert rates and durations to specific time units
CsvReporter.forRegistry(registry)
	.formatFor(Locale.US)
	.build(new File("/my/metrics/directory"))
	.start(5, TimeUnit.MINUTES);

SLF4J Logger

  • Periodically report metrics to a SLF4J logger
  • Convert rates and durations to specific time units
Slf4jReporter.forRegistry(registry)
	.outputTo(LoggerFactory.getLogger("com.company.app.metrics"))
	.build()
	.start(5, TimeUnit.MINUTES);

Graphite

  • Available via the Graphite plugin
  • Constantly streams metrics data to a graphite listener
Graphite graphite = new Graphite(new InetSocketAddress("graphite.domain.com"), port);
GraphiteReporter.forRegistry(registry)
	.prefixedWith("com.company.app")
	.build(graphite)
	.start(1, TimeUnit.MINUTES);

Useful Plugins

The following plugins are available that plug into Metrics as a third-party library.

Ehcache

The EHCache plugin decorates existing cache with various metrics including hits, misses, duratinos, etc.

CacheManager cache = CacheManager.getCache("name");
this.cache = InstrumentedEhcache.instrument(registry, cache);
this.cache.get(...);

Jersey

The Jersey plugin automatically plugs into the Jersey runtime with a custom adapter to instrument any resource containing the @Timed, @Metered, or @ExceptionMetered annotations.

@Path("/app")
public class AppResource {
	@GET
	@Timed
	public String list() {
		return "data";
	}
}

Jetty

The Jetty plugin automatically provides various instrumented decorators of default Jetty implementations including Connectors, ThreadPools, and Handlers. The instrumented specific classes should be used within the corresponding Jetty configuration.

Within Spring Boot, you can create a custom JettyEmbeddedServletContainerFactory bean that then decorates the default Jetty implementation exchanging the handlers with the instrumented versions.

Tomcat / Glassfish

No standard implementation available although various libraries provide some support. May also create a context listener to override filters and/or servlets.

Logging

The logging instrumenters decorate the Log4J and Logback implementations with metrics to record the rate of logged events.

Log4J

LogManager.getRootLogger().addAppender(new InstrumentedAppender(registry));

Logback

LoggerContext factory = (LoggerContext) LoggerFactory.getILoggerFactory();
Logger root = factory.getLogger(Logger.ROOT_LOGGER_NAME);

InstrumentedAppender metrics = new InstrumentedAppender(registry);
metrics.setContext(root.getLoggerContext());
metrics.start();
root.addAppender(metrics);

JVM

The JVM plugin exposes many of the built-in JVM-based MBeans for memory pools, threading, GC, etc

registry.registerAll(new GarbageCollectorMetricSet());
registry.registerAll(new MemoryUsageMetricSet());
registry.registerAll(new ThreadStatesMetricSet());

Servlets and Web Applications

The metrics-servlet library provides a variety of useful preconfigured servlets for exposing metrics data as well as a filter to instrument servlets including active requests, status code meters, and request duration meters.

The InstrumentedFilter provides the filter to use within web.xml.

The AdminServlet provides 4 endpoints:

  • /healthcheck invokes all the healthchecks
  • /metrics dumps the metrics as a JSON response
  • /ping provides a simple pong response for servlet liveliness
  • /threads provides a thread dump response

Apache HTTP Client

The HTTP client library instruments and decorates the HTTP client from Apache. The metrics are automatically named based on the actual request URI based on the configured strategy which may include method only, host and method, or queryless URI and method.

HttpClient client = new InstrumentedHttpClient(registry, HttpClientMetricNameStrategies.HOST_AND_METHOD);

There is also a InstrumentedClientConnManager to instrument the number of open connections in the pool.

Spring

The Spring plugin automatically instruments all annotated beans with duration timers as well as provides auto-injection of metrics.

To expose using the new Spring Configuration in Java, annotate the configuration class with the @EnableMetrics annotation, extend the base MetricsConfigurerAdapter, and override the getMetricRegistery and optional configureReporters methods.

@Configuration
@EnableMetrics
public class MyConfiguration extends MetricsConfigurerAdapter {
	public MetricRegistry getMetricRegistry() {
		return SharedMetricRegistries.getOrCreate("com.company.app");
	}

	public void configureReporters(MetricRegistry registry) {
		JmxReporter.forRegistry(registry).build().start();
	} 
}

To time methods (only public methods may be proxied via AOP and excludes internal invocations), annotate the methods with @Timed, @Metered, @Counted, or @ExceptionMetered.

@Singleton
public class MyBean {
	@Timed
	public void doSomething() { ... }
}

Metrics may also be injected into spring beans.

@Singleton
public class MyBean {
	@InjectMetric
	private Timer myTimer;

	public void doStuff() {
		Timer.Context timer = myTimer.start();
		// do stuff
		timer.stop();
	}
}
@bdemers
Copy link

bdemers commented Oct 30, 2013

Sweet, creating a Readme/markup in a gist is nice!

I have a sql / aop example: https://github.disney.com/gist/350

@bdemers
Copy link

bdemers commented Oct 30, 2013

Example healthcheck: https://github.disney.com/espn-build-eng/healthcheck/blob/master/healthcheck-tea/src/main/java/com/espn/infra/healthcheck/tea/DatabaseHealthCheck.java

I can also have this example app running if we want: https://github.disney.com/espn-build-eng/healthcheck/tree/master/healthcheck-example-webapp

Which exercises a this code in a simple mvn jetty:run

metrics: https://github.disney.com/gist/352
Health checks:

* database: OK
  Number of Databases registered and healthy: 1
* tea-compile: OK

I also have this accessible via the TeaAdmin page, (but this may not be interesting outside this group)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment