001package io.prometheus.metrics.instrumentation.dropwizard5.internal;
002
003import io.prometheus.metrics.instrumentation.dropwizard5.InvalidMetricHandler;
004import io.prometheus.metrics.instrumentation.dropwizard5.labels.CustomLabelMapper;
005import io.prometheus.metrics.model.registry.MultiCollector;
006import io.prometheus.metrics.model.snapshots.CounterSnapshot;
007import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
008import io.prometheus.metrics.model.snapshots.MetricMetadata;
009import io.prometheus.metrics.model.snapshots.MetricSnapshot;
010import io.prometheus.metrics.model.snapshots.MetricSnapshots;
011import io.prometheus.metrics.model.snapshots.PrometheusNaming;
012import io.prometheus.metrics.model.snapshots.Quantiles;
013import io.prometheus.metrics.model.snapshots.SummarySnapshot;
014import java.util.Collections;
015import java.util.Map;
016import java.util.concurrent.TimeUnit;
017import java.util.function.BiFunction;
018import java.util.logging.Level;
019import java.util.logging.Logger;
020import javax.annotation.Nullable;
021
022/**
023 * Abstract base class for Dropwizard metrics exporters. Contains all the common logic for
024 * converting Dropwizard metrics to Prometheus metrics. Subclasses only need to implement {@link
025 * #collectMetricSnapshots()} to handle version-specific registry APIs.
026 *
027 * @param <R> The Dropwizard MetricRegistry type
028 * @param <F> The Dropwizard MetricFilter type
029 * @param <C> The Dropwizard Counter type
030 * @param <G> The Dropwizard Gauge type
031 * @param <H> The Dropwizard Histogram type
032 * @param <T> The Dropwizard Timer type
033 * @param <M> The Dropwizard Meter type
034 * @param <B> The Dropwizard Metric base type
035 * @param <S> The Dropwizard Snapshot type
036 */
037public abstract class AbstractDropwizardExports<R, F, C, G, H, T, M, B, S>
038    implements MultiCollector {
039
040  private static final Logger logger = Logger.getLogger(AbstractDropwizardExports.class.getName());
041
042  protected final R registry;
043  protected final F metricFilter;
044  @Nullable protected final CustomLabelMapper labelMapper;
045  protected final InvalidMetricHandler invalidMetricHandler;
046
047  protected AbstractDropwizardExports(
048      R registry,
049      F metricFilter,
050      @Nullable CustomLabelMapper labelMapper,
051      InvalidMetricHandler invalidMetricHandler) {
052    this.registry = registry;
053    this.metricFilter = metricFilter;
054    this.labelMapper = labelMapper;
055    this.invalidMetricHandler = invalidMetricHandler;
056  }
057
058  protected static String getHelpMessage(String metricName, Object metric) {
059    return String.format(
060        "Generated from Dropwizard metric import (metric=%s, type=%s)",
061        metricName, metric.getClass().getName());
062  }
063
064  protected MetricMetadata getMetricMetaData(String metricName, B metric) {
065    String name = labelMapper != null ? labelMapper.getName(metricName) : metricName;
066    return new MetricMetadata(
067        PrometheusNaming.sanitizeMetricName(name), getHelpMessage(metricName, metric));
068  }
069
070  /**
071   * Export counter as Prometheus <a
072   * href="https://prometheus.io/docs/concepts/metric_types/#gauge">Gauge</a>.
073   */
074  @SuppressWarnings("unchecked")
075  protected MetricSnapshot fromCounter(String dropwizardName, C counter) {
076    long count = getCounterCount(counter);
077    MetricMetadata metadata = getMetricMetaData(dropwizardName, (B) counter);
078    CounterSnapshot.CounterDataPointSnapshot.Builder dataPointBuilder =
079        CounterSnapshot.CounterDataPointSnapshot.builder().value(Long.valueOf(count).doubleValue());
080    if (labelMapper != null) {
081      dataPointBuilder.labels(
082          labelMapper.getLabels(dropwizardName, Collections.emptyList(), Collections.emptyList()));
083    }
084    return new CounterSnapshot(metadata, Collections.singletonList(dataPointBuilder.build()));
085  }
086
087  /** Export gauge as a prometheus gauge. */
088  @SuppressWarnings("unchecked")
089  @Nullable
090  protected MetricSnapshot fromGauge(String dropwizardName, G gauge) {
091    Object obj = getGaugeValue(gauge);
092    double value;
093    if (obj instanceof Number) {
094      value = ((Number) obj).doubleValue();
095    } else if (obj instanceof Boolean) {
096      value = ((Boolean) obj) ? 1 : 0;
097    } else {
098      logger.log(
099          Level.FINE,
100          String.format(
101              "Invalid type for Gauge %s: %s",
102              PrometheusNaming.sanitizeMetricName(dropwizardName),
103              obj == null ? "null" : obj.getClass().getName()));
104      return null;
105    }
106    MetricMetadata metadata = getMetricMetaData(dropwizardName, (B) gauge);
107    GaugeSnapshot.GaugeDataPointSnapshot.Builder dataPointBuilder =
108        GaugeSnapshot.GaugeDataPointSnapshot.builder().value(value);
109    if (labelMapper != null) {
110      dataPointBuilder.labels(
111          labelMapper.getLabels(dropwizardName, Collections.emptyList(), Collections.emptyList()));
112    }
113    return new GaugeSnapshot(metadata, Collections.singletonList(dataPointBuilder.build()));
114  }
115
116  /**
117   * Export a histogram snapshot as a prometheus SUMMARY.
118   *
119   * @param dropwizardName metric name.
120   * @param snapshot the histogram snapshot.
121   * @param count the total sample count for this snapshot.
122   * @param factor a factor to apply to histogram values.
123   */
124  protected MetricSnapshot fromSnapshotAndCount(
125      String dropwizardName, S snapshot, long count, double factor, String helpMessage) {
126    Quantiles quantiles =
127        Quantiles.builder()
128            .quantile(0.5, getMedian(snapshot) * factor)
129            .quantile(0.75, get75thPercentile(snapshot) * factor)
130            .quantile(0.95, get95thPercentile(snapshot) * factor)
131            .quantile(0.98, get98thPercentile(snapshot) * factor)
132            .quantile(0.99, get99thPercentile(snapshot) * factor)
133            .quantile(0.999, get999thPercentile(snapshot) * factor)
134            .build();
135
136    String name = labelMapper != null ? labelMapper.getName(dropwizardName) : dropwizardName;
137    MetricMetadata metadata =
138        new MetricMetadata(PrometheusNaming.sanitizeMetricName(name), helpMessage);
139    SummarySnapshot.SummaryDataPointSnapshot.Builder dataPointBuilder =
140        SummarySnapshot.SummaryDataPointSnapshot.builder().quantiles(quantiles).count(count);
141    if (labelMapper != null) {
142      dataPointBuilder.labels(
143          labelMapper.getLabels(dropwizardName, Collections.emptyList(), Collections.emptyList()));
144    }
145    return new SummarySnapshot(metadata, Collections.singletonList(dataPointBuilder.build()));
146  }
147
148  /** Convert histogram snapshot. */
149  protected MetricSnapshot fromHistogram(String dropwizardName, H histogram) {
150    S snapshot = getHistogramSnapshot(histogram);
151    long count = getHistogramCount(histogram);
152    return fromSnapshotAndCount(
153        dropwizardName, snapshot, count, 1.0, getHelpMessage(dropwizardName, histogram));
154  }
155
156  /** Export Dropwizard Timer as a histogram. Use TIME_UNIT as time unit. */
157  protected MetricSnapshot fromTimer(String dropwizardName, T timer) {
158    S snapshot = getTimerSnapshot(timer);
159    long count = getTimerCount(timer);
160    return fromSnapshotAndCount(
161        dropwizardName,
162        snapshot,
163        count,
164        1.0D / TimeUnit.SECONDS.toNanos(1L),
165        getHelpMessage(dropwizardName, timer));
166  }
167
168  /** Export a Meter as a prometheus COUNTER. */
169  @SuppressWarnings("unchecked")
170  protected MetricSnapshot fromMeter(String dropwizardName, M meter) {
171    MetricMetadata metadata = getMetricMetaData(dropwizardName + "_total", (B) meter);
172    CounterSnapshot.CounterDataPointSnapshot.Builder dataPointBuilder =
173        CounterSnapshot.CounterDataPointSnapshot.builder().value(getMeterCount(meter));
174    if (labelMapper != null) {
175      dataPointBuilder.labels(
176          labelMapper.getLabels(dropwizardName, Collections.emptyList(), Collections.emptyList()));
177    }
178    return new CounterSnapshot(metadata, Collections.singletonList(dataPointBuilder.build()));
179  }
180
181  @Override
182  public MetricSnapshots collect() {
183    return collectMetricSnapshots();
184  }
185
186  protected <K, V> void collectMetricKind(
187      MetricSnapshots.Builder builder,
188      Map<K, V> metrics,
189      BiFunction<String, V, MetricSnapshot> toSnapshot,
190      java.util.function.Function<K, String> keyExtractor) {
191    for (Map.Entry<K, V> entry : metrics.entrySet()) {
192      String metricName = keyExtractor.apply(entry.getKey());
193      try {
194        MetricSnapshot snapshot = toSnapshot.apply(metricName, entry.getValue());
195        if (snapshot != null) {
196          builder.metricSnapshot(snapshot);
197        }
198      } catch (Exception e) {
199        if (!invalidMetricHandler.suppressException(metricName, e)) {
200          throw e;
201        }
202      }
203    }
204  }
205
206  // Abstract methods to be implemented by version-specific subclasses
207
208  /** Collect all metric snapshots from the registry. */
209  protected abstract MetricSnapshots collectMetricSnapshots();
210
211  protected abstract long getCounterCount(C counter);
212
213  protected abstract Object getGaugeValue(G gauge);
214
215  protected abstract S getHistogramSnapshot(H histogram);
216
217  protected abstract long getHistogramCount(H histogram);
218
219  protected abstract S getTimerSnapshot(T timer);
220
221  protected abstract long getTimerCount(T timer);
222
223  protected abstract long getMeterCount(M meter);
224
225  protected abstract double getMedian(S snapshot);
226
227  protected abstract double get75thPercentile(S snapshot);
228
229  protected abstract double get95thPercentile(S snapshot);
230
231  protected abstract double get98thPercentile(S snapshot);
232
233  protected abstract double get99thPercentile(S snapshot);
234
235  protected abstract double get999thPercentile(S snapshot);
236}