001package io.prometheus.metrics.instrumentation.dropwizard5; 002 003import io.dropwizard.metrics5.Counter; 004import io.dropwizard.metrics5.Gauge; 005import io.dropwizard.metrics5.Histogram; 006import io.dropwizard.metrics5.Meter; 007import io.dropwizard.metrics5.Metric; 008import io.dropwizard.metrics5.MetricFilter; 009import io.dropwizard.metrics5.MetricName; 010import io.dropwizard.metrics5.MetricRegistry; 011import io.dropwizard.metrics5.Snapshot; 012import io.dropwizard.metrics5.Timer; 013import io.prometheus.metrics.instrumentation.dropwizard5.labels.CustomLabelMapper; 014import io.prometheus.metrics.model.registry.MultiCollector; 015import io.prometheus.metrics.model.registry.PrometheusRegistry; 016import io.prometheus.metrics.model.snapshots.CounterSnapshot; 017import io.prometheus.metrics.model.snapshots.GaugeSnapshot; 018import io.prometheus.metrics.model.snapshots.MetricMetadata; 019import io.prometheus.metrics.model.snapshots.MetricSnapshot; 020import io.prometheus.metrics.model.snapshots.MetricSnapshots; 021import io.prometheus.metrics.model.snapshots.PrometheusNaming; 022import io.prometheus.metrics.model.snapshots.Quantiles; 023import io.prometheus.metrics.model.snapshots.SummarySnapshot; 024import java.util.Collections; 025import java.util.Map; 026import java.util.Optional; 027import java.util.concurrent.TimeUnit; 028import java.util.logging.Level; 029import java.util.logging.Logger; 030 031/** Collect Dropwizard metrics from a MetricRegistry. */ 032public class DropwizardExports implements MultiCollector { 033 private static final Logger logger = Logger.getLogger(DropwizardExports.class.getName()); 034 private final MetricRegistry registry; 035 private final MetricFilter metricFilter; 036 private final Optional<CustomLabelMapper> labelMapper; 037 038 /** 039 * Creates a new DropwizardExports and {@link MetricFilter#ALL}. 040 * 041 * @param registry a metric registry to export in prometheus. 042 */ 043 public DropwizardExports(MetricRegistry registry) { 044 super(); 045 this.registry = registry; 046 this.metricFilter = MetricFilter.ALL; 047 this.labelMapper = Optional.empty(); 048 } 049 050 /** 051 * Creates a new DropwizardExports with a custom {@link MetricFilter}. 052 * 053 * @param registry a metric registry to export in prometheus. 054 * @param metricFilter a custom metric filter. 055 */ 056 public DropwizardExports(MetricRegistry registry, MetricFilter metricFilter) { 057 this.registry = registry; 058 this.metricFilter = metricFilter; 059 this.labelMapper = Optional.empty(); 060 } 061 062 /** 063 * @param registry a metric registry to export in prometheus. 064 * @param metricFilter a custom metric filter. 065 * @param labelMapper a labelMapper to use to map labels. 066 */ 067 public DropwizardExports( 068 MetricRegistry registry, MetricFilter metricFilter, CustomLabelMapper labelMapper) { 069 this.registry = registry; 070 this.metricFilter = metricFilter; 071 this.labelMapper = Optional.ofNullable(labelMapper); 072 } 073 074 private static String getHelpMessage(String metricName, Metric metric) { 075 return String.format( 076 "Generated from Dropwizard metric import (metric=%s, type=%s)", 077 metricName, metric.getClass().getName()); 078 } 079 080 private MetricMetadata getMetricMetaData(String metricName, Metric metric) { 081 String name = labelMapper.isPresent() ? labelMapper.get().getName(metricName) : metricName; 082 return new MetricMetadata( 083 PrometheusNaming.sanitizeMetricName(name), getHelpMessage(metricName, metric)); 084 } 085 086 /** 087 * Export counter as Prometheus <a 088 * href="https://prometheus.io/docs/concepts/metric_types/#gauge">Gauge</a>. 089 */ 090 MetricSnapshot fromCounter(String dropwizardName, Counter counter) { 091 MetricMetadata metadata = getMetricMetaData(dropwizardName, counter); 092 CounterSnapshot.CounterDataPointSnapshot.Builder dataPointBuilder = 093 CounterSnapshot.CounterDataPointSnapshot.builder() 094 .value(Long.valueOf(counter.getCount()).doubleValue()); 095 labelMapper.ifPresent( 096 mapper -> 097 dataPointBuilder.labels( 098 mapper.getLabels( 099 dropwizardName, Collections.emptyList(), Collections.emptyList()))); 100 return new CounterSnapshot(metadata, Collections.singletonList(dataPointBuilder.build())); 101 } 102 103 /** Export gauge as a prometheus gauge. */ 104 MetricSnapshot fromGauge(String dropwizardName, Gauge<?> gauge) { 105 Object obj = gauge.getValue(); 106 double value; 107 if (obj instanceof Number) { 108 value = ((Number) obj).doubleValue(); 109 } else if (obj instanceof Boolean) { 110 value = ((Boolean) obj) ? 1 : 0; 111 } else { 112 logger.log( 113 Level.FINE, 114 String.format( 115 "Invalid type for Gauge %s: %s", 116 PrometheusNaming.sanitizeMetricName(dropwizardName), 117 obj == null ? "null" : obj.getClass().getName())); 118 return null; 119 } 120 MetricMetadata metadata = getMetricMetaData(dropwizardName, gauge); 121 GaugeSnapshot.GaugeDataPointSnapshot.Builder dataPointBuilder = 122 GaugeSnapshot.GaugeDataPointSnapshot.builder().value(value); 123 labelMapper.ifPresent( 124 mapper -> 125 dataPointBuilder.labels( 126 mapper.getLabels( 127 dropwizardName, Collections.emptyList(), Collections.emptyList()))); 128 return new GaugeSnapshot(metadata, Collections.singletonList(dataPointBuilder.build())); 129 } 130 131 /** 132 * Export a histogram snapshot as a prometheus SUMMARY. 133 * 134 * @param dropwizardName metric name. 135 * @param snapshot the histogram snapshot. 136 * @param count the total sample count for this snapshot. 137 * @param factor a factor to apply to histogram values. 138 */ 139 MetricSnapshot fromSnapshotAndCount( 140 String dropwizardName, Snapshot snapshot, long count, double factor, String helpMessage) { 141 Quantiles quantiles = 142 Quantiles.builder() 143 .quantile(0.5, snapshot.getMedian() * factor) 144 .quantile(0.75, snapshot.get75thPercentile() * factor) 145 .quantile(0.95, snapshot.get95thPercentile() * factor) 146 .quantile(0.98, snapshot.get98thPercentile() * factor) 147 .quantile(0.99, snapshot.get99thPercentile() * factor) 148 .quantile(0.999, snapshot.get999thPercentile() * factor) 149 .build(); 150 151 MetricMetadata metadata = 152 new MetricMetadata(PrometheusNaming.sanitizeMetricName(dropwizardName), helpMessage); 153 SummarySnapshot.SummaryDataPointSnapshot.Builder dataPointBuilder = 154 SummarySnapshot.SummaryDataPointSnapshot.builder().quantiles(quantiles).count(count); 155 labelMapper.ifPresent( 156 mapper -> 157 dataPointBuilder.labels( 158 mapper.getLabels( 159 dropwizardName, Collections.emptyList(), Collections.emptyList()))); 160 return new SummarySnapshot(metadata, Collections.singletonList(dataPointBuilder.build())); 161 } 162 163 /** Convert histogram snapshot. */ 164 MetricSnapshot fromHistogram(String dropwizardName, Histogram histogram) { 165 return fromSnapshotAndCount( 166 dropwizardName, 167 histogram.getSnapshot(), 168 histogram.getCount(), 169 1.0, 170 getHelpMessage(dropwizardName, histogram)); 171 } 172 173 /** Export Dropwizard Timer as a histogram. Use TIME_UNIT as time unit. */ 174 MetricSnapshot fromTimer(String dropwizardName, Timer timer) { 175 return fromSnapshotAndCount( 176 dropwizardName, 177 timer.getSnapshot(), 178 timer.getCount(), 179 1.0D / TimeUnit.SECONDS.toNanos(1L), 180 getHelpMessage(dropwizardName, timer)); 181 } 182 183 /** Export a Meter as a prometheus COUNTER. */ 184 MetricSnapshot fromMeter(String dropwizardName, Meter meter) { 185 MetricMetadata metadata = getMetricMetaData(dropwizardName + "_total", meter); 186 CounterSnapshot.CounterDataPointSnapshot.Builder dataPointBuilder = 187 CounterSnapshot.CounterDataPointSnapshot.builder().value(meter.getCount()); 188 labelMapper.ifPresent( 189 mapper -> 190 dataPointBuilder.labels( 191 mapper.getLabels( 192 dropwizardName, Collections.emptyList(), Collections.emptyList()))); 193 return new CounterSnapshot(metadata, Collections.singletonList(dataPointBuilder.build())); 194 } 195 196 @Override 197 public MetricSnapshots collect() { 198 MetricSnapshots.Builder metricSnapshots = MetricSnapshots.builder(); 199 for (@SuppressWarnings("rawtypes") 200 Map.Entry<MetricName, Gauge> entry : registry.getGauges(metricFilter).entrySet()) { 201 Optional.ofNullable(fromGauge(entry.getKey().getKey(), entry.getValue())) 202 .ifPresent(metricSnapshots::metricSnapshot); 203 } 204 for (Map.Entry<MetricName, Counter> entry : registry.getCounters(metricFilter).entrySet()) { 205 metricSnapshots.metricSnapshot(fromCounter(entry.getKey().getKey(), entry.getValue())); 206 } 207 for (Map.Entry<MetricName, Histogram> entry : registry.getHistograms(metricFilter).entrySet()) { 208 metricSnapshots.metricSnapshot(fromHistogram(entry.getKey().getKey(), entry.getValue())); 209 } 210 for (Map.Entry<MetricName, Timer> entry : registry.getTimers(metricFilter).entrySet()) { 211 metricSnapshots.metricSnapshot(fromTimer(entry.getKey().getKey(), entry.getValue())); 212 } 213 for (Map.Entry<MetricName, Meter> entry : registry.getMeters(metricFilter).entrySet()) { 214 metricSnapshots.metricSnapshot(fromMeter(entry.getKey().getKey(), entry.getValue())); 215 } 216 return metricSnapshots.build(); 217 } 218 219 public static Builder builder() { 220 return new Builder(); 221 } 222 223 // Builder class for DropwizardExports 224 public static class Builder { 225 private MetricRegistry registry; 226 private MetricFilter metricFilter; 227 private CustomLabelMapper labelMapper; 228 229 private Builder() { 230 this.metricFilter = MetricFilter.ALL; 231 } 232 233 public Builder dropwizardRegistry(MetricRegistry registry) { 234 this.registry = registry; 235 return this; 236 } 237 238 public Builder metricFilter(MetricFilter metricFilter) { 239 this.metricFilter = metricFilter; 240 return this; 241 } 242 243 public Builder customLabelMapper(CustomLabelMapper labelMapper) { 244 this.labelMapper = labelMapper; 245 return this; 246 } 247 248 DropwizardExports build() { 249 if (registry == null) { 250 throw new IllegalArgumentException("MetricRegistry must be set"); 251 } 252 if (labelMapper == null) { 253 return new DropwizardExports(registry, metricFilter); 254 } else { 255 return new DropwizardExports(registry, metricFilter, labelMapper); 256 } 257 } 258 259 public void register() { 260 register(PrometheusRegistry.defaultRegistry); 261 } 262 263 public void register(PrometheusRegistry registry) { 264 DropwizardExports dropwizardExports = build(); 265 registry.register(dropwizardExports); 266 } 267 } 268}