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