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