/*
* Copyright 2012-2017 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 org.springframework.boot.actuate.metrics.dropwizard;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Reservoir;
import com.codahale.metrics.Timer;
import org.springframework.boot.actuate.metrics.CounterService;
import org.springframework.boot.actuate.metrics.GaugeService;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;
/**
* A {@link GaugeService} and {@link CounterService} that sends data to a Dropwizard
* {@link MetricRegistry} based on a naming convention.
* <ul>
* <li>Updates to {@link #increment(String)} with names in "meter.*" are treated as
* {@link Meter} events</li>
* <li>Other deltas are treated as simple {@link Counter} values</li>
* <li>Inputs to {@link #submit(String, double)} with names in "histogram.*" are treated
* as {@link Histogram} updates</li>
* <li>Inputs to {@link #submit(String, double)} with names in "timer.*" are treated as
* {@link Timer} updates</li>
* <li>Other metrics are treated as simple {@link Gauge} values (single valued
* measurements of type double)</li>
* </ul>
*
* @author Dave Syer
* @author Jay Anderson
* @author Andy Wilkinson
*/
public class DropwizardMetricServices implements CounterService, GaugeService {
private final MetricRegistry registry;
private final ReservoirFactory reservoirFactory;
private final ConcurrentMap<String, SimpleGauge> gauges = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, String> names = new ConcurrentHashMap<>();
/**
* Create a new {@link DropwizardMetricServices} instance.
* @param registry the underlying metric registry
*/
public DropwizardMetricServices(MetricRegistry registry) {
this(registry, null);
}
/**
* Create a new {@link DropwizardMetricServices} instance.
* @param registry the underlying metric registry
* @param reservoirFactory the factory that instantiates the {@link Reservoir} that
* will be used on Timers and Histograms
*/
public DropwizardMetricServices(MetricRegistry registry,
ReservoirFactory reservoirFactory) {
this.registry = registry;
this.reservoirFactory = (reservoirFactory == null ? ReservoirFactory.NONE
: reservoirFactory);
}
@Override
public void increment(String name) {
incrementInternal(name, 1L);
}
@Override
public void decrement(String name) {
incrementInternal(name, -1L);
}
private void incrementInternal(String name, long value) {
if (name.startsWith("meter")) {
Meter meter = this.registry.meter(name);
meter.mark(value);
}
else {
name = wrapCounterName(name);
Counter counter = this.registry.counter(name);
counter.inc(value);
}
}
@Override
public void submit(String name, double value) {
if (name.startsWith("histogram")) {
submitHistogram(name, value);
}
else if (name.startsWith("timer")) {
submitTimer(name, value);
}
else {
name = wrapGaugeName(name);
setGaugeValue(name, value);
}
}
private void submitTimer(String name, double value) {
long longValue = (long) value;
Timer metric = register(name, new TimerMetricRegistrar());
metric.update(longValue, TimeUnit.MILLISECONDS);
}
private void submitHistogram(String name, double value) {
long longValue = (long) value;
Histogram metric = register(name, new HistogramMetricRegistrar());
metric.update(longValue);
}
@SuppressWarnings("unchecked")
private <T extends Metric> T register(String name, MetricRegistrar<T> registrar) {
Reservoir reservoir = this.reservoirFactory.getReservoir(name);
if (reservoir == null) {
return registrar.register(this.registry, name);
}
Metric metric = this.registry.getMetrics().get(name);
if (metric != null) {
registrar.checkExisting(metric);
return (T) metric;
}
try {
return this.registry.register(name, registrar.createForReservoir(reservoir));
}
catch (IllegalArgumentException ex) {
Metric added = this.registry.getMetrics().get(name);
registrar.checkExisting(metric);
return (T) added;
}
}
private void setGaugeValue(String name, double value) {
// NOTE: Dropwizard provides no way to do this atomically
SimpleGauge gauge = this.gauges.get(name);
if (gauge == null) {
SimpleGauge newGauge = new SimpleGauge(value);
gauge = this.gauges.putIfAbsent(name, newGauge);
if (gauge == null) {
this.registry.register(name, newGauge);
return;
}
}
gauge.setValue(value);
}
private String wrapGaugeName(String metricName) {
return wrapName(metricName, "gauge.");
}
private String wrapCounterName(String metricName) {
return wrapName(metricName, "counter.");
}
private String wrapName(String metricName, String prefix) {
String cached = this.names.get(metricName);
if (cached != null) {
return cached;
}
if (metricName.startsWith(prefix)) {
return metricName;
}
String name = prefix + metricName;
this.names.put(metricName, name);
return name;
}
@Override
public void reset(String name) {
if (!name.startsWith("meter")) {
name = wrapCounterName(name);
}
this.registry.remove(name);
}
/**
* Simple {@link Gauge} implementation to {@literal double} value.
*/
private final static class SimpleGauge implements Gauge<Double> {
private volatile double value;
private SimpleGauge(double value) {
this.value = value;
}
@Override
public Double getValue() {
return this.value;
}
public void setValue(double value) {
this.value = value;
}
}
/**
* Strategy used to register metrics.
*/
private static abstract class MetricRegistrar<T extends Metric> {
private final Class<T> type;
@SuppressWarnings("unchecked")
MetricRegistrar() {
this.type = (Class<T>) ResolvableType
.forClass(MetricRegistrar.class, getClass()).resolveGeneric();
}
public void checkExisting(Metric metric) {
Assert.isInstanceOf(this.type, metric,
"Different metric type already registered");
}
protected abstract T register(MetricRegistry registry, String name);
protected abstract T createForReservoir(Reservoir reservoir);
}
/**
* {@link MetricRegistrar} for {@link Timer} metrics.
*/
private static class TimerMetricRegistrar extends MetricRegistrar<Timer> {
@Override
protected Timer register(MetricRegistry registry, String name) {
return registry.timer(name);
}
@Override
protected Timer createForReservoir(Reservoir reservoir) {
return new Timer(reservoir);
}
}
/**
* {@link MetricRegistrar} for {@link Histogram} metrics.
*/
private static class HistogramMetricRegistrar extends MetricRegistrar<Histogram> {
@Override
protected Histogram register(MetricRegistry registry, String name) {
return registry.histogram(name);
}
@Override
protected Histogram createForReservoir(Reservoir reservoir) {
return new Histogram(reservoir);
}
}
}