001package io.prometheus.client.dropwizard;
002
003import com.codahale.metrics.*;
004
005import java.util.ArrayList;
006import java.util.Arrays;
007import java.util.List;
008import java.util.SortedMap;
009import java.util.concurrent.TimeUnit;
010import java.util.logging.Level;
011import java.util.logging.Logger;
012import java.util.regex.Pattern;
013
014/**
015 * Collect Dropwizard metrics from a MetricRegistry.
016 */
017public class DropwizardExports extends io.prometheus.client.Collector implements io.prometheus.client.Collector.Describable {
018    private MetricRegistry registry;
019    private static final Logger LOGGER = Logger.getLogger(DropwizardExports.class.getName());
020
021    /**
022     * @param registry a metric registry to export in prometheus.
023     */
024    public DropwizardExports(MetricRegistry registry) {
025        this.registry = registry;
026    }
027
028    /**
029     * Export counter as Prometheus <a href="https://prometheus.io/docs/concepts/metric_types/#gauge">Gauge</a>.
030     */
031    List<MetricFamilySamples> fromCounter(String dropwizardName, Counter counter) {
032        String name = sanitizeMetricName(dropwizardName);
033        MetricFamilySamples.Sample sample = new MetricFamilySamples.Sample(name, new ArrayList<String>(), new ArrayList<String>(),
034                new Long(counter.getCount()).doubleValue());
035        return Arrays.asList(new MetricFamilySamples(name, Type.GAUGE, getHelpMessage(dropwizardName, counter), Arrays.asList(sample)));
036    }
037
038    private static String getHelpMessage(String metricName, Metric metric){
039        return String.format("Generated from Dropwizard metric import (metric=%s, type=%s)",
040                metricName, metric.getClass().getName());
041    }
042
043    /**
044     * Export gauge as a prometheus gauge.
045     */
046    List<MetricFamilySamples> fromGauge(String dropwizardName, Gauge gauge) {
047        String name = sanitizeMetricName(dropwizardName);
048        Object obj = gauge.getValue();
049        double value;
050        if (obj instanceof Number) {
051            value = ((Number) obj).doubleValue();
052        } else if (obj instanceof Boolean) {
053            value = ((Boolean) obj) ? 1 : 0;
054        } else {
055            LOGGER.log(Level.FINE, String.format("Invalid type for Gauge %s: %s", name,
056                    obj.getClass().getName()));
057            return new ArrayList<MetricFamilySamples>();
058        }
059        MetricFamilySamples.Sample sample = new MetricFamilySamples.Sample(name,
060                new ArrayList<String>(), new ArrayList<String>(), value);
061        return Arrays.asList(new MetricFamilySamples(name, Type.GAUGE, getHelpMessage(dropwizardName, gauge), Arrays.asList(sample)));
062    }
063
064    /**
065     * Export a histogram snapshot as a prometheus SUMMARY.
066     *
067     * @param dropwizardName metric name.
068     * @param snapshot the histogram snapshot.
069     * @param count the total sample count for this snapshot.
070     * @param factor a factor to apply to histogram values.
071     *
072     */
073    List<MetricFamilySamples> fromSnapshotAndCount(String dropwizardName, Snapshot snapshot, long count, double factor, String helpMessage) {
074        String name = sanitizeMetricName(dropwizardName);
075        List<MetricFamilySamples.Sample> samples = Arrays.asList(
076                new MetricFamilySamples.Sample(name, Arrays.asList("quantile"), Arrays.asList("0.5"), snapshot.getMedian() * factor),
077                new MetricFamilySamples.Sample(name, Arrays.asList("quantile"), Arrays.asList("0.75"), snapshot.get75thPercentile() * factor),
078                new MetricFamilySamples.Sample(name, Arrays.asList("quantile"), Arrays.asList("0.95"), snapshot.get95thPercentile() * factor),
079                new MetricFamilySamples.Sample(name, Arrays.asList("quantile"), Arrays.asList("0.98"), snapshot.get98thPercentile() * factor),
080                new MetricFamilySamples.Sample(name, Arrays.asList("quantile"), Arrays.asList("0.99"), snapshot.get99thPercentile() * factor),
081                new MetricFamilySamples.Sample(name, Arrays.asList("quantile"), Arrays.asList("0.999"), snapshot.get999thPercentile() * factor),
082                new MetricFamilySamples.Sample(name + "_count", new ArrayList<String>(), new ArrayList<String>(), count)
083        );
084        return Arrays.asList(
085                new MetricFamilySamples(name, Type.SUMMARY, helpMessage, samples)
086        );
087    }
088
089    /**
090     * Convert histogram snapshot.
091     */
092    List<MetricFamilySamples> fromHistogram(String dropwizardName, Histogram histogram) {
093        return fromSnapshotAndCount(dropwizardName, histogram.getSnapshot(), histogram.getCount(), 1.0,
094                getHelpMessage(dropwizardName, histogram));
095    }
096
097    /**
098     * Export Dropwizard Timer as a histogram. Use TIME_UNIT as time unit.
099     */
100    List<MetricFamilySamples> fromTimer(String dropwizardName, Timer timer) {
101        return fromSnapshotAndCount(dropwizardName, timer.getSnapshot(), timer.getCount(),
102                1.0D / TimeUnit.SECONDS.toNanos(1L), getHelpMessage(dropwizardName, timer));
103    }
104
105    /**
106     * Export a Meter as as prometheus COUNTER.
107     */
108    List<MetricFamilySamples> fromMeter(String dropwizardName, Meter meter) {
109        String name = sanitizeMetricName(dropwizardName);
110        return Arrays.asList(
111                new MetricFamilySamples(name + "_total", Type.COUNTER, getHelpMessage(dropwizardName, meter),
112                        Arrays.asList(new MetricFamilySamples.Sample(name + "_total",
113                                new ArrayList<String>(),
114                                new ArrayList<String>(),
115                                meter.getCount())))
116
117        );
118    }
119
120    private static final Pattern METRIC_NAME_RE = Pattern.compile("[^a-zA-Z0-9:_]");
121
122    /**
123     * Replace all unsupported chars with '_', prepend '_' if name starts with digit.
124     *
125     * @param dropwizardName
126     *            original metric name.
127     * @return the sanitized metric name.
128     */
129    public static String sanitizeMetricName(String dropwizardName) {
130        String name = METRIC_NAME_RE.matcher(dropwizardName).replaceAll("_");
131        if (!name.isEmpty() && Character.isDigit(name.charAt(0))) {
132            name = "_" + name;
133        }
134        return name;
135    }
136
137    @Override
138    public List<MetricFamilySamples> collect() {
139        ArrayList<MetricFamilySamples> mfSamples = new ArrayList<MetricFamilySamples>();
140        for (SortedMap.Entry<String, Gauge> entry : registry.getGauges().entrySet()) {
141            mfSamples.addAll(fromGauge(entry.getKey(), entry.getValue()));
142        }
143        for (SortedMap.Entry<String, Counter> entry : registry.getCounters().entrySet()) {
144            mfSamples.addAll(fromCounter(entry.getKey(), entry.getValue()));
145        }
146        for (SortedMap.Entry<String, Histogram> entry : registry.getHistograms().entrySet()) {
147            mfSamples.addAll(fromHistogram(entry.getKey(), entry.getValue()));
148        }
149        for (SortedMap.Entry<String, Timer> entry : registry.getTimers().entrySet()) {
150            mfSamples.addAll(fromTimer(entry.getKey(), entry.getValue()));
151        }
152        for (SortedMap.Entry<String, Meter> entry : registry.getMeters().entrySet()) {
153            mfSamples.addAll(fromMeter(entry.getKey(), entry.getValue()));
154        }
155        return mfSamples;
156    }
157
158    @Override
159    public List<MetricFamilySamples> describe() {
160      return new ArrayList<MetricFamilySamples>();
161    }
162}