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