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.concurrent.TimeUnit;
027import java.util.function.BiFunction;
028import java.util.logging.Level;
029import java.util.logging.Logger;
030import javax.annotation.Nullable;
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  @Nullable private final 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 = null;
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 = null;
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, @Nullable CustomLabelMapper labelMapper) {
073    this.registry = registry;
074    this.metricFilter = metricFilter;
075    this.labelMapper = 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      @Nullable CustomLabelMapper labelMapper,
088      InvalidMetricHandler invalidMetricHandler) {
089    this.registry = registry;
090    this.metricFilter = metricFilter;
091    this.labelMapper = 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 != null ? labelMapper.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    if (labelMapper != null) {
117      dataPointBuilder.labels(
118          labelMapper.getLabels(dropwizardName, Collections.emptyList(), Collections.emptyList()));
119    }
120    return new CounterSnapshot(metadata, Collections.singletonList(dataPointBuilder.build()));
121  }
122
123  /** Export gauge as a prometheus gauge. */
124  @Nullable
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    if (labelMapper != null) {
145      dataPointBuilder.labels(
146          labelMapper.getLabels(dropwizardName, Collections.emptyList(), Collections.emptyList()));
147    }
148    return new GaugeSnapshot(metadata, Collections.singletonList(dataPointBuilder.build()));
149  }
150
151  /**
152   * Export a histogram snapshot as a prometheus SUMMARY.
153   *
154   * @param dropwizardName metric name.
155   * @param snapshot the histogram snapshot.
156   * @param count the total sample count for this snapshot.
157   * @param factor a factor to apply to histogram values.
158   */
159  MetricSnapshot fromSnapshotAndCount(
160      String dropwizardName, Snapshot snapshot, long count, double factor, String helpMessage) {
161    Quantiles quantiles =
162        Quantiles.builder()
163            .quantile(0.5, snapshot.getMedian() * factor)
164            .quantile(0.75, snapshot.get75thPercentile() * factor)
165            .quantile(0.95, snapshot.get95thPercentile() * factor)
166            .quantile(0.98, snapshot.get98thPercentile() * factor)
167            .quantile(0.99, snapshot.get99thPercentile() * factor)
168            .quantile(0.999, snapshot.get999thPercentile() * factor)
169            .build();
170
171    String name = labelMapper != null ? labelMapper.getName(dropwizardName) : dropwizardName;
172    MetricMetadata metadata =
173        new MetricMetadata(PrometheusNaming.sanitizeMetricName(name), helpMessage);
174    SummarySnapshot.SummaryDataPointSnapshot.Builder dataPointBuilder =
175        SummarySnapshot.SummaryDataPointSnapshot.builder().quantiles(quantiles).count(count);
176    if (labelMapper != null) {
177      dataPointBuilder.labels(
178          labelMapper.getLabels(dropwizardName, Collections.emptyList(), Collections.emptyList()));
179    }
180    return new SummarySnapshot(metadata, Collections.singletonList(dataPointBuilder.build()));
181  }
182
183  /** Convert histogram snapshot. */
184  MetricSnapshot fromHistogram(String dropwizardName, Histogram histogram) {
185    return fromSnapshotAndCount(
186        dropwizardName,
187        histogram.getSnapshot(),
188        histogram.getCount(),
189        1.0,
190        getHelpMessage(dropwizardName, histogram));
191  }
192
193  /** Export Dropwizard Timer as a histogram. Use TIME_UNIT as time unit. */
194  MetricSnapshot fromTimer(String dropwizardName, Timer timer) {
195    return fromSnapshotAndCount(
196        dropwizardName,
197        timer.getSnapshot(),
198        timer.getCount(),
199        1.0D / TimeUnit.SECONDS.toNanos(1L),
200        getHelpMessage(dropwizardName, timer));
201  }
202
203  /** Export a Meter as a prometheus COUNTER. */
204  MetricSnapshot fromMeter(String dropwizardName, Meter meter) {
205    MetricMetadata metadata = getMetricMetaData(dropwizardName + "_total", meter);
206    CounterSnapshot.CounterDataPointSnapshot.Builder dataPointBuilder =
207        CounterSnapshot.CounterDataPointSnapshot.builder().value(meter.getCount());
208    if (labelMapper != null) {
209      dataPointBuilder.labels(
210          labelMapper.getLabels(dropwizardName, Collections.emptyList(), Collections.emptyList()));
211    }
212    return new CounterSnapshot(metadata, Collections.singletonList(dataPointBuilder.build()));
213  }
214
215  @Override
216  public MetricSnapshots collect() {
217    MetricSnapshots.Builder metricSnapshots = MetricSnapshots.builder();
218    collectMetricKind(metricSnapshots, registry.getGauges(metricFilter), this::fromGauge);
219    collectMetricKind(metricSnapshots, registry.getCounters(metricFilter), this::fromCounter);
220    collectMetricKind(metricSnapshots, registry.getHistograms(metricFilter), this::fromHistogram);
221    collectMetricKind(metricSnapshots, registry.getTimers(metricFilter), this::fromTimer);
222    collectMetricKind(metricSnapshots, registry.getMeters(metricFilter), this::fromMeter);
223    return metricSnapshots.build();
224  }
225
226  private <T> void collectMetricKind(
227      MetricSnapshots.Builder builder,
228      Map<MetricName, T> metric,
229      BiFunction<String, T, MetricSnapshot> toSnapshot) {
230    for (Map.Entry<MetricName, T> entry : metric.entrySet()) {
231      String metricName = entry.getKey().getKey();
232      try {
233        MetricSnapshot snapshot = toSnapshot.apply(metricName, entry.getValue());
234        if (snapshot != null) {
235          builder.metricSnapshot(snapshot);
236        }
237      } catch (Exception e) {
238        if (!invalidMetricHandler.suppressException(metricName, e)) {
239          throw e;
240        }
241      }
242    }
243  }
244
245  public static Builder builder() {
246    return new Builder();
247  }
248
249  // Builder class for DropwizardExports
250  public static class Builder {
251    @Nullable private MetricRegistry registry;
252    private MetricFilter metricFilter;
253    @Nullable private CustomLabelMapper labelMapper;
254    private InvalidMetricHandler invalidMetricHandler;
255
256    private Builder() {
257      this.metricFilter = MetricFilter.ALL;
258      this.invalidMetricHandler = InvalidMetricHandler.ALWAYS_THROW;
259    }
260
261    public Builder dropwizardRegistry(MetricRegistry registry) {
262      this.registry = registry;
263      return this;
264    }
265
266    public Builder metricFilter(MetricFilter metricFilter) {
267      this.metricFilter = metricFilter;
268      return this;
269    }
270
271    public Builder customLabelMapper(CustomLabelMapper labelMapper) {
272      this.labelMapper = labelMapper;
273      return this;
274    }
275
276    public Builder invalidMetricHandler(InvalidMetricHandler invalidMetricHandler) {
277      this.invalidMetricHandler = invalidMetricHandler;
278      return this;
279    }
280
281    DropwizardExports build() {
282      if (registry == null) {
283        throw new IllegalArgumentException("MetricRegistry must be set");
284      }
285      return new DropwizardExports(registry, metricFilter, labelMapper, invalidMetricHandler);
286    }
287
288    public void register() {
289      register(PrometheusRegistry.defaultRegistry);
290    }
291
292    public void register(PrometheusRegistry registry) {
293      DropwizardExports dropwizardExports = build();
294      registry.register(dropwizardExports);
295    }
296  }
297}