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