001package io.prometheus.metrics.expositionformats; 002 003import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeDouble; 004import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeEscapedString; 005import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLabels; 006import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLong; 007import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeName; 008import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeOpenMetricsTimestamp; 009import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getOriginalMetadataName; 010import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName; 011 012import io.prometheus.metrics.annotations.StableApi; 013import io.prometheus.metrics.config.EscapingScheme; 014import io.prometheus.metrics.config.OpenMetrics2Properties; 015import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; 016import io.prometheus.metrics.model.snapshots.CounterSnapshot; 017import io.prometheus.metrics.model.snapshots.DataPointSnapshot; 018import io.prometheus.metrics.model.snapshots.Exemplar; 019import io.prometheus.metrics.model.snapshots.Exemplars; 020import io.prometheus.metrics.model.snapshots.GaugeSnapshot; 021import io.prometheus.metrics.model.snapshots.HistogramSnapshot; 022import io.prometheus.metrics.model.snapshots.InfoSnapshot; 023import io.prometheus.metrics.model.snapshots.Labels; 024import io.prometheus.metrics.model.snapshots.MetricMetadata; 025import io.prometheus.metrics.model.snapshots.MetricSnapshot; 026import io.prometheus.metrics.model.snapshots.MetricSnapshots; 027import io.prometheus.metrics.model.snapshots.NativeHistogramBuckets; 028import io.prometheus.metrics.model.snapshots.PrometheusNaming; 029import io.prometheus.metrics.model.snapshots.Quantile; 030import io.prometheus.metrics.model.snapshots.SnapshotEscaper; 031import io.prometheus.metrics.model.snapshots.StateSetSnapshot; 032import io.prometheus.metrics.model.snapshots.SummarySnapshot; 033import io.prometheus.metrics.model.snapshots.UnknownSnapshot; 034import java.io.BufferedWriter; 035import java.io.IOException; 036import java.io.OutputStream; 037import java.io.OutputStreamWriter; 038import java.io.Writer; 039import java.nio.charset.StandardCharsets; 040import javax.annotation.Nullable; 041 042/** 043 * Write the OpenMetrics 2.0 text format. Unlike the OM1 writer, this writer outputs metric names as 044 * provided by the user, without appending {@code _total} or unit suffixes. The {@code _info} suffix 045 * is enforced per the OM2 spec (MUST). This is experimental and subject to change as the <a 046 * href="https://github.com/prometheus/docs/blob/main/docs/specs/om/open_metrics_spec_2_0.md">OpenMetrics 047 * 2.0 specification</a> evolves. 048 */ 049@StableApi 050public class OpenMetrics2TextFormatWriter implements ExpositionFormatWriter { 051 052 public static class Builder { 053 private OpenMetrics2Properties openMetrics2Properties = 054 OpenMetrics2Properties.builder().build(); 055 boolean createdTimestampsEnabled; 056 boolean exemplarsOnAllMetricTypesEnabled; 057 058 private Builder() {} 059 060 /** 061 * @param openMetrics2Properties OpenMetrics 2.0 feature flags 062 */ 063 public Builder setOpenMetrics2Properties(OpenMetrics2Properties openMetrics2Properties) { 064 this.openMetrics2Properties = openMetrics2Properties; 065 return this; 066 } 067 068 /** 069 * @param createdTimestampsEnabled whether delegated OM1 output includes _created metrics 070 */ 071 public Builder setCreatedTimestampsEnabled(boolean createdTimestampsEnabled) { 072 this.createdTimestampsEnabled = createdTimestampsEnabled; 073 return this; 074 } 075 076 /** 077 * @param exemplarsOnAllMetricTypesEnabled whether to include exemplars in the output for all 078 * metric types 079 */ 080 public Builder setExemplarsOnAllMetricTypesEnabled(boolean exemplarsOnAllMetricTypesEnabled) { 081 this.exemplarsOnAllMetricTypesEnabled = exemplarsOnAllMetricTypesEnabled; 082 return this; 083 } 084 085 public OpenMetrics2TextFormatWriter build() { 086 return new OpenMetrics2TextFormatWriter( 087 openMetrics2Properties, createdTimestampsEnabled, exemplarsOnAllMetricTypesEnabled); 088 } 089 } 090 091 public static final String CONTENT_TYPE = 092 "application/openmetrics-text; version=2.0.0; charset=utf-8"; 093 private final OpenMetrics2Properties openMetrics2Properties; 094 private final boolean createdTimestampsEnabled; 095 private final boolean exemplarsOnAllMetricTypesEnabled; 096 private final OpenMetricsTextFormatWriter om1Writer; 097 098 /** 099 * @param openMetrics2Properties OpenMetrics 2.0 feature flags 100 * @param createdTimestampsEnabled whether delegated OM1 output includes _created metrics 101 * @param exemplarsOnAllMetricTypesEnabled whether to include exemplars on all metric types 102 */ 103 public OpenMetrics2TextFormatWriter( 104 OpenMetrics2Properties openMetrics2Properties, 105 boolean createdTimestampsEnabled, 106 boolean exemplarsOnAllMetricTypesEnabled) { 107 this.openMetrics2Properties = openMetrics2Properties; 108 this.createdTimestampsEnabled = createdTimestampsEnabled; 109 this.exemplarsOnAllMetricTypesEnabled = exemplarsOnAllMetricTypesEnabled; 110 this.om1Writer = 111 new OpenMetricsTextFormatWriter(createdTimestampsEnabled, exemplarsOnAllMetricTypesEnabled); 112 } 113 114 public static Builder builder() { 115 return new Builder(); 116 } 117 118 public static OpenMetrics2TextFormatWriter create() { 119 return builder().build(); 120 } 121 122 @Override 123 public boolean accepts(@Nullable String acceptHeader) { 124 if (acceptHeader == null) { 125 return false; 126 } 127 return acceptHeader.contains("application/openmetrics-text"); 128 } 129 130 @Override 131 public String getContentType() { 132 // When contentNegotiation=false (default), masquerade as OM1 for compatibility. 133 // When contentNegotiation=true, use proper OM2 version. 134 if (openMetrics2Properties.getContentNegotiation()) { 135 return CONTENT_TYPE; 136 } else { 137 return OpenMetricsTextFormatWriter.CONTENT_TYPE; 138 } 139 } 140 141 public OpenMetrics2Properties getOpenMetrics2Properties() { 142 return openMetrics2Properties; 143 } 144 145 @Override 146 public void write(OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme scheme) 147 throws IOException { 148 Writer writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)); 149 MetricSnapshots merged = TextFormatUtil.mergeDuplicates(metricSnapshots); 150 for (MetricSnapshot s : merged) { 151 MetricSnapshot snapshot = SnapshotEscaper.escapeMetricSnapshot(s, scheme); 152 if (!snapshot.getDataPoints().isEmpty()) { 153 if (snapshot instanceof CounterSnapshot) { 154 writeCounter(writer, (CounterSnapshot) snapshot, scheme); 155 } else if (snapshot instanceof GaugeSnapshot) { 156 writeGauge(writer, (GaugeSnapshot) snapshot, scheme); 157 } else if (snapshot instanceof HistogramSnapshot) { 158 writeHistogram(writer, (HistogramSnapshot) snapshot, scheme); 159 } else if (snapshot instanceof SummarySnapshot) { 160 writeSummary(writer, (SummarySnapshot) snapshot, scheme); 161 } else if (snapshot instanceof InfoSnapshot) { 162 writeInfo(writer, (InfoSnapshot) snapshot, scheme); 163 } else if (snapshot instanceof StateSetSnapshot) { 164 writeStateSet(writer, (StateSetSnapshot) snapshot, scheme); 165 } else if (snapshot instanceof UnknownSnapshot) { 166 writeUnknown(writer, (UnknownSnapshot) snapshot, scheme); 167 } 168 } 169 } 170 writer.write("# EOF\n"); 171 writer.flush(); 172 } 173 174 private void writeCounter(Writer writer, CounterSnapshot snapshot, EscapingScheme scheme) 175 throws IOException { 176 MetricMetadata metadata = snapshot.getMetadata(); 177 // OM2: use the original name, no _total or unit suffix appending. 178 String counterName = getOriginalMetadataName(metadata, scheme); 179 writeMetadataWithName(writer, counterName, "counter", metadata); 180 for (CounterSnapshot.CounterDataPointSnapshot data : snapshot.getDataPoints()) { 181 writeNameAndLabels(writer, counterName, null, data.getLabels(), scheme); 182 writeDouble(writer, data.getValue()); 183 if (data.hasScrapeTimestamp()) { 184 writer.write(' '); 185 writeOpenMetricsTimestamp(writer, data.getScrapeTimestampMillis()); 186 } 187 if (data.hasCreatedTimestamp()) { 188 writer.write(" st@"); 189 writeOpenMetricsTimestamp(writer, data.getCreatedTimestampMillis()); 190 } 191 writeExemplar(writer, data.getExemplar(), scheme); 192 writer.write('\n'); 193 } 194 } 195 196 private void writeGauge(Writer writer, GaugeSnapshot snapshot, EscapingScheme scheme) 197 throws IOException { 198 MetricMetadata metadata = snapshot.getMetadata(); 199 String name = getOriginalMetadataName(metadata, scheme); 200 writeMetadataWithName(writer, name, "gauge", metadata); 201 for (GaugeSnapshot.GaugeDataPointSnapshot data : snapshot.getDataPoints()) { 202 writeNameAndLabels(writer, name, null, data.getLabels(), scheme); 203 writeDouble(writer, data.getValue()); 204 if (exemplarsOnAllMetricTypesEnabled) { 205 writeScrapeTimestampAndExemplar(writer, data, data.getExemplar(), scheme); 206 } else { 207 writeScrapeTimestampAndExemplar(writer, data, null, scheme); 208 } 209 } 210 } 211 212 private void writeHistogram(Writer writer, HistogramSnapshot snapshot, EscapingScheme scheme) 213 throws IOException { 214 boolean compositeHistogram = 215 openMetrics2Properties.getCompositeValues() || openMetrics2Properties.getNativeHistograms(); 216 MetricMetadata metadata = snapshot.getMetadata(); 217 String name = getOriginalMetadataName(metadata, scheme); 218 if (!compositeHistogram && !openMetrics2Properties.getExemplarCompliance()) { 219 writeClassicHistogram(writer, name, snapshot, scheme); 220 return; 221 } 222 if (snapshot.isGaugeHistogram()) { 223 writeMetadataWithName(writer, name, "gaugehistogram", metadata); 224 for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) { 225 if (openMetrics2Properties.getNativeHistograms() && data.hasNativeHistogramData()) { 226 writeNativeHistogramDataPoint(writer, name, "gcount", "gsum", data, scheme, false); 227 } else { 228 writeCompositeHistogramDataPoint(writer, name, "gcount", "gsum", data, scheme, false); 229 } 230 } 231 } else { 232 writeMetadataWithName(writer, name, "histogram", metadata); 233 for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) { 234 if (openMetrics2Properties.getNativeHistograms() && data.hasNativeHistogramData()) { 235 writeNativeHistogramDataPoint(writer, name, "count", "sum", data, scheme, true); 236 } else { 237 writeCompositeHistogramDataPoint(writer, name, "count", "sum", data, scheme, true); 238 } 239 } 240 } 241 } 242 243 private void writeClassicHistogram( 244 Writer writer, String name, HistogramSnapshot snapshot, EscapingScheme scheme) 245 throws IOException { 246 if (snapshot.isGaugeHistogram()) { 247 writeMetadataWithName(writer, name, "gaugehistogram", snapshot.getMetadata()); 248 writeClassicHistogramDataPoints(writer, name, "_gcount", "_gsum", snapshot, scheme); 249 } else { 250 writeMetadataWithName(writer, name, "histogram", snapshot.getMetadata()); 251 writeClassicHistogramDataPoints(writer, name, "_count", "_sum", snapshot, scheme); 252 } 253 } 254 255 private void writeClassicHistogramDataPoints( 256 Writer writer, 257 String name, 258 String countSuffix, 259 String sumSuffix, 260 HistogramSnapshot snapshot, 261 EscapingScheme scheme) 262 throws IOException { 263 String bucketName = name + "_bucket"; 264 for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) { 265 ClassicHistogramBuckets buckets = getClassicBuckets(data); 266 Exemplars exemplars = data.getExemplars(); 267 long cumulativeCount = 0; 268 for (int i = 0; i < buckets.size(); i++) { 269 cumulativeCount += buckets.getCount(i); 270 writeNameAndLabels( 271 writer, bucketName, null, data.getLabels(), scheme, "le", buckets.getUpperBound(i)); 272 writeLong(writer, cumulativeCount); 273 Exemplar exemplar; 274 if (i == 0) { 275 exemplar = exemplars.get(Double.NEGATIVE_INFINITY, buckets.getUpperBound(i)); 276 } else { 277 exemplar = exemplars.get(buckets.getUpperBound(i - 1), buckets.getUpperBound(i)); 278 } 279 writeScrapeTimestampAndExemplar(writer, data, exemplar, scheme); 280 } 281 if (data.hasCount() && data.hasSum()) { 282 writeClassicCountAndSum(writer, name, data, countSuffix, sumSuffix, exemplars, scheme); 283 } 284 writeClassicCreated(writer, name, data, scheme); 285 } 286 } 287 288 private void writeClassicCountAndSum( 289 Writer writer, 290 String name, 291 HistogramSnapshot.HistogramDataPointSnapshot data, 292 String countSuffix, 293 String sumSuffix, 294 Exemplars exemplars, 295 EscapingScheme scheme) 296 throws IOException { 297 writeNameAndLabels(writer, name, countSuffix, data.getLabels(), scheme); 298 writeLong(writer, data.getCount()); 299 if (exemplarsOnAllMetricTypesEnabled) { 300 writeScrapeTimestampAndExemplar(writer, data, exemplars.getLatest(), scheme); 301 } else { 302 writeScrapeTimestampAndExemplar(writer, data, null, scheme); 303 } 304 writeNameAndLabels(writer, name, sumSuffix, data.getLabels(), scheme); 305 writeDouble(writer, data.getSum()); 306 writeScrapeTimestampAndExemplar(writer, data, null, scheme); 307 } 308 309 private void writeClassicCreated( 310 Writer writer, 311 String name, 312 HistogramSnapshot.HistogramDataPointSnapshot data, 313 EscapingScheme scheme) 314 throws IOException { 315 if (createdTimestampsEnabled && data.hasCreatedTimestamp()) { 316 writeNameAndLabels(writer, name, "_created", data.getLabels(), scheme); 317 writeOpenMetricsTimestamp(writer, data.getCreatedTimestampMillis()); 318 if (data.hasScrapeTimestamp()) { 319 writer.write(' '); 320 writeOpenMetricsTimestamp(writer, data.getScrapeTimestampMillis()); 321 } 322 writer.write('\n'); 323 } 324 } 325 326 private void writeCompositeHistogramDataPoint( 327 Writer writer, 328 String name, 329 String countKey, 330 String sumKey, 331 HistogramSnapshot.HistogramDataPointSnapshot data, 332 EscapingScheme scheme, 333 boolean includeStartTimestamp) 334 throws IOException { 335 writeNameAndLabels(writer, name, null, data.getLabels(), scheme); 336 writer.write('{'); 337 writer.write(countKey); 338 writer.write(':'); 339 writeLong(writer, data.getCount()); 340 writer.write(','); 341 writer.write(sumKey); 342 writer.write(':'); 343 writeDouble(writer, data.getSum()); 344 writeClassicBucketsField(writer, data); 345 writer.write('}'); 346 if (data.hasScrapeTimestamp()) { 347 writer.write(' '); 348 writeOpenMetricsTimestamp(writer, data.getScrapeTimestampMillis()); 349 } 350 if (includeStartTimestamp && data.hasCreatedTimestamp()) { 351 writer.write(" st@"); 352 writeOpenMetricsTimestamp(writer, data.getCreatedTimestampMillis()); 353 } 354 writeExemplars(writer, data.getExemplars(), scheme); 355 writer.write('\n'); 356 } 357 358 private void writeNativeHistogramDataPoint( 359 Writer writer, 360 String name, 361 String countKey, 362 String sumKey, 363 HistogramSnapshot.HistogramDataPointSnapshot data, 364 EscapingScheme scheme, 365 boolean includeStartTimestamp) 366 throws IOException { 367 writeNameAndLabels(writer, name, null, data.getLabels(), scheme); 368 writer.write('{'); 369 writer.write(countKey); 370 writer.write(':'); 371 writeLong(writer, data.getCount()); 372 writer.write(','); 373 writer.write(sumKey); 374 writer.write(':'); 375 writeDouble(writer, data.getSum()); 376 writer.write(",schema:"); 377 writer.write(Integer.toString(data.getNativeSchema())); 378 writer.write(",zero_threshold:"); 379 writeDouble(writer, data.getNativeZeroThreshold()); 380 writer.write(",zero_count:"); 381 writeLong(writer, data.getNativeZeroCount()); 382 writeNativeBucketFields(writer, "negative", data.getNativeBucketsForNegativeValues()); 383 writeNativeBucketFields(writer, "positive", data.getNativeBucketsForPositiveValues()); 384 if (data.hasClassicHistogramData()) { 385 writeClassicBucketsField(writer, data); 386 } 387 writer.write('}'); 388 if (data.hasScrapeTimestamp()) { 389 writer.write(' '); 390 writeOpenMetricsTimestamp(writer, data.getScrapeTimestampMillis()); 391 } 392 if (includeStartTimestamp && data.hasCreatedTimestamp()) { 393 writer.write(" st@"); 394 writeOpenMetricsTimestamp(writer, data.getCreatedTimestampMillis()); 395 } 396 writeExemplars(writer, data.getExemplars(), scheme); 397 writer.write('\n'); 398 } 399 400 private ClassicHistogramBuckets getClassicBuckets( 401 HistogramSnapshot.HistogramDataPointSnapshot data) { 402 if (data.getClassicBuckets().isEmpty()) { 403 return ClassicHistogramBuckets.of( 404 new double[] {Double.POSITIVE_INFINITY}, new long[] {data.getCount()}); 405 } else { 406 return data.getClassicBuckets(); 407 } 408 } 409 410 private void writeClassicBucketsField( 411 Writer writer, HistogramSnapshot.HistogramDataPointSnapshot data) throws IOException { 412 writer.write(",bucket:["); 413 ClassicHistogramBuckets buckets = getClassicBuckets(data); 414 long cumulativeCount = 0; 415 for (int i = 0; i < buckets.size(); i++) { 416 if (i > 0) { 417 writer.write(','); 418 } 419 cumulativeCount += buckets.getCount(i); 420 writeDouble(writer, buckets.getUpperBound(i)); 421 writer.write(':'); 422 writeLong(writer, cumulativeCount); 423 } 424 writer.write(']'); 425 } 426 427 private void writeNativeBucketFields(Writer writer, String prefix, NativeHistogramBuckets buckets) 428 throws IOException { 429 if (buckets.size() == 0) { 430 return; 431 } 432 writer.write(','); 433 writer.write(prefix); 434 writer.write("_spans:["); 435 writeNativeBucketSpans(writer, buckets); 436 writer.write("],"); 437 writer.write(prefix); 438 writer.write("_buckets:["); 439 for (int i = 0; i < buckets.size(); i++) { 440 if (i > 0) { 441 writer.write(','); 442 } 443 writeLong(writer, buckets.getCount(i)); 444 } 445 writer.write(']'); 446 } 447 448 private void writeNativeBucketSpans(Writer writer, NativeHistogramBuckets buckets) 449 throws IOException { 450 int spanOffset = buckets.getBucketIndex(0); 451 int spanLength = 1; 452 int previousIndex = buckets.getBucketIndex(0); 453 boolean firstSpan = true; 454 for (int i = 1; i < buckets.size(); i++) { 455 int bucketIndex = buckets.getBucketIndex(i); 456 if (bucketIndex == previousIndex + 1) { 457 spanLength++; 458 } else { 459 firstSpan = writeNativeBucketSpan(writer, spanOffset, spanLength, firstSpan); 460 spanOffset = bucketIndex - previousIndex - 1; 461 spanLength = 1; 462 } 463 previousIndex = bucketIndex; 464 } 465 writeNativeBucketSpan(writer, spanOffset, spanLength, firstSpan); 466 } 467 468 private boolean writeNativeBucketSpan(Writer writer, int offset, int length, boolean firstSpan) 469 throws IOException { 470 if (!firstSpan) { 471 writer.write(','); 472 } 473 writer.write(Integer.toString(offset)); 474 writer.write(':'); 475 writer.write(Integer.toString(length)); 476 return false; 477 } 478 479 private void writeSummary(Writer writer, SummarySnapshot snapshot, EscapingScheme scheme) 480 throws IOException { 481 if (!openMetrics2Properties.getCompositeValues() 482 && !openMetrics2Properties.getExemplarCompliance()) { 483 om1Writer.writeSummary(writer, snapshot, scheme); 484 return; 485 } 486 boolean metadataWritten = false; 487 MetricMetadata metadata = snapshot.getMetadata(); 488 String name = getOriginalMetadataName(metadata, scheme); 489 for (SummarySnapshot.SummaryDataPointSnapshot data : snapshot.getDataPoints()) { 490 if (data.getQuantiles().size() == 0 && !data.hasCount() && !data.hasSum()) { 491 continue; 492 } 493 if (!metadataWritten) { 494 writeMetadataWithName(writer, name, "summary", metadata); 495 metadataWritten = true; 496 } 497 writeCompositeSummaryDataPoint(writer, name, data, scheme); 498 } 499 } 500 501 private void writeCompositeSummaryDataPoint( 502 Writer writer, 503 String name, 504 SummarySnapshot.SummaryDataPointSnapshot data, 505 EscapingScheme scheme) 506 throws IOException { 507 writeNameAndLabels(writer, name, null, data.getLabels(), scheme); 508 writer.write('{'); 509 boolean first = true; 510 if (data.hasCount()) { 511 writer.write("count:"); 512 writeLong(writer, data.getCount()); 513 first = false; 514 } 515 if (data.hasSum()) { 516 if (!first) { 517 writer.write(','); 518 } 519 writer.write("sum:"); 520 writeDouble(writer, data.getSum()); 521 first = false; 522 } 523 if (!first) { 524 writer.write(','); 525 } 526 writer.write("quantile:["); 527 for (int i = 0; i < data.getQuantiles().size(); i++) { 528 if (i > 0) { 529 writer.write(','); 530 } 531 Quantile q = data.getQuantiles().get(i); 532 writeDouble(writer, q.getQuantile()); 533 writer.write(':'); 534 writeDouble(writer, q.getValue()); 535 } 536 writer.write(']'); 537 writer.write('}'); 538 if (data.hasScrapeTimestamp()) { 539 writer.write(' '); 540 writeOpenMetricsTimestamp(writer, data.getScrapeTimestampMillis()); 541 } 542 if (data.hasCreatedTimestamp()) { 543 writer.write(" st@"); 544 writeOpenMetricsTimestamp(writer, data.getCreatedTimestampMillis()); 545 } 546 writeExemplars(writer, data.getExemplars(), scheme); 547 writer.write('\n'); 548 } 549 550 private void writeInfo(Writer writer, InfoSnapshot snapshot, EscapingScheme scheme) 551 throws IOException { 552 MetricMetadata metadata = snapshot.getMetadata(); 553 // OM2 spec: Info MetricFamily name MUST end in _info. 554 // In OM2, TYPE/HELP use the same name as the data lines. 555 String infoName = ensureSuffix(getOriginalMetadataName(metadata, scheme), "_info"); 556 writeMetadataWithName(writer, infoName, "info", metadata); 557 for (InfoSnapshot.InfoDataPointSnapshot data : snapshot.getDataPoints()) { 558 writeNameAndLabels(writer, infoName, null, data.getLabels(), scheme); 559 writer.write("1"); 560 writeScrapeTimestampAndExemplar(writer, data, null, scheme); 561 } 562 } 563 564 private void writeStateSet(Writer writer, StateSetSnapshot snapshot, EscapingScheme scheme) 565 throws IOException { 566 MetricMetadata metadata = snapshot.getMetadata(); 567 String name = getOriginalMetadataName(metadata, scheme); 568 writeMetadataWithName(writer, name, "stateset", metadata); 569 for (StateSetSnapshot.StateSetDataPointSnapshot data : snapshot.getDataPoints()) { 570 for (int i = 0; i < data.size(); i++) { 571 writer.write(name); 572 writer.write('{'); 573 Labels labels = data.getLabels(); 574 for (int j = 0; j < labels.size(); j++) { 575 if (j > 0) { 576 writer.write(","); 577 } 578 writer.write(getSnapshotLabelName(labels, j, scheme)); 579 writer.write("=\""); 580 writeEscapedString(writer, labels.getValue(j)); 581 writer.write("\""); 582 } 583 if (!labels.isEmpty()) { 584 writer.write(","); 585 } 586 writer.write(name); 587 writer.write("=\""); 588 writeEscapedString(writer, data.getName(i)); 589 writer.write("\"} "); 590 if (data.isTrue(i)) { 591 writer.write("1"); 592 } else { 593 writer.write("0"); 594 } 595 writeScrapeTimestampAndExemplar(writer, data, null, scheme); 596 } 597 } 598 } 599 600 private void writeUnknown(Writer writer, UnknownSnapshot snapshot, EscapingScheme scheme) 601 throws IOException { 602 MetricMetadata metadata = snapshot.getMetadata(); 603 String name = getOriginalMetadataName(metadata, scheme); 604 writeMetadataWithName(writer, name, "unknown", metadata); 605 for (UnknownSnapshot.UnknownDataPointSnapshot data : snapshot.getDataPoints()) { 606 writeNameAndLabels(writer, name, null, data.getLabels(), scheme); 607 writeDouble(writer, data.getValue()); 608 if (exemplarsOnAllMetricTypesEnabled) { 609 writeScrapeTimestampAndExemplar(writer, data, data.getExemplar(), scheme); 610 } else { 611 writeScrapeTimestampAndExemplar(writer, data, null, scheme); 612 } 613 } 614 } 615 616 private void writeNameAndLabels( 617 Writer writer, 618 String name, 619 @Nullable String suffix, 620 Labels labels, 621 EscapingScheme escapingScheme) 622 throws IOException { 623 writeNameAndLabels(writer, name, suffix, labels, escapingScheme, null, 0.0); 624 } 625 626 private void writeNameAndLabels( 627 Writer writer, 628 String name, 629 @Nullable String suffix, 630 Labels labels, 631 EscapingScheme escapingScheme, 632 @Nullable String additionalLabelName, 633 double additionalLabelValue) 634 throws IOException { 635 boolean metricInsideBraces = false; 636 // If the name does not pass the legacy validity check, we must put the 637 // metric name inside the braces. 638 if (!PrometheusNaming.isValidLegacyMetricName(name)) { 639 metricInsideBraces = true; 640 writer.write('{'); 641 } 642 writeName(writer, suffix != null ? name + suffix : name, NameType.Metric); 643 if (!labels.isEmpty() || additionalLabelName != null) { 644 writeLabels( 645 writer, 646 labels, 647 additionalLabelName, 648 additionalLabelValue, 649 metricInsideBraces, 650 escapingScheme); 651 } else if (metricInsideBraces) { 652 writer.write('}'); 653 } 654 writer.write(' '); 655 } 656 657 private void writeScrapeTimestampAndExemplar( 658 Writer writer, DataPointSnapshot data, @Nullable Exemplar exemplar, EscapingScheme scheme) 659 throws IOException { 660 if (!openMetrics2Properties.getExemplarCompliance()) { 661 om1Writer.writeScrapeTimestampAndExemplar(writer, data, exemplar, scheme); 662 return; 663 } 664 if (data.hasScrapeTimestamp()) { 665 writer.write(' '); 666 writeOpenMetricsTimestamp(writer, data.getScrapeTimestampMillis()); 667 } 668 writeExemplar(writer, exemplar, scheme); 669 writer.write('\n'); 670 } 671 672 private void writeExemplar(Writer writer, @Nullable Exemplar exemplar, EscapingScheme scheme) 673 throws IOException { 674 if (exemplar == null) { 675 return; 676 } 677 if (!openMetrics2Properties.getExemplarCompliance()) { 678 om1Writer.writeExemplar(writer, exemplar, scheme); 679 return; 680 } 681 // exemplarCompliance=true: exemplars MUST have a timestamp per the OM2 spec. 682 if (exemplar.hasTimestamp()) { 683 om1Writer.writeExemplar(writer, exemplar, scheme); 684 } 685 } 686 687 private void writeExemplars(Writer writer, Exemplars exemplars, EscapingScheme scheme) 688 throws IOException { 689 for (Exemplar exemplar : exemplars) { 690 writeExemplar(writer, exemplar, scheme); 691 } 692 } 693 694 private void writeMetadataWithName( 695 Writer writer, String name, String typeName, MetricMetadata metadata) throws IOException { 696 writer.write("# TYPE "); 697 writeName(writer, name, NameType.Metric); 698 writer.write(' '); 699 writer.write(typeName); 700 writer.write('\n'); 701 if (metadata.getUnit() != null) { 702 writer.write("# UNIT "); 703 writeName(writer, name, NameType.Metric); 704 writer.write(' '); 705 writeEscapedString(writer, metadata.getUnit().toString()); 706 writer.write('\n'); 707 } 708 if (metadata.getHelp() != null && !metadata.getHelp().isEmpty()) { 709 writer.write("# HELP "); 710 writeName(writer, name, NameType.Metric); 711 writer.write(' '); 712 writeEscapedString(writer, metadata.getHelp()); 713 writer.write('\n'); 714 } 715 } 716 717 private static String ensureSuffix(String name, String suffix) { 718 if (name.endsWith(suffix)) { 719 return name; 720 } 721 return name + suffix; 722 } 723}