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