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      builder.setName(SnapshotEscaper.getMetadataName(metadata, scheme) + nameSuffix);
300    }
301    if (metadata.getHelp() != null) {
302      builder.setHelp(metadata.getHelp());
303    }
304    builder.setType(type);
305  }
306
307  private long getNativeCount(HistogramSnapshot.HistogramDataPointSnapshot data) {
308    if (data.hasCount()) {
309      return data.getCount();
310    } else {
311      long count = data.getNativeZeroCount();
312      for (int i = 0; i < data.getNativeBucketsForPositiveValues().size(); i++) {
313        count += data.getNativeBucketsForPositiveValues().getCount(i);
314      }
315      for (int i = 0; i < data.getNativeBucketsForNegativeValues().size(); i++) {
316        count += data.getNativeBucketsForNegativeValues().getCount(i);
317      }
318      return count;
319    }
320  }
321
322  private void addBuckets(
323      Metrics.Histogram.Builder histogramBuilder, NativeHistogramBuckets buckets, int sgn) {
324    if (buckets.size() > 0) {
325      Metrics.BucketSpan.Builder currentSpan = Metrics.BucketSpan.newBuilder();
326      currentSpan.setOffset(buckets.getBucketIndex(0));
327      currentSpan.setLength(0);
328      int previousIndex = currentSpan.getOffset();
329      long previousCount = 0;
330      for (int i = 0; i < buckets.size(); i++) {
331        if (buckets.getBucketIndex(i) > previousIndex + 1) {
332          // If the gap between bucketIndex and previousIndex is just 1 or 2,
333          // we don't start a new span but continue the existing span and add 1 or 2 empty buckets.
334          if (buckets.getBucketIndex(i) <= previousIndex + 3) {
335            while (buckets.getBucketIndex(i) > previousIndex + 1) {
336              currentSpan.setLength(currentSpan.getLength() + 1);
337              previousIndex++;
338              if (sgn > 0) {
339                histogramBuilder.addPositiveDelta(-previousCount);
340              } else {
341                histogramBuilder.addNegativeDelta(-previousCount);
342              }
343              previousCount = 0;
344            }
345          } else {
346            if (sgn > 0) {
347              histogramBuilder.addPositiveSpan(currentSpan.build());
348            } else {
349              histogramBuilder.addNegativeSpan(currentSpan.build());
350            }
351            currentSpan = Metrics.BucketSpan.newBuilder();
352            currentSpan.setOffset(buckets.getBucketIndex(i) - (previousIndex + 1));
353          }
354        }
355        currentSpan.setLength(currentSpan.getLength() + 1);
356        previousIndex = buckets.getBucketIndex(i);
357        if (sgn > 0) {
358          histogramBuilder.addPositiveDelta(buckets.getCount(i) - previousCount);
359        } else {
360          histogramBuilder.addNegativeDelta(buckets.getCount(i) - previousCount);
361        }
362        previousCount = buckets.getCount(i);
363      }
364      if (sgn > 0) {
365        histogramBuilder.addPositiveSpan(currentSpan.build());
366      } else {
367        histogramBuilder.addNegativeSpan(currentSpan.build());
368      }
369    }
370  }
371
372  private void addLabels(
373      Metrics.Metric.Builder metricBuilder, Labels labels, EscapingScheme scheme) {
374    for (int i = 0; i < labels.size(); i++) {
375      metricBuilder.addLabel(
376          Metrics.LabelPair.newBuilder()
377              .setName(getSnapshotLabelName(labels, i, scheme))
378              .setValue(labels.getValue(i))
379              .build());
380    }
381  }
382
383  private void addLabels(
384      Metrics.Exemplar.Builder metricBuilder, Labels labels, EscapingScheme scheme) {
385    for (int i = 0; i < labels.size(); i++) {
386      metricBuilder.addLabel(
387          Metrics.LabelPair.newBuilder()
388              .setName(getSnapshotLabelName(labels, i, scheme))
389              .setValue(labels.getValue(i))
390              .build());
391    }
392  }
393
394  private void setScrapeTimestamp(Metrics.Metric.Builder metricBuilder, DataPointSnapshot data) {
395    if (data.hasScrapeTimestamp()) {
396      metricBuilder.setTimestampMs(data.getScrapeTimestampMillis());
397    }
398  }
399}