2 * Copyright (c) 2017 - 2018 Red Hat, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.infrautils.metrics.internal;
10 import static com.codahale.metrics.Slf4jReporter.LoggingLevel.INFO;
11 import static java.lang.management.ManagementFactory.getThreadMXBean;
12 import static java.util.Objects.requireNonNull;
13 import static java.util.concurrent.TimeUnit.MILLISECONDS;
14 import static java.util.concurrent.TimeUnit.SECONDS;
16 import com.codahale.metrics.MetricRegistry;
17 import com.codahale.metrics.Slf4jReporter;
18 import com.codahale.metrics.jmx.JmxReporter;
19 import com.codahale.metrics.jvm.BufferPoolMetricSet;
20 import com.codahale.metrics.jvm.CachedThreadStatesGaugeSet;
21 import com.codahale.metrics.jvm.ClassLoadingGaugeSet;
22 import com.codahale.metrics.jvm.FileDescriptorRatioGauge;
23 import com.codahale.metrics.jvm.GarbageCollectorMetricSet;
24 import com.codahale.metrics.jvm.MemoryUsageGaugeSet;
25 import com.codahale.metrics.jvm.ThreadDeadlockDetector;
26 import com.google.common.annotations.VisibleForTesting;
27 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
28 import java.lang.management.ManagementFactory;
29 import java.time.Duration;
31 import java.util.concurrent.ConcurrentHashMap;
32 import javax.annotation.Nullable;
33 import javax.annotation.PreDestroy;
34 import javax.inject.Singleton;
35 import org.apache.aries.blueprint.annotation.service.Service;
36 import org.opendaylight.infrautils.metrics.Counter;
37 import org.opendaylight.infrautils.metrics.Labeled;
38 import org.opendaylight.infrautils.metrics.Meter;
39 import org.opendaylight.infrautils.metrics.MetricDescriptor;
40 import org.opendaylight.infrautils.metrics.MetricProvider;
41 import org.opendaylight.infrautils.metrics.Timer;
42 import org.opendaylight.infrautils.utils.UncheckedCloseable;
43 import org.opendaylight.infrautils.utils.function.CheckedCallable;
44 import org.opendaylight.infrautils.utils.function.CheckedRunnable;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
49 * Implementation of {@link MetricProvider} based on <a href="http://metrics.dropwizard.io">Coda Hale's Dropwizard Metrics</a>.
51 * @author Michael Vorburger.ch
54 @Service(classes = MetricProvider.class)
55 public class MetricProviderImpl implements MetricProvider {
57 private static final Logger LOG = LoggerFactory.getLogger(MetricProviderImpl.class);
59 private final Map<String, MeterImpl> meters = new ConcurrentHashMap<>();
60 private final Map<String, CounterImpl> counters = new ConcurrentHashMap<>();
61 private final Map<String, TimerImpl> timers = new ConcurrentHashMap<>();
62 private final MetricRegistry registry;
63 private final JmxReporter jmxReporter;
64 private final Slf4jReporter slf4jReporter;
66 private volatile @Nullable MetricsFileReporter fileReporter;
67 private volatile @Nullable ThreadsWatcher threadsWatcher;
69 public MetricProviderImpl() {
70 this.registry = new MetricRegistry();
72 setUpJvmMetrics(registry);
74 jmxReporter = setUpJmxReporter(registry);
76 slf4jReporter = setUpSlf4jReporter(registry);
78 // TODO really get this to work in Karaf, through PAX Logging.. (it's currently NOK)
79 // instrumentLog4jV2(registry);
82 public MetricProviderImpl(Configuration configuration) {
84 updateConfiguration(configuration);
87 public final void updateConfiguration(Configuration configuration) {
88 if (threadsWatcher != null) {
89 threadsWatcher.close();
91 if (configuration.getThreadsWatcherIntervalMS() > 0 && (threadsWatcher == null
92 || configuration.getThreadsWatcherIntervalMS() != threadsWatcher.getInterval().toMillis()
93 || configuration.getMaxThreads() != threadsWatcher.getMaxThreads())
94 || configuration.getMaxThreadsMaxLogIntervalSecs()
95 != threadsWatcher.getMaxThreadsMaxLogInterval().getSeconds()
96 || configuration.getDeadlockedThreadsMaxLogIntervalSecs()
97 != threadsWatcher.getDeadlockedThreadsMaxLogInterval().getSeconds()) {
98 threadsWatcher = new ThreadsWatcher(configuration.getMaxThreads(),
99 Duration.ofMillis(configuration.getThreadsWatcherIntervalMS()),
100 Duration.ofSeconds(configuration.getMaxThreadsMaxLogIntervalSecs()),
101 Duration.ofSeconds(configuration.getDeadlockedThreadsMaxLogIntervalSecs()));
102 threadsWatcher.start();
105 if (fileReporter != null) {
106 fileReporter.close();
108 int fileReporterInterval = fileReporter != null ? (int)fileReporter.getInterval().getSeconds() : 0;
109 if (fileReporterInterval != configuration.getFileReporterIntervalSecs()) {
110 if (configuration.getFileReporterIntervalSecs() > 0) {
111 fileReporter = new MetricsFileReporter(registry,
112 Duration.ofSeconds(configuration.getFileReporterIntervalSecs()));
113 fileReporter.startReporter();
116 LOG.info("Updated: {}", configuration);
120 public void close() {
122 if (fileReporter != null) {
123 fileReporter.close();
125 slf4jReporter.close();
126 if (threadsWatcher != null) {
127 threadsWatcher.close();
132 public MetricRegistry getRegistry() {
136 private static void setUpJvmMetrics(MetricRegistry registry) {
137 ThreadDeadlockDetector threadDeadlockDetector = new ThreadDeadlockDetector();
138 FileDescriptorRatioGauge fileDescriptorRatioGauge = new FileDescriptorRatioGauge();
140 registry.registerAll(new GarbageCollectorMetricSet());
141 registry.registerAll(new BufferPoolMetricSet(ManagementFactory.getPlatformMBeanServer()));
142 registry.registerAll(new CachedThreadStatesGaugeSet(getThreadMXBean(), threadDeadlockDetector, 13, SECONDS));
143 registry.registerAll(new MemoryUsageGaugeSet());
144 registry.registerAll(new ClassLoadingGaugeSet());
145 registry.gauge("odl.infrautils.FileDescriptorRatio", () -> fileDescriptorRatioGauge);
148 private static JmxReporter setUpJmxReporter(MetricRegistry registry) {
149 JmxReporter reporter = JmxReporter.forRegistry(registry)
150 .createsObjectNamesWith(new CustomObjectNameFactory()).build();
152 LOG.info("JmxReporter started, ODL application's metrics are now available via JMX");
156 private static Slf4jReporter setUpSlf4jReporter(MetricRegistry registry) {
157 Slf4jReporter slf4jReporter = Slf4jReporter.forRegistry(registry)
158 .convertDurationsTo(MILLISECONDS)
159 .convertRatesTo(SECONDS)
162 .withLoggingLevel(INFO)
163 .shutdownExecutorOnStop(true)
165 // NB: We do intentionally *NOT* start() the Slf4jReporter to log all metrics regularly;
166 // as that will spam the log, and we have our own file based reporting instead.
167 // We do log system metrics once at boot up:
168 LOG.info("One time system JVM metrics FYI; "
169 + "to watch continously, monitor via JMX or enable periodic file dump option");
170 slf4jReporter.report();
171 return slf4jReporter;
174 // http://metrics.dropwizard.io/3.1.0/manual/log4j/
175 // private static void instrumentLog4jV2(MetricRegistry registry) {
176 // // TODO Confirm that Level ALL is a good idea?
177 // Level level = ALL;
179 // InstrumentedAppender appender = new InstrumentedAppender(registry,
180 // null /* null Filter fine, because we don't use filters */,
181 // null /* null PatternLayout, because the layout isn't used in InstrumentedAppender */, false);
184 // LoggerContext context = (LoggerContext) LogManager.getContext(false);
185 // Configuration config = context.getConfiguration();
186 // config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME).addAppender(appender, level,
187 // null /* null Filter fine, because we don't use filters */);
188 // context.updateLoggers(config);
191 private org.opendaylight.infrautils.metrics.Meter newOrExistingMeter(Object anchor, String id) {
192 return meters.computeIfAbsent(id, newId -> {
193 LOG.debug("New Meter metric: {}", id);
194 return new MeterImpl(newId);
199 public org.opendaylight.infrautils.metrics.Meter newMeter(Object anchor, String id) {
200 requireNonNull(anchor, "anchor == null");
201 checkForExistingID(id);
202 return newOrExistingMeter(anchor, id);
206 public Meter newMeter(MetricDescriptor descriptor) {
207 return newMeter(descriptor.anchor(), makeCodahaleID(descriptor));
211 public Labeled<Meter> newMeter(MetricDescriptor descriptor, String labelName) {
212 return labelValue -> newOrExistingMeter(descriptor.anchor(),
213 makeCodahaleID(descriptor) + "{" + labelName + "=" + labelValue + "}");
217 public Labeled<Labeled<Meter>> newMeter(MetricDescriptor descriptor,
218 String firstLabelName, String secondLabelName) {
219 return firstLabelValue -> secondLabelValue -> newOrExistingMeter(descriptor.anchor(), makeCodahaleID(descriptor)
220 + "{" + firstLabelName + "=" + firstLabelValue + "," + secondLabelName + "=" + secondLabelValue + "}");
224 public Labeled<Labeled<Labeled<Meter>>> newMeter(MetricDescriptor descriptor,
225 String firstLabelName, String secondLabelName, String thirdLabelName) {
226 return firstLabelValue -> secondLabelValue -> thirdLabelValue ->
227 newOrExistingMeter(descriptor.anchor(), makeCodahaleID(descriptor) + "{"
228 + firstLabelName + "=" + firstLabelValue + "," + secondLabelName + "=" + secondLabelValue + ","
229 + thirdLabelName + "=" + thirdLabelValue + "}");
233 public Labeled<Labeled<Labeled<Labeled<Meter>>>> newMeter(MetricDescriptor descriptor,
234 String firstLabelName, String secondLabelName, String thirdLabelName, String fourthLabelName) {
235 return firstLabelValue -> secondLabelValue -> thirdLabelValue -> fourthLabelValue ->
236 newOrExistingMeter(descriptor.anchor(), makeCodahaleID(descriptor) + "{"
237 + firstLabelName + "=" + firstLabelValue + "," + secondLabelName + "=" + secondLabelValue + ","
238 + thirdLabelName + "=" + thirdLabelValue + ","
239 + fourthLabelName + "=" + fourthLabelValue + "}");
243 public Labeled<Labeled<Labeled<Labeled<Labeled<Meter>>>>> newMeter(MetricDescriptor descriptor,
244 String firstLabelName, String secondLabelName, String thirdLabelName,
245 String fourthLabelName, String fifthLabelName) {
246 return firstLabelValue -> secondLabelValue -> thirdLabelValue -> fourthLabelValue -> fifthLabelValue ->
247 newOrExistingMeter(descriptor.anchor(), makeCodahaleID(descriptor) + "{"
248 + firstLabelName + "=" + firstLabelValue + "," + secondLabelName + "=" + secondLabelValue + ","
249 + thirdLabelName + "=" + thirdLabelValue + ","
250 + fourthLabelName + "=" + fourthLabelValue + ","
251 + fifthLabelName + "=" + fifthLabelValue + "}");
254 private org.opendaylight.infrautils.metrics.Counter newOrExistingCounter(Object anchor, String id) {
255 return counters.computeIfAbsent(id, newId -> {
256 LOG.debug("New Counter metric: {}", id);
257 return new CounterImpl(newId);
262 public org.opendaylight.infrautils.metrics.Counter newCounter(Object anchor, String id) {
263 requireNonNull(anchor, "anchor == null");
264 checkForExistingID(id);
265 return newOrExistingCounter(anchor, id);
269 public Counter newCounter(MetricDescriptor descriptor) {
270 return newCounter(descriptor.anchor(), makeCodahaleID(descriptor));
274 public Labeled<Counter> newCounter(MetricDescriptor descriptor, String labelName) {
275 return labelValue -> newOrExistingCounter(descriptor.anchor(),
276 makeCodahaleID(descriptor) + "{" + labelName + "=" + labelValue + "}");
280 public Labeled<Labeled<Counter>> newCounter(MetricDescriptor descriptor,
281 String firstLabelName, String secondLabelName) {
282 return firstLabelValue -> secondLabelValue -> newOrExistingCounter(descriptor.anchor(),
283 makeCodahaleID(descriptor) + "{" + firstLabelName + "=" + firstLabelValue + ","
284 + secondLabelName + "=" + secondLabelValue + "}");
288 public Labeled<Labeled<Labeled<Counter>>> newCounter(MetricDescriptor descriptor, String firstLabelName,
289 String secondLabelName, String thirdLabelName) {
290 return firstLabelValue -> secondLabelValue -> thirdLabelValue ->
291 newOrExistingCounter(descriptor.anchor(), makeCodahaleID(descriptor) + "{"
292 + firstLabelName + "=" + firstLabelValue + "," + secondLabelName + "=" + secondLabelValue + ","
293 + thirdLabelName + "=" + thirdLabelValue + "}");
297 public Labeled<Labeled<Labeled<Labeled<Counter>>>> newCounter(MetricDescriptor descriptor,
298 String firstLabelName, String secondLabelName,
299 String thirdLabelName, String fourthLabelName) {
300 return firstLabelValue -> secondLabelValue -> thirdLabelValue -> fourthLabelValue ->
301 newOrExistingCounter(descriptor.anchor(), makeCodahaleID(descriptor) + "{"
302 + firstLabelName + "=" + firstLabelValue + "," + secondLabelName + "=" + secondLabelValue + ","
303 + thirdLabelName + "=" + thirdLabelValue + ","
304 + fourthLabelName + "=" + fourthLabelValue + "}");
308 public Labeled<Labeled<Labeled<Labeled<Labeled<Counter>>>>> newCounter(MetricDescriptor descriptor,
309 String firstLabelName, String secondLabelName,
310 String thirdLabelName, String fourthLabelName,
311 String fifthLabelName) {
312 return firstLabelValue -> secondLabelValue -> thirdLabelValue -> fourthLabelValue -> fifthLabelValue ->
313 newOrExistingCounter(descriptor.anchor(), makeCodahaleID(descriptor) + "{"
314 + firstLabelName + "=" + firstLabelValue + "," + secondLabelName + "=" + secondLabelValue + ","
315 + thirdLabelName + "=" + thirdLabelValue + "," + fourthLabelName + "=" + fourthLabelValue + ","
316 + fifthLabelName + "=" + fifthLabelValue + "}");
319 private org.opendaylight.infrautils.metrics.Timer newOrExistingTimer(Object anchor, String id) {
320 return timers.computeIfAbsent(id, newId -> {
321 LOG.debug("New Timer metric: {}", id);
322 return new TimerImpl(newId);
327 public org.opendaylight.infrautils.metrics.Timer newTimer(Object anchor, String id) {
328 requireNonNull(anchor, "anchor == null");
329 checkForExistingID(id);
330 return newOrExistingTimer(anchor, id);
334 public Timer newTimer(MetricDescriptor descriptor) {
335 return newOrExistingTimer(descriptor.anchor(), makeCodahaleID(descriptor));
339 public Labeled<Timer> newTimer(MetricDescriptor descriptor, String labelName) {
340 return labelValue -> newOrExistingTimer(descriptor.anchor(),
341 makeCodahaleID(descriptor) + "{" + labelName + "=" + labelValue + "}");
345 public Labeled<Labeled<Timer>> newTimer(MetricDescriptor descriptor,
346 String firstLabelName, String secondLabelName) {
347 return firstLabelValue -> secondLabelValue -> newOrExistingTimer(descriptor.anchor(),
348 makeCodahaleID(descriptor) + "{" + firstLabelName + "=" + firstLabelValue + ","
349 + secondLabelName + "=" + secondLabelValue + "}");
352 private static String makeCodahaleID(MetricDescriptor descriptor) {
353 // We're ignoring descriptor.description() because Codahale Dropwizard Metrics
354 // doesn't have it but other future metrics API implementations (e.g. Prometheus.io), or a
355 // possible future metrics:list kind of CLI here, will use it.
356 return MetricRegistry.name(descriptor.project(), descriptor.module(), descriptor.id());
359 private void checkForExistingID(String id) {
360 requireNonNull(id, "id == null");
361 if (registry.getNames().contains(id)) {
362 throw new IllegalArgumentException("Metric ID already used: " + id);
366 private abstract class CloseableMetricImpl implements UncheckedCloseable {
367 private volatile boolean isClosed = false;
368 protected final String id;
370 CloseableMetricImpl(String id) {
374 protected void checkIfClosed() {
376 throw new IllegalStateException("Metric closed: " + id);
381 public void close() {
384 if (!registry.remove(id)) {
385 LOG.warn("Metric remove did not actualy remove: {}", id);
390 private final class MeterImpl extends CloseableMetricImpl implements org.opendaylight.infrautils.metrics.Meter {
392 private final com.codahale.metrics.Meter meter;
394 MeterImpl(String id) {
396 this.meter = registry.meter(id);
406 public void mark(long howMany) {
413 return meter.getCount();
417 public void close() {
423 private final class CounterImpl extends CloseableMetricImpl
424 implements org.opendaylight.infrautils.metrics.Counter {
426 private final com.codahale.metrics.Counter counter;
428 CounterImpl(String id) {
430 this.counter = registry.counter(id);
434 public void increment() {
440 public void increment(long howMany) {
442 counter.inc(howMany);
446 public void decrement() {
452 public void decrement(long howMany) {
454 counter.dec(howMany);
459 return counter.getCount();
463 public void close() {
469 private class TimerImpl extends CloseableMetricImpl implements org.opendaylight.infrautils.metrics.Timer {
471 private final com.codahale.metrics.Timer timer;
473 TimerImpl(String id) {
475 this.timer = registry.timer(id);
479 @SuppressWarnings({ "checkstyle:IllegalCatch", "unchecked" })
480 public <T, E extends Exception> T time(CheckedCallable<T, E> event) throws E {
483 return timer.time(event::call);
484 } catch (Exception e) {
490 @SuppressWarnings({ "checkstyle:IllegalCatch", "checkstyle:AvoidHidingCauseException", "unchecked" })
491 @SuppressFBWarnings("BC_UNCONFIRMED_CAST_OF_RETURN_VALUE") // getCause() will be Exception not Throwable
492 public <E extends Exception> void time(CheckedRunnable<E> event) throws E {
498 } catch (Exception exception) {
499 throw new InternalRuntimeException(exception);
502 } catch (InternalRuntimeException e) {
503 throw (E) e.getCause();
508 private static class InternalRuntimeException extends RuntimeException {
509 private static final long serialVersionUID = 1L;
511 InternalRuntimeException(Exception exception) {