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