1. Introduction

The Dropwizard Metrics plugin provides support for monitoring health checks and metrics monitoring for Grails 3 applications.

2. Installation

To add the Dropwizard Metrics plugin to an application add the following dependency to the dependencies block of your build.gradle:

compile "org.grails.plugins:dropwizard-metrics:1.0.0.M2"

3. Metrics

3.1. PublicMetric Beans

The plugin will discover all of the org.springframework.boot.actuate.endpoint.PublicMetrics instances that are found in the Spring application context and active them. An application may provide any number of PublicMetrics beans.

3.2. Marking Meters

A meter measures the rate of events over time (e.g., “requests per second”). In addition to the mean rate, meters also track 1-, 5-, and 15-minute moving averages.
— The Dropwizard Metrics Documentation

The Meterable trait provides a markMeter(String) method which makes it easy to access and increment a Meter. Any class may implement the trait and take advantage of this method.

class SomeClass implements Meterable {

    def someAction() {
        markMeter 'some meter'

        // ...
    }
}

The plugin provides the Metered annotation which may be applied to any method. If a method is marked with @Metered, every time the method is invoked, the meter with the specified name will automatically be incremented.

class SomeOtherClass {

    @Metered('some meter')
    void someAction() {
        // ...
    }
    @Metered(value='some other meter', useClassPrefix = true)
    void someOtherAction() {
        // ...
    }
    @Metered(value='yet another meter', useClassPrefix = false)
    void yetAnotherAction() {
        // ...
    }
}

The @Metered annotation supports an optional useClassPrefix attribute which when set to true will cause the name of the associated meter to be prefixed with the fully qualified name of the class which defines the annotated method.

3.3. Timers

A timer is basically a histogram of the duration of a type of event and a meter of the rate of its occurrence.
— The Dropwizard Metrics Documentation

The plugin provides the Timed annotation which may be applied to any method. If a method is marked with @Timed, every time the method is invoked, the timer with the specified name will automatically be started.

class SomeTimedClass {

    @Timed('some timer')
    void someAction() {
        // ...
    }

    @Timed(value='some other timer', useClassPrefix = true)
    void someOtherAction() {
        // ...
    }

    @Timed(value='yet other timer', useClassPrefix = false)
    void yetAnotherAction() {
        // ...
    }
}

The @Timed annotation supports an optional useClassPrefix attribute which when set to true will cause the name of the associated timer to be prefixed with the fully qualified name of the class which defines the annotated method.

3.4. Unit Testing

When unit testing code which uses any of the plugin’s code generation techniques (@Timed, @Metered, etc…​) a bean named metricsRegistry will need to be registed in the Spring application context. Below are examples of how to do that.

import com.codahale.metrics.MetricRegistry
import grails.test.mixin.TestMixin
import grails.test.mixin.support.GrailsUnitTestMixin
import spock.lang.Specification

@TestMixin(GrailsUnitTestMixin)
class MeterableSpec extends Specification {

    static doWithSpring = {
        metricRegistry MetricRegistry
        someBean SomeClass
    }

    void 'test markMeter method'() {
        setup:
        def registry = applicationContext.metricRegistry
        def obj = applicationContext.someBean

        when:
        obj.someAction()
        obj.someAction()
        obj.someAction()

        then:
        registry.meter('some meter').count == 3
    }
}

class SomeClass implements Meterable {

    def someAction() {
        markMeter 'some meter'

        // ...
    }
}
import com.codahale.metrics.MetricRegistry
import grails.test.mixin.TestMixin
import grails.test.mixin.support.GrailsUnitTestMixin
import spock.lang.Specification

@TestMixin(GrailsUnitTestMixin)
class MeteredAnnotationSpec extends Specification {

    static doWithSpring = {
        metricRegistry MetricRegistry
        someMeteredBean SomeOtherClass
    }

    void 'test the @Metered annotation'() {
        setup:
        def registry = applicationContext.metricRegistry
        def obj = applicationContext.someMeteredBean

        when:
        obj.someAction()
        obj.someAction()
        obj.someAction()

        then:
        registry.meter('some meter').count == 3
    }

    void 'test the @Metered annotation with useClassPrefix set to true'() {
        setup:
        def registry = applicationContext.metricRegistry
        def obj = applicationContext.someMeteredBean

        when:
        obj.someOtherAction()
        obj.someOtherAction()
        obj.someOtherAction()

        then:
        registry.meter('grails.plugin.dropwizard.metrics.meters.SomeOtherClass.some other meter').count == 3
    }

    void 'test the @Metered annotation with useClassPrefix set to false'() {
        setup:
        def registry = applicationContext.metricRegistry
        def obj = applicationContext.someMeteredBean

        when:
        obj.yetAnotherAction()
        obj.yetAnotherAction()
        obj.yetAnotherAction()

        then:
        registry.meter('yet another meter').count == 3
    }
}

class SomeOtherClass {

    @Metered('some meter')
    void someAction() {
        // ...
    }
    @Metered(value='some other meter', useClassPrefix = true)
    void someOtherAction() {
        // ...
    }
    @Metered(value='yet another meter', useClassPrefix = false)
    void yetAnotherAction() {
        // ...
    }
}

3.5. Exposing Metrics Data

The plugin supports exposing access to metrics data through a URI in the web application. By default metrics are available at /metrics. A request to /metrics will be responded to with JSON provided by all of the MetricRegistry instances that are active in the application as shown below.

{"version":"3.0.0","gauges":{"grails.dropwizard.dropwizardGarbageCollectorMetricSet.PS-MarkSweep.count":{"value":0},"grails.dropwizard.dropwizardGarbageCollectorMetricSet.PS-MarkSweep.time":{"value":0},"grails.dropwizard.dropwizardGarbageCollectorMetricSet.PS-Scavenge.count":{"value":9},"grails.dropwizard.dropwizardGarbageCollectorMetricSet.PS-Scavenge.time":{"value":468},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.heap.committed":{"value":738197504},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.heap.init":{"value":268435456},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.heap.max":{"value":3817865216},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.heap.usage":{"value":0.14876779767387158},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.heap.used":{"value":567975400},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.non-heap.committed":{"value":64880640},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.non-heap.init":{"value":24576000},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.non-heap.max":{"value":136314880},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.non-heap.usage":{"value":0.47363680326021634},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.non-heap.used":{"value":64563744},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.Code-Cache.committed":{"value":6160384},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.Code-Cache.init":{"value":2555904},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.Code-Cache.max":{"value":50331648},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.Code-Cache.usage":{"value":0.11999003092447917},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.Code-Cache.used":{"value":6039296},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.PS-Eden-Space.committed":{"value":525336576},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.PS-Eden-Space.init":{"value":67633152},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.PS-Eden-Space.max":{"value":1354760192},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.PS-Eden-Space.usage":{"value":0.3691370819375242},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.PS-Eden-Space.used":{"value":500092224},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.PS-Old-Gen.committed":{"value":178782208},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.PS-Old-Gen.init":{"value":178782208},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.PS-Old-Gen.max":{"value":2863136768},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.PS-Old-Gen.usage":{"value":0.011815351742218974},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.PS-Old-Gen.used":{"value":33828968},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.PS-Perm-Gen.committed":{"value":58720256},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.PS-Perm-Gen.init":{"value":22020096},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.PS-Perm-Gen.max":{"value":85983232},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.PS-Perm-Gen.usage":{"value":0.6806495480421113},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.PS-Perm-Gen.used":{"value":58524448},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.PS-Survivor-Space.committed":{"value":34078720},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.PS-Survivor-Space.init":{"value":11010048},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.PS-Survivor-Space.max":{"value":34078720},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.PS-Survivor-Space.usage":{"value":0.9992807241586539},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.pools.PS-Survivor-Space.used":{"value":34054208},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.total.committed":{"value":803078144},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.total.init":{"value":293011456},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.total.max":{"value":3954180096},"grails.dropwizard.dropwizardMemoryUsageGaugeSet.total.used":{"value":632539312},"grails.dropwizard.dropwizardThreadStatesGaugeSet.blocked.count":{"value":0},"grails.dropwizard.dropwizardThreadStatesGaugeSet.count":{"value":23},"grails.dropwizard.dropwizardThreadStatesGaugeSet.daemon.count":{"value":20},"grails.dropwizard.dropwizardThreadStatesGaugeSet.deadlock.count":{"value":0},"grails.dropwizard.dropwizardThreadStatesGaugeSet.deadlocks":{"value":[]},"grails.dropwizard.dropwizardThreadStatesGaugeSet.new.count":{"value":0},"grails.dropwizard.dropwizardThreadStatesGaugeSet.runnable.count":{"value":9},"grails.dropwizard.dropwizardThreadStatesGaugeSet.terminated.count":{"value":0},"grails.dropwizard.dropwizardThreadStatesGaugeSet.timed_waiting.count":{"value":9},"grails.dropwizard.dropwizardThreadStatesGaugeSet.waiting.count":{"value":5}},"counters":{},"histograms":{},"meters":{},"timers":{}}
TBD - notes on configuring the endpoint.

Metrics may also be exposed as JMX MBeans. To enable JMX support assign the value true to the grails.dropwizard.jmx.enabled config setting.

---
grails:
    dropwizard:
        jmx:
            enabled: true

4. Health Checks

4.1. HealthCheck Beans

The plugin will discover all of the org.springframework.boot.actuate.health.HealthIndicator instances that are found in the Spring application context and active them. An application may provide any number of HealthCheck beans.

4.2. Exposing Health Check Data

The plugin supports exposing access to health check data through a URI in the web application. To enable exposing the data assign a value to the grails.dropwizard.health.uri property to indicate where the data should be made available. A request to /health will be responded to with JSON provided by all of the HealthCheck instances that are active in the application as shown below.

{"dropwizardThreadDeadlockHealthCheck":{"healthy":true}}
TBD - notes on configuring the endpoint.

5. Logging

The plugin provides support for automatically logging metrics at a scheduled interval. To enable logging assign a value to the grails.dropwizard.metrics.reporterFrequency config property to indicate how often the logging should occur. The value represents the logging interval in seconds.

grails:
    dropwizard:
        metrics:
            reporterFrequency: 30 # log every 30 seconds

The logging is being done from a logger named grails.plugin.dropwizard.DropwizardMetricsGrailsPlugin at the INFO level. You may want to configure the level for that logger in grails-app/conf/logback.groovy as shown below.

logger 'grails.plugin.dropwizard.DropwizardMetricsGrailsPlugin',
        INFO, ['STDOUT'], false

6. API Documentation

API documentation may be found here.