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}