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