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