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