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