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}