/* * Copyright 2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ratpack.dropwizard.metrics; import com.codahale.metrics.*; import com.codahale.metrics.annotation.Metered; import com.codahale.metrics.annotation.Timed; import com.codahale.metrics.graphite.GraphiteReporter; import com.codahale.metrics.jvm.GarbageCollectorMetricSet; import com.codahale.metrics.jvm.MemoryUsageGaugeSet; import com.codahale.metrics.jvm.ThreadStatesGaugeSet; import com.google.inject.Injector; import com.google.inject.Provider; import com.google.inject.matcher.Matchers; import com.google.inject.multibindings.Multibinder; import ratpack.dropwizard.metrics.internal.*; import ratpack.guice.ConfigurableModule; import ratpack.handling.HandlerDecorator; import ratpack.service.Service; import ratpack.service.StartEvent; import ratpack.service.StopEvent; import javax.inject.Inject; import static com.google.inject.Scopes.SINGLETON; import static java.util.concurrent.TimeUnit.SECONDS; /** * An extension module that provides support for Dropwizard Metrics. * <p> * To use it one has to register the module and enable the required functionality by chaining the various configuration * options. For example, to enable the capturing and reporting of metrics to {@link DropwizardMetricsConfig#jmx(ratpack.func.Action)} * one would write: (Groovy DSL) * * <pre class="groovy-ratpack-dsl"> * import ratpack.dropwizard.metrics.DropwizardMetricsModule * import static ratpack.groovy.Groovy.ratpack * * ratpack { * bindings { * module new DropwizardMetricsModule(), { it.jmx() } * } * } * </pre> * * <p> * To enable the capturing and reporting of metrics to JMX and the console, one would write: (Groovy DSL) * * <pre class="groovy-ratpack-dsl"> * import ratpack.dropwizard.metrics.DropwizardMetricsModule * import static ratpack.groovy.Groovy.ratpack * * ratpack { * bindings { * module new DropwizardMetricsModule(), { it.jmx().console() } * } * } * </pre> * * <h2>External Configuration</h2> * <p> * The module can also be configured via external configuration using the * <a href="http://www.ratpack.io/manual/current/api/ratpack/config/ConfigData.html" target="_blank">ratpack-config</a> extension. * For example, to enable the capturing and reporting of metrics to jmx via an external property file which can be overridden with * system properties one would write: (Groovy DSL) * * <pre class="groovy-ratpack-dsl"> * import com.google.common.collect.ImmutableMap * import ratpack.dropwizard.metrics.DropwizardMetricsModule * import ratpack.dropwizard.metrics.DropwizardMetricsConfig * import ratpack.config.ConfigData * import static ratpack.groovy.Groovy.ratpack * * ratpack { * serverConfig { * props(ImmutableMap.of("metrics.jmx.enabled", "true")) // for demo purposes we are using a map to easily see the properties being set * sysProps() * require("/metrics", DropwizardMetricsConfig) * } * * bindings { * module DropwizardMetricsModule * } * } * </pre> * * <h2>Metric Collection</h2> * <p> * By default {@link com.codahale.metrics.Timer} metrics are collected for all requests received and {@link Counter} metrics for response codes. * The module adds a default {@link RequestTimingHandler} to the handler chain <b>before</b> any user handlers. This means that response times do not * take any framework overhead into account and purely the amount of time spent in handlers. It is important that the module is registered first * in the modules list to ensure that <b>all</b> handlers are included in the metric. * <p> * The module also adds a default {@link BlockingExecTimingInterceptor} to the execution path. This will add timers that will account for time * spent on blocking io calls. * <p> * Both the request timing handler and the blocking execution timing interceptor can be disabled: * * <pre class="groovy-ratpack-dsl">{@code * import ratpack.dropwizard.metrics.DropwizardMetricsModule * import static ratpack.groovy.Groovy.ratpack * * ratpack { * bindings { * module new DropwizardMetricsModule(), { it.requestTimingMetrics(false).blockingTimingMetrics(false) } * } * * handlers { * all { * render "" * } * } * } * }</pre> * * <p> * Additional custom metrics can be registered with the provided {@link MetricRegistry} instance * <p> * Example custom metrics: (Groovy DSL) * * <pre class="groovy-ratpack-dsl">{@code * import ratpack.dropwizard.metrics.DropwizardMetricsModule * import com.codahale.metrics.MetricRegistry * import static ratpack.groovy.Groovy.ratpack * * ratpack { * bindings { * module new DropwizardMetricsModule(), { it.jmx() } * } * * handlers { MetricRegistry metricRegistry -> * all { * metricRegistry.meter("my custom meter").mark() * render "" * } * } * } * }</pre> * * <p> * Custom metrics can also be added via the Metrics annotations ({@link Metered}, {@link Timed} and {@link com.codahale.metrics.annotation.Gauge}) * to any Guice injected classes. * * @see <a href="https://dropwizard.github.io/metrics/3.1.0/manual/" target="_blank">Dropwizard Metrics</a> */ public class DropwizardMetricsModule extends ConfigurableModule<DropwizardMetricsConfig> { public static final String RATPACK_METRIC_REGISTRY = "ratpack-metrics"; @Override protected void configure() { SharedMetricRegistries.remove(RATPACK_METRIC_REGISTRY); MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(RATPACK_METRIC_REGISTRY); bind(MetricRegistry.class).toInstance(metricRegistry); bindInterceptor(Matchers.any(), Matchers.annotatedWith(Metered.class), injected(new MeteredMethodInterceptor())); bindInterceptor(Matchers.any(), Matchers.annotatedWith(Timed.class), injected(new TimedMethodInterceptor())); bindListener(Matchers.any(), injected(new GaugeTypeListener())); bind(JmxReporter.class).toProvider(JmxReporterProvider.class).in(SINGLETON); bind(ConsoleReporter.class).toProvider(ConsoleReporterProvider.class).in(SINGLETON); bind(Slf4jReporter.class).toProvider(Slf4jReporterProvider.class).in(SINGLETON); bind(CsvReporter.class).toProvider(CsvReporterProvider.class).in(SINGLETON); bind(GraphiteReporter.class).toProvider(GraphiteReporterProvider.class).in(SINGLETON); bind(MetricRegistryPeriodicPublisher.class).in(SINGLETON); bind(MetricsBroadcaster.class).in(SINGLETON); bind(Startup.class); bind(BlockingExecTimingInterceptor.class).toProvider(BlockingExecTimingInterceptorProvider.class).in(SINGLETON); bind(RequestTimingHandler.class).toProvider(RequestTimingHandlerProvider.class).in(SINGLETON); Provider<RequestTimingHandler> handlerProvider = getProvider(RequestTimingHandler.class); Multibinder.newSetBinder(binder(), HandlerDecorator.class).addBinding().toProvider(() -> HandlerDecorator.prepend(handlerProvider.get())); } private <T> T injected(T instance) { requestInjection(instance); return instance; } private static class Startup implements Service { private final DropwizardMetricsConfig config; private final Injector injector; @Inject public Startup(DropwizardMetricsConfig config, Injector injector) { this.config = config; this.injector = injector; } @Override public void onStart(StartEvent event) throws Exception { config.getJmx().ifPresent(jmx -> { if (jmx.isEnabled()) { injector.getInstance(JmxReporter.class).start(); } }); config.getConsole().ifPresent(console -> { if (console.isEnabled()) { injector.getInstance(ConsoleReporter.class).start(console.getReporterInterval().getSeconds(), SECONDS); } }); config.getSlf4j().ifPresent(slf4j -> { if (slf4j.isEnabled()) { injector.getInstance(Slf4jReporter.class).start(slf4j.getReporterInterval().getSeconds(), SECONDS); } }); config.getCsv().ifPresent(csv -> { if (csv.isEnabled()) { injector.getInstance(CsvReporter.class).start(csv.getReporterInterval().getSeconds(), SECONDS); } }); config.getGraphite().ifPresent(graphite -> { if (graphite.isEnabled()) { injector.getInstance(GraphiteReporter.class).start(graphite.getReporterInterval().getSeconds(), SECONDS); } }); if (config.isJvmMetrics()) { final MetricRegistry metricRegistry = injector.getInstance(MetricRegistry.class); metricRegistry.registerAll(new GarbageCollectorMetricSet()); metricRegistry.registerAll(new ThreadStatesGaugeSet()); metricRegistry.registerAll(new MemoryUsageGaugeSet()); } } @Override public void onStop(StopEvent event) throws Exception { Iterable<? extends ScheduledReporter> scheduledReporters = event.getRegistry().getAll(ScheduledReporter.class); for (ScheduledReporter scheduledReporter : scheduledReporters) { if (scheduledReporter != null) { scheduledReporter.stop(); } } } } }