001package io.prometheus.metrics.expositionformats.internal;
002
003import static io.prometheus.metrics.expositionformats.internal.ProtobufUtil.timestampFromMillis;
004import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName;
005
006import com.google.protobuf.TextFormat;
007import io.prometheus.metrics.config.EscapingScheme;
008import io.prometheus.metrics.expositionformats.ExpositionFormatWriter;
009import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_32_0.Metrics;
010import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets;
011import io.prometheus.metrics.model.snapshots.CounterSnapshot;
012import io.prometheus.metrics.model.snapshots.CounterSnapshot.CounterDataPointSnapshot;
013import io.prometheus.metrics.model.snapshots.DataPointSnapshot;
014import io.prometheus.metrics.model.snapshots.Exemplar;
015import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
016import io.prometheus.metrics.model.snapshots.HistogramSnapshot;
017import io.prometheus.metrics.model.snapshots.InfoSnapshot;
018import io.prometheus.metrics.model.snapshots.Labels;
019import io.prometheus.metrics.model.snapshots.MetricMetadata;
020import io.prometheus.metrics.model.snapshots.MetricSnapshot;
021import io.prometheus.metrics.model.snapshots.MetricSnapshots;
022import io.prometheus.metrics.model.snapshots.NativeHistogramBuckets;
023import io.prometheus.metrics.model.snapshots.Quantiles;
024import io.prometheus.metrics.model.snapshots.SnapshotEscaper;
025import io.prometheus.metrics.model.snapshots.StateSetSnapshot;
026import io.prometheus.metrics.model.snapshots.SummarySnapshot;
027import io.prometheus.metrics.model.snapshots.UnknownSnapshot;
028import java.io.IOException;
029import java.io.OutputStream;
030import javax.annotation.Nullable;
031
032public class PrometheusProtobufWriterImpl implements ExpositionFormatWriter {
033
034  @Override
035  public boolean accepts(@Nullable String acceptHeader) {
036    throw new IllegalStateException("use PrometheusProtobufWriter instead");
037  }
038
039  @Override
040  public String getContentType() {
041    throw new IllegalStateException("use PrometheusProtobufWriter instead");
042  }
043
044  @Override
045  public String toDebugString(MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) {
046    StringBuilder stringBuilder = new StringBuilder();
047    for (MetricSnapshot s : metricSnapshots) {
048      MetricSnapshot snapshot = SnapshotEscaper.escapeMetricSnapshot(s, escapingScheme);
049      if (!snapshot.getDataPoints().isEmpty()) {
050        stringBuilder.append(TextFormat.printer().printToString(convert(snapshot, escapingScheme)));
051      }
052    }
053    return stringBuilder.toString();
054  }
055
056  @Override
057  public void write(
058      OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme escapingScheme)
059      throws IOException {
060    for (MetricSnapshot s : metricSnapshots) {
061      MetricSnapshot snapshot = SnapshotEscaper.escapeMetricSnapshot(s, escapingScheme);
062      if (!snapshot.getDataPoints().isEmpty()) {
063        convert(snapshot, escapingScheme).writeDelimitedTo(out);
064      }
065    }
066  }
067
068  public Metrics.MetricFamily convert(MetricSnapshot snapshot, EscapingScheme scheme) {
069    Metrics.MetricFamily.Builder builder = Metrics.MetricFamily.newBuilder();
070    if (snapshot instanceof CounterSnapshot) {
071      for (CounterDataPointSnapshot data : ((CounterSnapshot) snapshot).getDataPoints()) {
072        builder.addMetric(convert(data, scheme));
073      }
074      setMetadataUnlessEmpty(
075          builder, snapshot.getMetadata(), "_total", Metrics.MetricType.COUNTER, scheme);
076    } else if (snapshot instanceof GaugeSnapshot) {
077      for (GaugeSnapshot.GaugeDataPointSnapshot data : ((GaugeSnapshot) snapshot).getDataPoints()) {
078        builder.addMetric(convert(data, scheme));
079      }
080      setMetadataUnlessEmpty(
081          builder, snapshot.getMetadata(), null, Metrics.MetricType.GAUGE, scheme);
082    } else if (snapshot instanceof HistogramSnapshot) {
083      HistogramSnapshot histogram = (HistogramSnapshot) snapshot;
084      for (HistogramSnapshot.HistogramDataPointSnapshot data : histogram.getDataPoints()) {
085        builder.addMetric(convert(data, scheme));
086      }
087      Metrics.MetricType type =
088          histogram.isGaugeHistogram()
089              ? Metrics.MetricType.GAUGE_HISTOGRAM
090              : Metrics.MetricType.HISTOGRAM;
091      setMetadataUnlessEmpty(builder, snapshot.getMetadata(), null, type, scheme);
092    } else if (snapshot instanceof SummarySnapshot) {
093      for (SummarySnapshot.SummaryDataPointSnapshot data :
094          ((SummarySnapshot) snapshot).getDataPoints()) {
095        if (data.hasCount() || data.hasSum() || data.getQuantiles().size() > 0) {
096          builder.addMetric(convert(data, scheme));
097        }
098      }
099      setMetadataUnlessEmpty(
100          builder, snapshot.getMetadata(), null, Metrics.MetricType.SUMMARY, scheme);
101    } else if (snapshot instanceof InfoSnapshot) {
102      for (InfoSnapshot.InfoDataPointSnapshot data : ((InfoSnapshot) snapshot).getDataPoints()) {
103        builder.addMetric(convert(data, scheme));
104      }
105      setMetadataUnlessEmpty(
106          builder, snapshot.getMetadata(), "_info", Metrics.MetricType.GAUGE, scheme);
107    } else if (snapshot instanceof StateSetSnapshot) {
108      for (StateSetSnapshot.StateSetDataPointSnapshot data :
109          ((StateSetSnapshot) snapshot).getDataPoints()) {
110        for (int i = 0; i < data.size(); i++) {
111          builder.addMetric(convert(data, snapshot.getMetadata().getPrometheusName(), i, scheme));
112        }
113      }
114      setMetadataUnlessEmpty(
115          builder, snapshot.getMetadata(), null, Metrics.MetricType.GAUGE, scheme);
116    } else if (snapshot instanceof UnknownSnapshot) {
117      for (UnknownSnapshot.UnknownDataPointSnapshot data :
118          ((UnknownSnapshot) snapshot).getDataPoints()) {
119        builder.addMetric(convert(data, scheme));
120      }
121      setMetadataUnlessEmpty(
122          builder, snapshot.getMetadata(), null, Metrics.MetricType.UNTYPED, scheme);
123    }
124    return builder.build();
125  }
126
127  private Metrics.Metric.Builder convert(CounterDataPointSnapshot data, EscapingScheme scheme) {
128    Metrics.Counter.Builder counterBuilder = Metrics.Counter.newBuilder();
129    counterBuilder.setValue(data.getValue());
130    if (data.getExemplar() != null) {
131      counterBuilder.setExemplar(convert(data.getExemplar(), scheme));
132    }
133    Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder();
134    addLabels(metricBuilder, data.getLabels(), scheme);
135    metricBuilder.setCounter(counterBuilder.build());
136    setScrapeTimestamp(metricBuilder, data);
137    return metricBuilder;
138  }
139
140  private Metrics.Metric.Builder convert(
141      GaugeSnapshot.GaugeDataPointSnapshot data, EscapingScheme scheme) {
142    Metrics.Gauge.Builder gaugeBuilder = Metrics.Gauge.newBuilder();
143    gaugeBuilder.setValue(data.getValue());
144    Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder();
145    addLabels(metricBuilder, data.getLabels(), scheme);
146    metricBuilder.setGauge(gaugeBuilder);
147    setScrapeTimestamp(metricBuilder, data);
148    return metricBuilder;
149  }
150
151  private Metrics.Metric.Builder convert(
152      HistogramSnapshot.HistogramDataPointSnapshot data, EscapingScheme scheme) {
153    Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder();
154    Metrics.Histogram.Builder histogramBuilder = Metrics.Histogram.newBuilder();
155    if (data.hasNativeHistogramData()) {
156      histogramBuilder.setSchema(data.getNativeSchema());
157      histogramBuilder.setZeroCount(data.getNativeZeroCount());
158      histogramBuilder.setZeroThreshold(data.getNativeZeroThreshold());
159      addBuckets(histogramBuilder, data.getNativeBucketsForPositiveValues(), +1);
160      addBuckets(histogramBuilder, data.getNativeBucketsForNegativeValues(), -1);
161
162      if (!data.hasClassicHistogramData()) { // native only
163        // Add a single +Inf bucket for the exemplar.
164        Exemplar exemplar = data.getExemplars().getLatest();
165        if (exemplar != null) {
166          Metrics.Bucket.Builder bucketBuilder =
167              Metrics.Bucket.newBuilder()
168                  .setCumulativeCount(getNativeCount(data))
169                  .setUpperBound(Double.POSITIVE_INFINITY);
170          bucketBuilder.setExemplar(convert(exemplar, scheme));
171          histogramBuilder.addBucket(bucketBuilder);
172        }
173      }
174    }
175    if (data.hasClassicHistogramData()) {
176
177      ClassicHistogramBuckets buckets = data.getClassicBuckets();
178      double lowerBound = Double.NEGATIVE_INFINITY;
179      long cumulativeCount = 0;
180      for (int i = 0; i < buckets.size(); i++) {
181        cumulativeCount += buckets.getCount(i);
182        double upperBound = buckets.getUpperBound(i);
183        Metrics.Bucket.Builder bucketBuilder =
184            Metrics.Bucket.newBuilder()
185                .setCumulativeCount(cumulativeCount)
186                .setUpperBound(upperBound);
187        Exemplar exemplar = data.getExemplars().get(lowerBound, upperBound);
188        if (exemplar != null) {
189          bucketBuilder.setExemplar(convert(exemplar, scheme));
190        }
191        histogramBuilder.addBucket(bucketBuilder);
192        lowerBound = upperBound;
193      }
194    }
195    addLabels(metricBuilder, data.getLabels(), scheme);
196    setScrapeTimestamp(metricBuilder, data);
197    if (data.hasCount()) {
198      histogramBuilder.setSampleCount(data.getCount());
199    }
200    if (data.hasSum()) {
201      histogramBuilder.setSampleSum(data.getSum());
202    }
203    metricBuilder.setHistogram(histogramBuilder.build());
204    return metricBuilder;
205  }
206
207  private Metrics.Metric.Builder convert(
208      SummarySnapshot.SummaryDataPointSnapshot data, EscapingScheme scheme) {
209    Metrics.Summary.Builder summaryBuilder = Metrics.Summary.newBuilder();
210    if (data.hasCount()) {
211      summaryBuilder.setSampleCount(data.getCount());
212    }
213    if (data.hasSum()) {
214      summaryBuilder.setSampleSum(data.getSum());
215    }
216    Quantiles quantiles = data.getQuantiles();
217    for (int i = 0; i < quantiles.size(); i++) {
218      summaryBuilder.addQuantile(
219          Metrics.Quantile.newBuilder()
220              .setQuantile(quantiles.get(i).getQuantile())
221              .setValue(quantiles.get(i).getValue())
222              .build());
223    }
224    Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder();
225    addLabels(metricBuilder, data.getLabels(), scheme);
226    metricBuilder.setSummary(summaryBuilder.build());
227    setScrapeTimestamp(metricBuilder, data);
228    return metricBuilder;
229  }
230
231  private Metrics.Metric.Builder convert(
232      InfoSnapshot.InfoDataPointSnapshot data, EscapingScheme scheme) {
233    Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder();
234    Metrics.Gauge.Builder gaugeBuilder = Metrics.Gauge.newBuilder();
235    gaugeBuilder.setValue(1);
236    addLabels(metricBuilder, data.getLabels(), scheme);
237    metricBuilder.setGauge(gaugeBuilder);
238    setScrapeTimestamp(metricBuilder, data);
239    return metricBuilder;
240  }
241
242  private Metrics.Metric.Builder convert(
243      StateSetSnapshot.StateSetDataPointSnapshot data, String name, int i, EscapingScheme scheme) {
244    Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder();
245    Metrics.Gauge.Builder gaugeBuilder = Metrics.Gauge.newBuilder();
246    addLabels(metricBuilder, data.getLabels(), scheme);
247    metricBuilder.addLabel(
248        Metrics.LabelPair.newBuilder().setName(name).setValue(data.getName(i)).build());
249    if (data.isTrue(i)) {
250      gaugeBuilder.setValue(1);
251    } else {
252      gaugeBuilder.setValue(0);
253    }
254    metricBuilder.setGauge(gaugeBuilder);
255    setScrapeTimestamp(metricBuilder, data);
256    return metricBuilder;
257  }
258
259  private Metrics.Metric.Builder convert(
260      UnknownSnapshot.UnknownDataPointSnapshot data, EscapingScheme scheme) {
261    Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder();
262    Metrics.Untyped.Builder untypedBuilder = Metrics.Untyped.newBuilder();
263    untypedBuilder.setValue(data.getValue());
264    addLabels(metricBuilder, data.getLabels(), scheme);
265    metricBuilder.setUntyped(untypedBuilder);
266    return metricBuilder;
267  }
268
269  private Metrics.Exemplar.Builder convert(Exemplar exemplar, EscapingScheme scheme) {
270    Metrics.Exemplar.Builder builder = Metrics.Exemplar.newBuilder();
271    builder.setValue(exemplar.getValue());
272    addLabels(builder, exemplar.getLabels(), scheme);
273    if (exemplar.hasTimestamp()) {
274      builder.setTimestamp(timestampFromMillis(exemplar.getTimestampMillis()));
275    }
276    return builder;
277  }
278
279  private void setMetadataUnlessEmpty(
280      Metrics.MetricFamily.Builder builder,
281      MetricMetadata metadata,
282      @Nullable String nameSuffix,
283      Metrics.MetricType type,
284      EscapingScheme scheme) {
285    if (builder.getMetricCount() == 0) {
286      return;
287    }
288    if (nameSuffix == null) {
289      builder.setName(SnapshotEscaper.getMetadataName(metadata, scheme));
290    } else {
291      builder.setName(SnapshotEscaper.getMetadataName(metadata, scheme) + nameSuffix);
292    }
293    if (metadata.getHelp() != null) {
294      builder.setHelp(metadata.getHelp());
295    }
296    builder.setType(type);
297  }
298
299  private long getNativeCount(HistogramSnapshot.HistogramDataPointSnapshot data) {
300    if (data.hasCount()) {
301      return data.getCount();
302    } else {
303      long count = data.getNativeZeroCount();
304      for (int i = 0; i < data.getNativeBucketsForPositiveValues().size(); i++) {
305        count += data.getNativeBucketsForPositiveValues().getCount(i);
306      }
307      for (int i = 0; i < data.getNativeBucketsForNegativeValues().size(); i++) {
308        count += data.getNativeBucketsForNegativeValues().getCount(i);
309      }
310      return count;
311    }
312  }
313
314  private void addBuckets(
315      Metrics.Histogram.Builder histogramBuilder, NativeHistogramBuckets buckets, int sgn) {
316    if (buckets.size() > 0) {
317      Metrics.BucketSpan.Builder currentSpan = Metrics.BucketSpan.newBuilder();
318      currentSpan.setOffset(buckets.getBucketIndex(0));
319      currentSpan.setLength(0);
320      int previousIndex = currentSpan.getOffset();
321      long previousCount = 0;
322      for (int i = 0; i < buckets.size(); i++) {
323        if (buckets.getBucketIndex(i) > previousIndex + 1) {
324          // If the gap between bucketIndex and previousIndex is just 1 or 2,
325          // we don't start a new span but continue the existing span and add 1 or 2 empty buckets.
326          if (buckets.getBucketIndex(i) <= previousIndex + 3) {
327            while (buckets.getBucketIndex(i) > previousIndex + 1) {
328              currentSpan.setLength(currentSpan.getLength() + 1);
329              previousIndex++;
330              if (sgn > 0) {
331                histogramBuilder.addPositiveDelta(-previousCount);
332              } else {
333                histogramBuilder.addNegativeDelta(-previousCount);
334              }
335              previousCount = 0;
336            }
337          } else {
338            if (sgn > 0) {
339              histogramBuilder.addPositiveSpan(currentSpan.build());
340            } else {
341              histogramBuilder.addNegativeSpan(currentSpan.build());
342            }
343            currentSpan = Metrics.BucketSpan.newBuilder();
344            currentSpan.setOffset(buckets.getBucketIndex(i) - (previousIndex + 1));
345          }
346        }
347        currentSpan.setLength(currentSpan.getLength() + 1);
348        previousIndex = buckets.getBucketIndex(i);
349        if (sgn > 0) {
350          histogramBuilder.addPositiveDelta(buckets.getCount(i) - previousCount);
351        } else {
352          histogramBuilder.addNegativeDelta(buckets.getCount(i) - previousCount);
353        }
354        previousCount = buckets.getCount(i);
355      }
356      if (sgn > 0) {
357        histogramBuilder.addPositiveSpan(currentSpan.build());
358      } else {
359        histogramBuilder.addNegativeSpan(currentSpan.build());
360      }
361    }
362  }
363
364  private void addLabels(
365      Metrics.Metric.Builder metricBuilder, Labels labels, EscapingScheme scheme) {
366    for (int i = 0; i < labels.size(); i++) {
367      metricBuilder.addLabel(
368          Metrics.LabelPair.newBuilder()
369              .setName(getSnapshotLabelName(labels, i, scheme))
370              .setValue(labels.getValue(i))
371              .build());
372    }
373  }
374
375  private void addLabels(
376      Metrics.Exemplar.Builder metricBuilder, Labels labels, EscapingScheme scheme) {
377    for (int i = 0; i < labels.size(); i++) {
378      metricBuilder.addLabel(
379          Metrics.LabelPair.newBuilder()
380              .setName(getSnapshotLabelName(labels, i, scheme))
381              .setValue(labels.getValue(i))
382              .build());
383    }
384  }
385
386  private void setScrapeTimestamp(Metrics.Metric.Builder metricBuilder, DataPointSnapshot data) {
387    if (data.hasScrapeTimestamp()) {
388      metricBuilder.setTimestampMs(data.getScrapeTimestampMillis());
389    }
390  }
391}