CDI extension for Dropwizard Metrics
Java

README.md

CDI Extension for Metrics

Build Status Coverage Status Dependency Status Maven Central

CDI portable extension for Dropwizard Metrics compliant with JSR 346: Contexts and Dependency Injection for JavaTM EE 1.2.

About

Metrics CDI provides support for the Metrics annotations in CDI enabled environments. It implements the contract specified by these annotations with the following level of functionality:

Metrics CDI is compatible with Metrics version 3.1.0+.

Getting Started

Using Maven

Add the metrics-cdi library as a dependency:

<dependency>
    <groupId>io.astefanutti.metrics.cdi</groupId>
    <artifactId>metrics-cdi</artifactId>
    <version>1.3.6</version>
</dependency>

Required Dependencies

Besides depending on Metrics (metrics-core and metrics-annotation modules), Metrics CDI requires a CDI enabled environment running in Java 7 or greater.

Supported Containers

Metrics CDI is currently successfully tested with the following containers:

Container Version Environment
Weld 2.3.4.Final Java SE 7,8 / CDI 1.2
OpenWebBeans 1.6.3 Java SE 7,8 / CDI 1.2
Jetty 9.3.3 Servlet 3.1
WildFly 8 8.2.1.Final Java EE 7
WildFly 9 9.0.2.Final Java EE 7
WildFly 10 10.0.0.Final Java EE 7

WildFly 8.1 requires to be patched with Weld 2.2+ as documented in Weld 2.2 on WildFly.

Usage

Metrics CDI activates the Metrics AOP Instrumentation for beans annotated with Metrics annotations and automatically registers the corresponding Metric instances in the Metrics registry resolved for the CDI application. The registration of these Metric instances happens each time such a bean gets instantiated. Besides, Metric instances can be retrieved from the Metrics registry by declaring metrics injection points.

The metrics registration mechanism can be used to customize the Metric instances that get registered. Besides, the Metrics registry resolution mechanism can be used for the application to provide a custom MetricRegistry instance.

Metrics AOP Instrumentation

Metrics comes with the metrics-annotation module that contains a set of annotations and provides a standard way to integrate Metrics with frameworks supporting Aspect Oriented Programming (AOP). These annotations are supported by Metrics CDI that implements their contract as documented in their Javadoc.

For example, a method of a bean can be annotated so that its execution can be monitored using Metrics:

import com.codahale.metrics.annotation.Timed;

class TimedMethodBean {

    @Timed
    void timedMethod() {
        // Timer name => TimedMethodBean.timedMethod
    }
}

or the bean class can be annotated directly so that all its public methods get monitored:

import com.codahale.metrics.annotation.Metered;

@Metered
public class MeteredClassBean {

    public void meteredMethod() {
        // Meter name => MeteredClassBean.meteredMethod
    }
}

or the bean constructor can be annotated so that its instantiations get monitored:

import com.codahale.metrics.annotation.Counted;

class CountedConstructorBean {

    @Counted
    CountedConstructorBean() {
        // Counter name => CountedConstructorBean.CountedConstructorBean
    }
}

The name and absolute attributes available on every Metrics annotation can be used to customize the name of the Metric instance that gets registered in the Metrics registry. The default naming convention being the annotated member simple name relative to the declaring class fully qualified name as illustrated in the above examples.

Metrics Injection

Metric instances can be retrieved from the Metrics registry by declaring an injected field, e.g.:

import com.codahale.metrics.Timer;

import javax.inject.Inject;

class TimerBean {

    @Inject
    private Timer timer; // Timer name => TimerBean.Timer
}

Metric instances can be injected similarly as parameters of any initializer method or bean constructor, e.g.:

import com.codahale.metrics.Timer;

import javax.inject.Inject;

class TimerBean {

    private final Timer timer;

    @Inject
    private TimerBean(Timer timer) { // Timer name => TimerBean.Timer
       this.timer = timer;
    }
}

In the above example, Java 8 with the -parameters compiler option activated is required to get access to injected parameter name. Indeed, access to parameter names at runtime has been introduced with JEP-118. More information can be found in Obtaining Names of Method Parameters from the Java tutorials. To work around that limitation for Java versions prior to Java 8, or to declare a specific name, the @Metric annotation can be used as documented hereafter.

In order to provide metadata for the Metric instantiation and resolution, the injection point can be annotated with the @Metric annotation, e.g., with an injected field:

import com.codahale.metrics.Timer;
import com.codahale.metrics.annotation.Metric;

import javax.inject.Inject;

@Inject
@Metric(name = "timerName", absolute = true)
private Timer timer; // Timer name => timerName

or when using a bean constructor:

import com.codahale.metrics.Timer;
import com.codahale.metrics.annotation.Metric;

import javax.inject.Inject;

class TimerBean {

    private final Timer timer;

    @Inject
    private TimerBean(@Metric(name = "timerName", absolute = true) Timer timer) {
        // Timer name => timerName
        this.timer = timer;
    }
}

Metrics Registration

While Metrics CDI automatically registers Metric instances during the Metrics AOP instrumentation, it may be necessary for an application to explicitly provide the Metric instances to register. For example, to provide particular Reservoir implementations to histograms or timers, e.g. with a producer field:

import com.codahale.metrics.SlidingTimeWindowReservoir;
import com.codahale.metrics.Timer;
import com.codahale.metrics.annotation.Metric;
import com.codahale.metrics.annotation.Timed;

import javax.enterprise.inject.Produces;

class TimedMethodBean {

    @Produces
    @Metric(name = "customTimer") // Timer name => TimedMethodBean.customTimer
    Timer Timer = new Timer(new SlidingTimeWindowReservoir(1L, TimeUnit.MINUTES));

    @Timed(name = "customTimer")
    void timedMethod() {
        // Timer name => TimedMethodBean.customTimer
    }
}

Another use case is to register custom gauges, e.g. with a producer method:

import com.codahale.metrics.Gauge;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Ratio;
import com.codahale.metrics.RatioGauge;
import com.codahale.metrics.Timer;
import com.codahale.metrics.annotation.Metric;
import com.codahale.metrics.annotation.Timed;

import javax.enterprise.inject.Produces;
import javax.inject.Inject;

class CacheHitRatioBean {

    @Inject
    private Meter hits; // Meter name => CacheHitRatioBean.hits

    @Timed(name = "calls")
    public void cachedMethod() {
        // Timer name => CacheHitRatioBean.calls
        if (hit)
            hits.mark();
    }

    @Produces
    @Metric(name = "cache-hits")
    private Gauge<Double> cacheHitRatioGauge(final Meter hits, final Timer calls) {
        return new RatioGauge() { // Gauge name => CacheHitRatioBean.cache-hits
            @Override
            protected Ratio getRatio() {
                return Ratio.of(hits.getOneMinuteRate(), calls.getOneMinuteRate());
            }
        };
    }
}

Since Java 8, lambda expressions can be used as a generic way to compose metrics, so that the above example can be rewritten the following way:

class CacheHitRatioBean {

    @Inject
    private Meter hits;

    @Timed(name = "calls")
    public void cachedMethod() {
        if (hit)
            hits.mark();
    }

    @Produces
    @Metric(name = "cache-hits")
    private Gauge<Double> cacheHitRatioGauge(Meter hits, Timer calls) {
        return () -> Ratio.of(hits.getCount(), calls.getCount()).getValue();
    }
}

Metrics Registry Resolution

Metrics CDI automatically registers a MetricRegistry bean into the CDI container to register any Metric instances produced. That default MetricRegistry bean can be injected using standard CDI typesafe resolution, for example, by declaring an injected field:

import com.codahale.metrics.MetricRegistry;

import javax.inject.Inject;

@Inject
private MetricRegistry registry;

or by declaring a bean constructor:

import com.codahale.metrics.MetricRegistry;

import javax.inject.Inject;

class MetricRegistryBean {

    private final MetricRegistry registry;

    @Inject
    private MetricRegistryBean(MetricRegistry registry) {
        this.registry = registry;
    }
}

Otherwise, Metrics CDI uses any MetricRegistry bean declared in the CDI container with the built-in default qualifier @Default so that a custom MetricRegistry can be provided. For example, that custom MetricRegistry can be declared with a producer field:

import com.codahale.metrics.MetricRegistry;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;

@Produces
@ApplicationScoped
private final MetricRegistry registry = new MetricRegistry();

or with a producer method:

import com.codahale.metrics.MetricRegistry;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;

class MetricRegistryFactoryBean {

    @Produces
    @ApplicationScoped
    private MetricRegistry metricRegistry() {
        return new MetricRegistry();
    }
}

Metrics CDI Configuration

Metrics CDI fires a MetricsConfiguration event at deployment time that can be used by the application to configure it, e.g.:

import io.astefanutti.metrics.cdi.MetricsConfiguration;

import javax.enterprise.event.Observes;

class MetricsCdiConfiguration {

    static void configure(@Observes MetricsConfiguration metrics) {
        metrics.useAbsoluteName(true);
    }
}

Note that this event can only be used within the context of the observer method invocation. Any attempt to call one of its methods outside of that context will result in an IllegalStateException to be thrown.

Limitations

CDI 1.2 leverages on Java Interceptors Specification 1.2 to provide the ability to associate interceptors to beans via typesafe interceptor bindings. Interceptors are a mean to separate cross-cutting concerns from the business logic and Metrics CDI is relying on interceptors to implement the support of Metrics annotations in a CDI enabled environment.

CDI 1.2 sets additional restrictions about the type of bean to which an interceptor can be bound. From a Metrics CDI end-user perspective, that implies that the managed beans to be monitored with Metrics (i.e. having at least one member method annotated with one of the Metrics annotations) must be proxyable bean types, as defined in Unproxyable bean types, that are:

  • Classes which don’t have a non-private constructor with no parameters,
  • Classes which are declared final,
  • Classes which have non-static, final methods with public, protected or default visibility,
  • Primitive types,
  • And array types.

License

Copyright © 2013-2015, Antonin Stefanutti

Published under Apache Software License 2.0, see LICENSE