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