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