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