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 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 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.