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}