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