Metrics Library
- 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)
- 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");
- 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();
- 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) { ... }}
- 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();
- 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());
- 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();
- 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();
- Provides a logical grouping of metrics
- Useful for library authors and grouping common metrics
Reporters expose data from the Metric Registry
- Expose data as JMX managed beans
- Convert rates and durations to specific time units
JmxReporter.forRegistry(registry).build().start();
- 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);
- 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);
- 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);
- 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);
The following plugins are available that plug into Metrics as a third-party library.
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(...);
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";
}
}
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.
No standard implementation available although various libraries provide some support. May also create a context listener to override filters and/or servlets.
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);
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());
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
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.
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();
}
}
Sweet, creating a Readme/markup in a gist is nice!
I have a sql / aop example: https://github.disney.com/gist/350