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