001package io.prometheus.client.dropwizard;
002
003
004import com.codahale.metrics.*;
005
006import java.util.ArrayList;
007import java.util.Arrays;
008import java.util.List;
009import java.util.SortedMap;
010import java.util.concurrent.TimeUnit;
011import java.util.logging.Level;
012import java.util.logging.Logger;
013
014/**
015 * Collect dropwizard metrics from a MetricRegistry.
016 */
017public class DropwizardExports extends io.prometheus.client.Collector {
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 counter.
030     */
031    List<MetricFamilySamples> fromCounter(String name, Counter counter) {
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(name, 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 name, Gauge gauge) {
046        Object obj = gauge.getValue();
047        Double value;
048        if (obj instanceof Integer) {
049            value = Double.valueOf(((Integer) obj).doubleValue());
050        } else if (obj instanceof Double) {
051            value = (Double) obj;
052        } else if (obj instanceof Float) {
053            value = Double.valueOf(((Float) obj).doubleValue());
054        } else if (obj instanceof Long) {
055            value = Double.valueOf(((Long) obj).doubleValue());
056        } else {
057            LOGGER.log(Level.FINE, String.format("Invalid type for Gauge %s: %s", name,
058                    obj.getClass().getName()));
059            return new ArrayList<MetricFamilySamples>();
060        }
061        MetricFamilySamples.Sample sample = new MetricFamilySamples.Sample(name,
062                new ArrayList<String>(), new ArrayList<String>(), value);
063        return Arrays.asList(new MetricFamilySamples(name, Type.GAUGE, getHelpMessage(name, gauge), Arrays.asList(sample)));
064    }
065
066    /**
067     * Export a histogram snapshot as a prometheus SUMMARY.
068     *
069     * @param name metric name.
070     * @param snapshot the histogram snapshot.
071     * @param count the total sample count for this snapshot.
072     * @param factor a factor to apply to histogram values.
073     *
074     */
075    List<MetricFamilySamples> fromSnapshotAndCount(String name, Snapshot snapshot, long count, double factor, String helpMessage) {
076        long sum = 0;
077        for (long i : snapshot.getValues()) {
078            sum += i;
079        }
080        List<MetricFamilySamples.Sample> samples = Arrays.asList(
081                new MetricFamilySamples.Sample(name, Arrays.asList("quantile"), Arrays.asList("0.5"), snapshot.getMedian() * factor),
082                new MetricFamilySamples.Sample(name, Arrays.asList("quantile"), Arrays.asList("0.75"), snapshot.get75thPercentile() * factor),
083                new MetricFamilySamples.Sample(name, Arrays.asList("quantile"), Arrays.asList("0.95"), snapshot.get95thPercentile() * factor),
084                new MetricFamilySamples.Sample(name, Arrays.asList("quantile"), Arrays.asList("0.98"), snapshot.get98thPercentile() * factor),
085                new MetricFamilySamples.Sample(name, Arrays.asList("quantile"), Arrays.asList("0.99"), snapshot.get99thPercentile() * factor),
086                new MetricFamilySamples.Sample(name, Arrays.asList("quantile"), Arrays.asList("0.999"), snapshot.get999thPercentile() * factor),
087                new MetricFamilySamples.Sample(name + "_count", new ArrayList<String>(), new ArrayList<String>(), count),
088                new MetricFamilySamples.Sample(name + "_sum", new ArrayList<String>(), new ArrayList<String>(), sum * factor)
089        );
090        return Arrays.asList(
091                new MetricFamilySamples(name, Type.SUMMARY, helpMessage, samples)
092        );
093    }
094
095    /**
096     * Convert histogram snapshot.
097     */
098    List<MetricFamilySamples> fromHistogram(String name, Histogram histogram) {
099        return fromSnapshotAndCount(name, histogram.getSnapshot(), histogram.getCount(), 1.0,
100                getHelpMessage(name, histogram));
101    }
102
103    /**
104     * Export dropwizard Timer as a histogram. Use TIME_UNIT as time unit.
105     */
106    List<MetricFamilySamples> fromTimer(String name, Timer timer) {
107        return fromSnapshotAndCount(name, timer.getSnapshot(), timer.getCount(),
108                1.0D / TimeUnit.SECONDS.toNanos(1L), getHelpMessage(name, timer));
109    }
110
111    /**
112     * Export a Meter as as prometheus COUNTER.
113     */
114    List<MetricFamilySamples> fromMeter(String name, Meter meter) {
115        return Arrays.asList(
116                new MetricFamilySamples(name + "_total", Type.COUNTER, getHelpMessage(name, meter),
117                        Arrays.asList(new MetricFamilySamples.Sample(name + "_total",
118                                new ArrayList<String>(),
119                                new ArrayList<String>(),
120                                meter.getCount())))
121
122        );
123    }
124
125    /**
126     * Replace all unsupported chars with '_'.
127     *
128     * @param name metric name.
129     * @return the sanitized metric name.
130     */
131    public static String sanitizeMetricName(String name){
132        return name.replaceAll("[^a-zA-Z0-9:_]", "_");
133    }
134
135    @Override
136    public List<MetricFamilySamples> collect() {
137        ArrayList<MetricFamilySamples> mfSamples = new ArrayList<MetricFamilySamples>();
138        for (SortedMap.Entry<String, Gauge> entry : registry.getGauges().entrySet()) {
139            mfSamples.addAll(fromGauge(sanitizeMetricName(entry.getKey()), entry.getValue()));
140        }
141        for (SortedMap.Entry<String, Counter> entry : registry.getCounters().entrySet()) {
142            mfSamples.addAll(fromCounter(sanitizeMetricName(entry.getKey()), entry.getValue()));
143        }
144        for (SortedMap.Entry<String, Histogram> entry : registry.getHistograms().entrySet()) {
145            mfSamples.addAll(fromHistogram(sanitizeMetricName(entry.getKey()), entry.getValue()));
146        }
147        for (SortedMap.Entry<String, Timer> entry : registry.getTimers().entrySet()) {
148            mfSamples.addAll(fromTimer(sanitizeMetricName(entry.getKey()), entry.getValue()));
149        }
150        for (SortedMap.Entry<String, Meter> entry : registry.getMeters().entrySet()) {
151            mfSamples.addAll(fromMeter(sanitizeMetricName(entry.getKey()), entry.getValue()));
152        }
153        return mfSamples;
154    }
155}