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