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