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}