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_1.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    if (data.hasCreatedTimestamp()) {
134      counterBuilder.setCreatedTimestamp(
135          ProtobufUtil.timestampFromMillis(data.getCreatedTimestampMillis()));
136    }
137    Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder();
138    addLabels(metricBuilder, data.getLabels(), scheme);
139    metricBuilder.setCounter(counterBuilder.build());
140    setScrapeTimestamp(metricBuilder, data);
141    return metricBuilder;
142  }
143
144  private Metrics.Metric.Builder convert(
145      GaugeSnapshot.GaugeDataPointSnapshot data, EscapingScheme scheme) {
146    Metrics.Gauge.Builder gaugeBuilder = Metrics.Gauge.newBuilder();
147    gaugeBuilder.setValue(data.getValue());
148    Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder();
149    addLabels(metricBuilder, data.getLabels(), scheme);
150    metricBuilder.setGauge(gaugeBuilder);
151    setScrapeTimestamp(metricBuilder, data);
152    return metricBuilder;
153  }
154
155  private Metrics.Metric.Builder convert(
156      HistogramSnapshot.HistogramDataPointSnapshot data, EscapingScheme scheme) {
157    Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder();
158    Metrics.Histogram.Builder histogramBuilder = Metrics.Histogram.newBuilder();
159    if (data.hasNativeHistogramData()) {
160      histogramBuilder.setSchema(data.getNativeSchema());
161      histogramBuilder.setZeroCount(data.getNativeZeroCount());
162      histogramBuilder.setZeroThreshold(data.getNativeZeroThreshold());
163      addBuckets(histogramBuilder, data.getNativeBucketsForPositiveValues(), +1);
164      addBuckets(histogramBuilder, data.getNativeBucketsForNegativeValues(), -1);
165
166      if (!data.hasClassicHistogramData()) { // native only
167        // Add a single +Inf bucket for the exemplar.
168        Exemplar exemplar = data.getExemplars().getLatest();
169        if (exemplar != null) {
170          Metrics.Bucket.Builder bucketBuilder =
171              Metrics.Bucket.newBuilder()
172                  .setCumulativeCount(getNativeCount(data))
173                  .setUpperBound(Double.POSITIVE_INFINITY);
174          bucketBuilder.setExemplar(convert(exemplar, scheme));
175          histogramBuilder.addBucket(bucketBuilder);
176        }
177      }
178    }
179    if (data.hasClassicHistogramData()) {
180
181      ClassicHistogramBuckets buckets = data.getClassicBuckets();
182      double lowerBound = Double.NEGATIVE_INFINITY;
183      long cumulativeCount = 0;
184      for (int i = 0; i < buckets.size(); i++) {
185        cumulativeCount += buckets.getCount(i);
186        double upperBound = buckets.getUpperBound(i);
187        Metrics.Bucket.Builder bucketBuilder =
188            Metrics.Bucket.newBuilder()
189                .setCumulativeCount(cumulativeCount)
190                .setUpperBound(upperBound);
191        Exemplar exemplar = data.getExemplars().get(lowerBound, upperBound);
192        if (exemplar != null) {
193          bucketBuilder.setExemplar(convert(exemplar, scheme));
194        }
195        histogramBuilder.addBucket(bucketBuilder);
196        lowerBound = upperBound;
197      }
198    }
199    addLabels(metricBuilder, data.getLabels(), scheme);
200    setScrapeTimestamp(metricBuilder, data);
201    if (data.hasCount()) {
202      histogramBuilder.setSampleCount(data.getCount());
203    }
204    if (data.hasSum()) {
205      histogramBuilder.setSampleSum(data.getSum());
206    }
207    metricBuilder.setHistogram(histogramBuilder.build());
208    return metricBuilder;
209  }
210
211  private Metrics.Metric.Builder convert(
212      SummarySnapshot.SummaryDataPointSnapshot data, EscapingScheme scheme) {
213    Metrics.Summary.Builder summaryBuilder = Metrics.Summary.newBuilder();
214    if (data.hasCount()) {
215      summaryBuilder.setSampleCount(data.getCount());
216    }
217    if (data.hasSum()) {
218      summaryBuilder.setSampleSum(data.getSum());
219    }
220    Quantiles quantiles = data.getQuantiles();
221    for (int i = 0; i < quantiles.size(); i++) {
222      summaryBuilder.addQuantile(
223          Metrics.Quantile.newBuilder()
224              .setQuantile(quantiles.get(i).getQuantile())
225              .setValue(quantiles.get(i).getValue())
226              .build());
227    }
228    Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder();
229    addLabels(metricBuilder, data.getLabels(), scheme);
230    metricBuilder.setSummary(summaryBuilder.build());
231    setScrapeTimestamp(metricBuilder, data);
232    return metricBuilder;
233  }
234
235  private Metrics.Metric.Builder convert(
236      InfoSnapshot.InfoDataPointSnapshot data, EscapingScheme scheme) {
237    Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder();
238    Metrics.Gauge.Builder gaugeBuilder = Metrics.Gauge.newBuilder();
239    gaugeBuilder.setValue(1);
240    addLabels(metricBuilder, data.getLabels(), scheme);
241    metricBuilder.setGauge(gaugeBuilder);
242    setScrapeTimestamp(metricBuilder, data);
243    return metricBuilder;
244  }
245
246  private Metrics.Metric.Builder convert(
247      StateSetSnapshot.StateSetDataPointSnapshot data, String name, int i, EscapingScheme scheme) {
248    Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder();
249    Metrics.Gauge.Builder gaugeBuilder = Metrics.Gauge.newBuilder();
250    addLabels(metricBuilder, data.getLabels(), scheme);
251    metricBuilder.addLabel(
252        Metrics.LabelPair.newBuilder().setName(name).setValue(data.getName(i)).build());
253    if (data.isTrue(i)) {
254      gaugeBuilder.setValue(1);
255    } else {
256      gaugeBuilder.setValue(0);
257    }
258    metricBuilder.setGauge(gaugeBuilder);
259    setScrapeTimestamp(metricBuilder, data);
260    return metricBuilder;
261  }
262
263  private Metrics.Metric.Builder convert(
264      UnknownSnapshot.UnknownDataPointSnapshot data, EscapingScheme scheme) {
265    Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder();
266    Metrics.Untyped.Builder untypedBuilder = Metrics.Untyped.newBuilder();
267    untypedBuilder.setValue(data.getValue());
268    addLabels(metricBuilder, data.getLabels(), scheme);
269    metricBuilder.setUntyped(untypedBuilder);
270    return metricBuilder;
271  }
272
273  private Metrics.Exemplar.Builder convert(Exemplar exemplar, EscapingScheme scheme) {
274    Metrics.Exemplar.Builder builder = Metrics.Exemplar.newBuilder();
275    builder.setValue(exemplar.getValue());
276    addLabels(builder, exemplar.getLabels(), scheme);
277    if (exemplar.hasTimestamp()) {
278      builder.setTimestamp(timestampFromMillis(exemplar.getTimestampMillis()));
279    }
280    return builder;
281  }
282
283  private void setMetadataUnlessEmpty(
284      Metrics.MetricFamily.Builder builder,
285      MetricMetadata metadata,
286      @Nullable String nameSuffix,
287      Metrics.MetricType type,
288      EscapingScheme scheme) {
289    if (builder.getMetricCount() == 0) {
290      return;
291    }
292    if (nameSuffix == null) {
293      builder.setName(SnapshotEscaper.getMetadataName(metadata, scheme));
294    } else {
295      builder.setName(SnapshotEscaper.getMetadataName(metadata, scheme) + nameSuffix);
296    }
297    if (metadata.getHelp() != null) {
298      builder.setHelp(metadata.getHelp());
299    }
300    builder.setType(type);
301  }
302
303  private long getNativeCount(HistogramSnapshot.HistogramDataPointSnapshot data) {
304    if (data.hasCount()) {
305      return data.getCount();
306    } else {
307      long count = data.getNativeZeroCount();
308      for (int i = 0; i < data.getNativeBucketsForPositiveValues().size(); i++) {
309        count += data.getNativeBucketsForPositiveValues().getCount(i);
310      }
311      for (int i = 0; i < data.getNativeBucketsForNegativeValues().size(); i++) {
312        count += data.getNativeBucketsForNegativeValues().getCount(i);
313      }
314      return count;
315    }
316  }
317
318  private void addBuckets(
319      Metrics.Histogram.Builder histogramBuilder, NativeHistogramBuckets buckets, int sgn) {
320    if (buckets.size() > 0) {
321      Metrics.BucketSpan.Builder currentSpan = Metrics.BucketSpan.newBuilder();
322      currentSpan.setOffset(buckets.getBucketIndex(0));
323      currentSpan.setLength(0);
324      int previousIndex = currentSpan.getOffset();
325      long previousCount = 0;
326      for (int i = 0; i < buckets.size(); i++) {
327        if (buckets.getBucketIndex(i) > previousIndex + 1) {
328          // If the gap between bucketIndex and previousIndex is just 1 or 2,
329          // we don't start a new span but continue the existing span and add 1 or 2 empty buckets.
330          if (buckets.getBucketIndex(i) <= previousIndex + 3) {
331            while (buckets.getBucketIndex(i) > previousIndex + 1) {
332              currentSpan.setLength(currentSpan.getLength() + 1);
333              previousIndex++;
334              if (sgn > 0) {
335                histogramBuilder.addPositiveDelta(-previousCount);
336              } else {
337                histogramBuilder.addNegativeDelta(-previousCount);
338              }
339              previousCount = 0;
340            }
341          } else {
342            if (sgn > 0) {
343              histogramBuilder.addPositiveSpan(currentSpan.build());
344            } else {
345              histogramBuilder.addNegativeSpan(currentSpan.build());
346            }
347            currentSpan = Metrics.BucketSpan.newBuilder();
348            currentSpan.setOffset(buckets.getBucketIndex(i) - (previousIndex + 1));
349          }
350        }
351        currentSpan.setLength(currentSpan.getLength() + 1);
352        previousIndex = buckets.getBucketIndex(i);
353        if (sgn > 0) {
354          histogramBuilder.addPositiveDelta(buckets.getCount(i) - previousCount);
355        } else {
356          histogramBuilder.addNegativeDelta(buckets.getCount(i) - previousCount);
357        }
358        previousCount = buckets.getCount(i);
359      }
360      if (sgn > 0) {
361        histogramBuilder.addPositiveSpan(currentSpan.build());
362      } else {
363        histogramBuilder.addNegativeSpan(currentSpan.build());
364      }
365    }
366  }
367
368  private void addLabels(
369      Metrics.Metric.Builder metricBuilder, Labels labels, EscapingScheme scheme) {
370    for (int i = 0; i < labels.size(); i++) {
371      metricBuilder.addLabel(
372          Metrics.LabelPair.newBuilder()
373              .setName(getSnapshotLabelName(labels, i, scheme))
374              .setValue(labels.getValue(i))
375              .build());
376    }
377  }
378
379  private void addLabels(
380      Metrics.Exemplar.Builder metricBuilder, Labels labels, EscapingScheme scheme) {
381    for (int i = 0; i < labels.size(); i++) {
382      metricBuilder.addLabel(
383          Metrics.LabelPair.newBuilder()
384              .setName(getSnapshotLabelName(labels, i, scheme))
385              .setValue(labels.getValue(i))
386              .build());
387    }
388  }
389
390  private void setScrapeTimestamp(Metrics.Metric.Builder metricBuilder, DataPointSnapshot data) {
391    if (data.hasScrapeTimestamp()) {
392      metricBuilder.setTimestampMs(data.getScrapeTimestampMillis());
393    }
394  }
395}