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