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_0.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 Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder(); 134 addLabels(metricBuilder, data.getLabels(), scheme); 135 metricBuilder.setCounter(counterBuilder.build()); 136 setScrapeTimestamp(metricBuilder, data); 137 return metricBuilder; 138 } 139 140 private Metrics.Metric.Builder convert( 141 GaugeSnapshot.GaugeDataPointSnapshot data, EscapingScheme scheme) { 142 Metrics.Gauge.Builder gaugeBuilder = Metrics.Gauge.newBuilder(); 143 gaugeBuilder.setValue(data.getValue()); 144 Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder(); 145 addLabels(metricBuilder, data.getLabels(), scheme); 146 metricBuilder.setGauge(gaugeBuilder); 147 setScrapeTimestamp(metricBuilder, data); 148 return metricBuilder; 149 } 150 151 private Metrics.Metric.Builder convert( 152 HistogramSnapshot.HistogramDataPointSnapshot data, EscapingScheme scheme) { 153 Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder(); 154 Metrics.Histogram.Builder histogramBuilder = Metrics.Histogram.newBuilder(); 155 if (data.hasNativeHistogramData()) { 156 histogramBuilder.setSchema(data.getNativeSchema()); 157 histogramBuilder.setZeroCount(data.getNativeZeroCount()); 158 histogramBuilder.setZeroThreshold(data.getNativeZeroThreshold()); 159 addBuckets(histogramBuilder, data.getNativeBucketsForPositiveValues(), +1); 160 addBuckets(histogramBuilder, data.getNativeBucketsForNegativeValues(), -1); 161 162 if (!data.hasClassicHistogramData()) { // native only 163 // Add a single +Inf bucket for the exemplar. 164 Exemplar exemplar = data.getExemplars().getLatest(); 165 if (exemplar != null) { 166 Metrics.Bucket.Builder bucketBuilder = 167 Metrics.Bucket.newBuilder() 168 .setCumulativeCount(getNativeCount(data)) 169 .setUpperBound(Double.POSITIVE_INFINITY); 170 bucketBuilder.setExemplar(convert(exemplar, scheme)); 171 histogramBuilder.addBucket(bucketBuilder); 172 } 173 } 174 } 175 if (data.hasClassicHistogramData()) { 176 177 ClassicHistogramBuckets buckets = data.getClassicBuckets(); 178 double lowerBound = Double.NEGATIVE_INFINITY; 179 long cumulativeCount = 0; 180 for (int i = 0; i < buckets.size(); i++) { 181 cumulativeCount += buckets.getCount(i); 182 double upperBound = buckets.getUpperBound(i); 183 Metrics.Bucket.Builder bucketBuilder = 184 Metrics.Bucket.newBuilder() 185 .setCumulativeCount(cumulativeCount) 186 .setUpperBound(upperBound); 187 Exemplar exemplar = data.getExemplars().get(lowerBound, upperBound); 188 if (exemplar != null) { 189 bucketBuilder.setExemplar(convert(exemplar, scheme)); 190 } 191 histogramBuilder.addBucket(bucketBuilder); 192 lowerBound = upperBound; 193 } 194 } 195 addLabels(metricBuilder, data.getLabels(), scheme); 196 setScrapeTimestamp(metricBuilder, data); 197 if (data.hasCount()) { 198 histogramBuilder.setSampleCount(data.getCount()); 199 } 200 if (data.hasSum()) { 201 histogramBuilder.setSampleSum(data.getSum()); 202 } 203 metricBuilder.setHistogram(histogramBuilder.build()); 204 return metricBuilder; 205 } 206 207 private Metrics.Metric.Builder convert( 208 SummarySnapshot.SummaryDataPointSnapshot data, EscapingScheme scheme) { 209 Metrics.Summary.Builder summaryBuilder = Metrics.Summary.newBuilder(); 210 if (data.hasCount()) { 211 summaryBuilder.setSampleCount(data.getCount()); 212 } 213 if (data.hasSum()) { 214 summaryBuilder.setSampleSum(data.getSum()); 215 } 216 Quantiles quantiles = data.getQuantiles(); 217 for (int i = 0; i < quantiles.size(); i++) { 218 summaryBuilder.addQuantile( 219 Metrics.Quantile.newBuilder() 220 .setQuantile(quantiles.get(i).getQuantile()) 221 .setValue(quantiles.get(i).getValue()) 222 .build()); 223 } 224 Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder(); 225 addLabels(metricBuilder, data.getLabels(), scheme); 226 metricBuilder.setSummary(summaryBuilder.build()); 227 setScrapeTimestamp(metricBuilder, data); 228 return metricBuilder; 229 } 230 231 private Metrics.Metric.Builder convert( 232 InfoSnapshot.InfoDataPointSnapshot data, EscapingScheme scheme) { 233 Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder(); 234 Metrics.Gauge.Builder gaugeBuilder = Metrics.Gauge.newBuilder(); 235 gaugeBuilder.setValue(1); 236 addLabels(metricBuilder, data.getLabels(), scheme); 237 metricBuilder.setGauge(gaugeBuilder); 238 setScrapeTimestamp(metricBuilder, data); 239 return metricBuilder; 240 } 241 242 private Metrics.Metric.Builder convert( 243 StateSetSnapshot.StateSetDataPointSnapshot data, String name, int i, EscapingScheme scheme) { 244 Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder(); 245 Metrics.Gauge.Builder gaugeBuilder = Metrics.Gauge.newBuilder(); 246 addLabels(metricBuilder, data.getLabels(), scheme); 247 metricBuilder.addLabel( 248 Metrics.LabelPair.newBuilder().setName(name).setValue(data.getName(i)).build()); 249 if (data.isTrue(i)) { 250 gaugeBuilder.setValue(1); 251 } else { 252 gaugeBuilder.setValue(0); 253 } 254 metricBuilder.setGauge(gaugeBuilder); 255 setScrapeTimestamp(metricBuilder, data); 256 return metricBuilder; 257 } 258 259 private Metrics.Metric.Builder convert( 260 UnknownSnapshot.UnknownDataPointSnapshot data, EscapingScheme scheme) { 261 Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder(); 262 Metrics.Untyped.Builder untypedBuilder = Metrics.Untyped.newBuilder(); 263 untypedBuilder.setValue(data.getValue()); 264 addLabels(metricBuilder, data.getLabels(), scheme); 265 metricBuilder.setUntyped(untypedBuilder); 266 return metricBuilder; 267 } 268 269 private Metrics.Exemplar.Builder convert(Exemplar exemplar, EscapingScheme scheme) { 270 Metrics.Exemplar.Builder builder = Metrics.Exemplar.newBuilder(); 271 builder.setValue(exemplar.getValue()); 272 addLabels(builder, exemplar.getLabels(), scheme); 273 if (exemplar.hasTimestamp()) { 274 builder.setTimestamp(timestampFromMillis(exemplar.getTimestampMillis())); 275 } 276 return builder; 277 } 278 279 private void setMetadataUnlessEmpty( 280 Metrics.MetricFamily.Builder builder, 281 MetricMetadata metadata, 282 @Nullable String nameSuffix, 283 Metrics.MetricType type, 284 EscapingScheme scheme) { 285 if (builder.getMetricCount() == 0) { 286 return; 287 } 288 if (nameSuffix == null) { 289 builder.setName(SnapshotEscaper.getMetadataName(metadata, scheme)); 290 } else { 291 builder.setName(SnapshotEscaper.getMetadataName(metadata, scheme) + nameSuffix); 292 } 293 if (metadata.getHelp() != null) { 294 builder.setHelp(metadata.getHelp()); 295 } 296 builder.setType(type); 297 } 298 299 private long getNativeCount(HistogramSnapshot.HistogramDataPointSnapshot data) { 300 if (data.hasCount()) { 301 return data.getCount(); 302 } else { 303 long count = data.getNativeZeroCount(); 304 for (int i = 0; i < data.getNativeBucketsForPositiveValues().size(); i++) { 305 count += data.getNativeBucketsForPositiveValues().getCount(i); 306 } 307 for (int i = 0; i < data.getNativeBucketsForNegativeValues().size(); i++) { 308 count += data.getNativeBucketsForNegativeValues().getCount(i); 309 } 310 return count; 311 } 312 } 313 314 private void addBuckets( 315 Metrics.Histogram.Builder histogramBuilder, NativeHistogramBuckets buckets, int sgn) { 316 if (buckets.size() > 0) { 317 Metrics.BucketSpan.Builder currentSpan = Metrics.BucketSpan.newBuilder(); 318 currentSpan.setOffset(buckets.getBucketIndex(0)); 319 currentSpan.setLength(0); 320 int previousIndex = currentSpan.getOffset(); 321 long previousCount = 0; 322 for (int i = 0; i < buckets.size(); i++) { 323 if (buckets.getBucketIndex(i) > previousIndex + 1) { 324 // If the gap between bucketIndex and previousIndex is just 1 or 2, 325 // we don't start a new span but continue the existing span and add 1 or 2 empty buckets. 326 if (buckets.getBucketIndex(i) <= previousIndex + 3) { 327 while (buckets.getBucketIndex(i) > previousIndex + 1) { 328 currentSpan.setLength(currentSpan.getLength() + 1); 329 previousIndex++; 330 if (sgn > 0) { 331 histogramBuilder.addPositiveDelta(-previousCount); 332 } else { 333 histogramBuilder.addNegativeDelta(-previousCount); 334 } 335 previousCount = 0; 336 } 337 } else { 338 if (sgn > 0) { 339 histogramBuilder.addPositiveSpan(currentSpan.build()); 340 } else { 341 histogramBuilder.addNegativeSpan(currentSpan.build()); 342 } 343 currentSpan = Metrics.BucketSpan.newBuilder(); 344 currentSpan.setOffset(buckets.getBucketIndex(i) - (previousIndex + 1)); 345 } 346 } 347 currentSpan.setLength(currentSpan.getLength() + 1); 348 previousIndex = buckets.getBucketIndex(i); 349 if (sgn > 0) { 350 histogramBuilder.addPositiveDelta(buckets.getCount(i) - previousCount); 351 } else { 352 histogramBuilder.addNegativeDelta(buckets.getCount(i) - previousCount); 353 } 354 previousCount = buckets.getCount(i); 355 } 356 if (sgn > 0) { 357 histogramBuilder.addPositiveSpan(currentSpan.build()); 358 } else { 359 histogramBuilder.addNegativeSpan(currentSpan.build()); 360 } 361 } 362 } 363 364 private void addLabels( 365 Metrics.Metric.Builder metricBuilder, Labels labels, EscapingScheme scheme) { 366 for (int i = 0; i < labels.size(); i++) { 367 metricBuilder.addLabel( 368 Metrics.LabelPair.newBuilder() 369 .setName(getSnapshotLabelName(labels, i, scheme)) 370 .setValue(labels.getValue(i)) 371 .build()); 372 } 373 } 374 375 private void addLabels( 376 Metrics.Exemplar.Builder metricBuilder, Labels labels, EscapingScheme scheme) { 377 for (int i = 0; i < labels.size(); i++) { 378 metricBuilder.addLabel( 379 Metrics.LabelPair.newBuilder() 380 .setName(getSnapshotLabelName(labels, i, scheme)) 381 .setValue(labels.getValue(i)) 382 .build()); 383 } 384 } 385 386 private void setScrapeTimestamp(Metrics.Metric.Builder metricBuilder, DataPointSnapshot data) { 387 if (data.hasScrapeTimestamp()) { 388 metricBuilder.setTimestampMs(data.getScrapeTimestampMillis()); 389 } 390 } 391}