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