diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetrics2TextFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetrics2TextFormatWriter.java index 02f59b527..e2b2eb90e 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetrics2TextFormatWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetrics2TextFormatWriter.java @@ -14,9 +14,7 @@ import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.DataPointSnapshot; -import io.prometheus.metrics.model.snapshots.DistributionDataPointSnapshot; import io.prometheus.metrics.model.snapshots.Exemplar; -import io.prometheus.metrics.model.snapshots.Exemplars; import io.prometheus.metrics.model.snapshots.GaugeSnapshot; import io.prometheus.metrics.model.snapshots.HistogramSnapshot; import io.prometheus.metrics.model.snapshots.InfoSnapshot; @@ -36,7 +34,6 @@ import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; -import java.util.List; import javax.annotation.Nullable; /** @@ -92,6 +89,7 @@ public OpenMetrics2TextFormatWriter build() { private final OpenMetrics2Properties openMetrics2Properties; private final boolean createdTimestampsEnabled; private final boolean exemplarsOnAllMetricTypesEnabled; + private final OpenMetricsTextFormatWriter om1Writer; /** * @param openMetrics2Properties OpenMetrics 2.0 feature flags @@ -106,6 +104,8 @@ public OpenMetrics2TextFormatWriter( this.openMetrics2Properties = openMetrics2Properties; this.createdTimestampsEnabled = createdTimestampsEnabled; this.exemplarsOnAllMetricTypesEnabled = exemplarsOnAllMetricTypesEnabled; + this.om1Writer = + new OpenMetricsTextFormatWriter(createdTimestampsEnabled, exemplarsOnAllMetricTypesEnabled); } public static Builder builder() { @@ -200,50 +200,65 @@ private void writeGauge(Writer writer, GaugeSnapshot snapshot, EscapingScheme sc private void writeHistogram(Writer writer, HistogramSnapshot snapshot, EscapingScheme scheme) throws IOException { + if (!openMetrics2Properties.getCompositeValues()) { + om1Writer.writeHistogram(writer, snapshot, scheme); + return; + } MetricMetadata metadata = snapshot.getMetadata(); String name = getExpositionBaseMetadataName(metadata, scheme); if (snapshot.isGaugeHistogram()) { writeMetadataWithName(writer, name, "gaugehistogram", metadata); - writeClassicHistogramBuckets( - writer, name, "_gcount", "_gsum", snapshot.getDataPoints(), scheme); + for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) { + writeCompositeHistogramDataPoint(writer, name, "gcount", "gsum", data, scheme); + } } else { writeMetadataWithName(writer, name, "histogram", metadata); - writeClassicHistogramBuckets( - writer, name, "_count", "_sum", snapshot.getDataPoints(), scheme); + for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) { + writeCompositeHistogramDataPoint(writer, name, "count", "sum", data, scheme); + } } } - private void writeClassicHistogramBuckets( + private void writeCompositeHistogramDataPoint( Writer writer, String name, - String countSuffix, - String sumSuffix, - List dataList, + String countKey, + String sumKey, + HistogramSnapshot.HistogramDataPointSnapshot data, EscapingScheme scheme) throws IOException { - for (HistogramSnapshot.HistogramDataPointSnapshot data : dataList) { - ClassicHistogramBuckets buckets = getClassicBuckets(data); - Exemplars exemplars = data.getExemplars(); - long cumulativeCount = 0; - for (int i = 0; i < buckets.size(); i++) { - cumulativeCount += buckets.getCount(i); - writeNameAndLabels( - writer, name, "_bucket", data.getLabels(), scheme, "le", buckets.getUpperBound(i)); - writeLong(writer, cumulativeCount); - Exemplar exemplar; - if (i == 0) { - exemplar = exemplars.get(Double.NEGATIVE_INFINITY, buckets.getUpperBound(i)); - } else { - exemplar = exemplars.get(buckets.getUpperBound(i - 1), buckets.getUpperBound(i)); - } - writeScrapeTimestampAndExemplar(writer, data, exemplar, scheme); + writeNameAndLabels(writer, name, null, data.getLabels(), scheme); + writer.write('{'); + writer.write(countKey); + writer.write(':'); + writeLong(writer, data.getCount()); + writer.write(','); + writer.write(sumKey); + writer.write(':'); + writeDouble(writer, data.getSum()); + writer.write(",bucket:["); + ClassicHistogramBuckets buckets = getClassicBuckets(data); + long cumulativeCount = 0; + for (int i = 0; i < buckets.size(); i++) { + if (i > 0) { + writer.write(','); } - // In OpenMetrics format, histogram _count and _sum are either both present or both absent. - if (data.hasCount() && data.hasSum()) { - writeCountAndSum(writer, name, data, countSuffix, sumSuffix, exemplars, scheme); - } - writeCreated(writer, name, data, scheme); + cumulativeCount += buckets.getCount(i); + writeDouble(writer, buckets.getUpperBound(i)); + writer.write(':'); + writeLong(writer, cumulativeCount); } + writer.write("]}"); + if (data.hasScrapeTimestamp()) { + writer.write(' '); + writeOpenMetricsTimestamp(writer, data.getScrapeTimestampMillis()); + } + if (data.hasCreatedTimestamp()) { + writer.write(" st@"); + writeOpenMetricsTimestamp(writer, data.getCreatedTimestampMillis()); + } + writeExemplar(writer, data.getExemplars().getLatest(), scheme); + writer.write('\n'); } private ClassicHistogramBuckets getClassicBuckets( @@ -258,6 +273,10 @@ private ClassicHistogramBuckets getClassicBuckets( private void writeSummary(Writer writer, SummarySnapshot snapshot, EscapingScheme scheme) throws IOException { + if (!openMetrics2Properties.getCompositeValues()) { + om1Writer.writeSummary(writer, snapshot, scheme); + return; + } boolean metadataWritten = false; MetricMetadata metadata = snapshot.getMetadata(); String name = getExpositionBaseMetadataName(metadata, scheme); @@ -269,28 +288,59 @@ private void writeSummary(Writer writer, SummarySnapshot snapshot, EscapingSchem writeMetadataWithName(writer, name, "summary", metadata); metadataWritten = true; } - Exemplars exemplars = data.getExemplars(); - // Exemplars for summaries are new, and there's no best practice yet which Exemplars to choose - // for which - // time series. We select exemplars[0] for _count, exemplars[1] for _sum, and exemplars[2...] - // for the - // quantiles, all indexes modulo exemplars.length. - int exemplarIndex = 1; - for (Quantile quantile : data.getQuantiles()) { - writeNameAndLabels( - writer, name, null, data.getLabels(), scheme, "quantile", quantile.getQuantile()); - writeDouble(writer, quantile.getValue()); - if (exemplars.size() > 0 && exemplarsOnAllMetricTypesEnabled) { - exemplarIndex = (exemplarIndex + 1) % exemplars.size(); - writeScrapeTimestampAndExemplar(writer, data, exemplars.get(exemplarIndex), scheme); - } else { - writeScrapeTimestampAndExemplar(writer, data, null, scheme); + writeCompositeSummaryDataPoint(writer, name, data, scheme); + } + } + + private void writeCompositeSummaryDataPoint( + Writer writer, + String name, + SummarySnapshot.SummaryDataPointSnapshot data, + EscapingScheme scheme) + throws IOException { + writeNameAndLabels(writer, name, null, data.getLabels(), scheme); + writer.write('{'); + boolean first = true; + if (data.hasCount()) { + writer.write("count:"); + writeLong(writer, data.getCount()); + first = false; + } + if (data.hasSum()) { + if (!first) { + writer.write(','); + } + writer.write("sum:"); + writeDouble(writer, data.getSum()); + first = false; + } + if (data.getQuantiles().size() > 0) { + if (!first) { + writer.write(','); + } + writer.write("quantile:["); + for (int i = 0; i < data.getQuantiles().size(); i++) { + if (i > 0) { + writer.write(','); } + Quantile q = data.getQuantiles().get(i); + writeDouble(writer, q.getQuantile()); + writer.write(':'); + writeDouble(writer, q.getValue()); } - // Unlike histograms, summaries can have only a count or only a sum according to OpenMetrics. - writeCountAndSum(writer, name, data, "_count", "_sum", exemplars, scheme); - writeCreated(writer, name, data, scheme); + writer.write(']'); } + writer.write('}'); + if (data.hasScrapeTimestamp()) { + writer.write(' '); + writeOpenMetricsTimestamp(writer, data.getScrapeTimestampMillis()); + } + if (data.hasCreatedTimestamp()) { + writer.write(" st@"); + writeOpenMetricsTimestamp(writer, data.getCreatedTimestampMillis()); + } + writeExemplar(writer, data.getExemplars().getLatest(), scheme); + writer.write('\n'); } private void writeInfo(Writer writer, InfoSnapshot snapshot, EscapingScheme scheme) @@ -359,31 +409,6 @@ private void writeUnknown(Writer writer, UnknownSnapshot snapshot, EscapingSchem } } - private void writeCountAndSum( - Writer writer, - String name, - DistributionDataPointSnapshot data, - String countSuffix, - String sumSuffix, - Exemplars exemplars, - EscapingScheme scheme) - throws IOException { - if (data.hasCount()) { - writeNameAndLabels(writer, name, countSuffix, data.getLabels(), scheme); - writeLong(writer, data.getCount()); - if (exemplarsOnAllMetricTypesEnabled) { - writeScrapeTimestampAndExemplar(writer, data, exemplars.getLatest(), scheme); - } else { - writeScrapeTimestampAndExemplar(writer, data, null, scheme); - } - } - if (data.hasSum()) { - writeNameAndLabels(writer, name, sumSuffix, data.getLabels(), scheme); - writeDouble(writer, data.getSum()); - writeScrapeTimestampAndExemplar(writer, data, null, scheme); - } - } - private void writeCreated( Writer writer, String name, DataPointSnapshot data, EscapingScheme scheme) throws IOException { @@ -442,23 +467,33 @@ private void writeNameAndLabels( private void writeScrapeTimestampAndExemplar( Writer writer, DataPointSnapshot data, @Nullable Exemplar exemplar, EscapingScheme scheme) throws IOException { + if (!openMetrics2Properties.getExemplarCompliance()) { + om1Writer.writeScrapeTimestampAndExemplar(writer, data, exemplar, scheme); + return; + } if (data.hasScrapeTimestamp()) { writer.write(' '); writeOpenMetricsTimestamp(writer, data.getScrapeTimestampMillis()); } - if (exemplar != null) { - writer.write(" # "); - writeLabels(writer, exemplar.getLabels(), null, 0, false, scheme); - writer.write(' '); - writeDouble(writer, exemplar.getValue()); - if (exemplar.hasTimestamp()) { - writer.write(' '); - writeOpenMetricsTimestamp(writer, exemplar.getTimestampMillis()); - } - } + writeExemplar(writer, exemplar, scheme); writer.write('\n'); } + private void writeExemplar(Writer writer, @Nullable Exemplar exemplar, EscapingScheme scheme) + throws IOException { + if (exemplar == null) { + return; + } + if (!openMetrics2Properties.getExemplarCompliance()) { + om1Writer.writeExemplar(writer, exemplar, scheme); + return; + } + // exemplarCompliance=true: exemplars MUST have a timestamp per the OM2 spec. + if (exemplar.hasTimestamp()) { + om1Writer.writeExemplar(writer, exemplar, scheme); + } + } + private void writeMetadataWithName( Writer writer, String name, String typeName, MetricMetadata metadata) throws IOException { writer.write("# TYPE "); diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java index bffb60c14..1bc7e101f 100644 --- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java +++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java @@ -168,7 +168,7 @@ private void writeGauge(Writer writer, GaugeSnapshot snapshot, EscapingScheme sc } } - private void writeHistogram(Writer writer, HistogramSnapshot snapshot, EscapingScheme scheme) + void writeHistogram(Writer writer, HistogramSnapshot snapshot, EscapingScheme scheme) throws IOException { MetricMetadata metadata = snapshot.getMetadata(); if (snapshot.isGaugeHistogram()) { @@ -231,7 +231,7 @@ private ClassicHistogramBuckets getClassicBuckets( } } - private void writeSummary(Writer writer, SummarySnapshot snapshot, EscapingScheme scheme) + void writeSummary(Writer writer, SummarySnapshot snapshot, EscapingScheme scheme) throws IOException { boolean metadataWritten = false; MetricMetadata metadata = snapshot.getMetadata(); @@ -424,7 +424,7 @@ private void writeNameAndLabels( writer.write(' '); } - private void writeScrapeTimestampAndExemplar( + void writeScrapeTimestampAndExemplar( Writer writer, DataPointSnapshot data, @Nullable Exemplar exemplar, EscapingScheme scheme) throws IOException { if (data.hasScrapeTimestamp()) { @@ -432,18 +432,22 @@ private void writeScrapeTimestampAndExemplar( writeOpenMetricsTimestamp(writer, data.getScrapeTimestampMillis()); } if (exemplar != null) { - writer.write(" # "); - writeLabels(writer, exemplar.getLabels(), null, 0, false, scheme); - writer.write(' '); - writeDouble(writer, exemplar.getValue()); - if (exemplar.hasTimestamp()) { - writer.write(' '); - writeOpenMetricsTimestamp(writer, exemplar.getTimestampMillis()); - } + writeExemplar(writer, exemplar, scheme); } writer.write('\n'); } + void writeExemplar(Writer writer, Exemplar exemplar, EscapingScheme scheme) throws IOException { + writer.write(" # "); + writeLabels(writer, exemplar.getLabels(), null, 0, false, scheme); + writer.write(' '); + writeDouble(writer, exemplar.getValue()); + if (exemplar.hasTimestamp()) { + writer.write(' '); + writeOpenMetricsTimestamp(writer, exemplar.getTimestampMillis()); + } + } + /** * Returns the full exposition name for a metric. If the original name already ends with the given * suffix (e.g. "_total" for counters), uses the original name directly. Otherwise, appends the diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/OpenMetrics2TextFormatWriterTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/OpenMetrics2TextFormatWriterTest.java index efa803db0..75af79954 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/OpenMetrics2TextFormatWriterTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/OpenMetrics2TextFormatWriterTest.java @@ -4,13 +4,16 @@ import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.config.OpenMetrics2Properties; +import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.Exemplar; +import io.prometheus.metrics.model.snapshots.Exemplars; import io.prometheus.metrics.model.snapshots.GaugeSnapshot; import io.prometheus.metrics.model.snapshots.HistogramSnapshot; import io.prometheus.metrics.model.snapshots.InfoSnapshot; import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.MetricSnapshots; +import io.prometheus.metrics.model.snapshots.Quantiles; import io.prometheus.metrics.model.snapshots.StateSetSnapshot; import io.prometheus.metrics.model.snapshots.SummarySnapshot; import io.prometheus.metrics.model.snapshots.Unit; @@ -325,6 +328,236 @@ void testEmptySnapshot() throws IOException { assertThat(om2Output).isEqualTo("# EOF\n"); } + @Test + void testCompositeHistogram() throws IOException { + MetricSnapshots snapshots = + MetricSnapshots.of( + HistogramSnapshot.builder() + .name("http_request_duration_seconds") + .help("Request duration") + .dataPoint( + HistogramSnapshot.HistogramDataPointSnapshot.builder() + .sum(324789.3) + .classicHistogramBuckets( + ClassicHistogramBuckets.builder() + .bucket(0.1, 8) + .bucket(0.25, 2) + .bucket(0.5, 1) + .bucket(1.0, 3) + .bucket(Double.POSITIVE_INFINITY, 3) + .build()) + .build()) + .build()); + + String output = writeWithCompositeValues(snapshots); + + assertThat(output) + .isEqualTo( + "# TYPE http_request_duration_seconds histogram\n" + + "# HELP http_request_duration_seconds Request duration\n" + + "http_request_duration_seconds" + + " {count:17,sum:324789.3,bucket:[0.1:8,0.25:10,0.5:11,1.0:14,+Inf:17]}\n" + + "# EOF\n"); + } + + @Test + void testCompositeHistogramWithLabelsTimestampAndCreated() throws IOException { + MetricSnapshots snapshots = + MetricSnapshots.of( + HistogramSnapshot.builder() + .name("foo") + .dataPoint( + HistogramSnapshot.HistogramDataPointSnapshot.builder() + .labels(Labels.of("method", "GET")) + .sum(324789.3) + .classicHistogramBuckets( + ClassicHistogramBuckets.builder() + .bucket(0.1, 8) + .bucket(Double.POSITIVE_INFINITY, 9) + .build()) + .createdTimestampMillis(1520430000123L) + .scrapeTimestampMillis(1520879607789L) + .build()) + .build()); + + String output = writeWithCompositeValues(snapshots); + + assertThat(output) + .isEqualTo( + "# TYPE foo histogram\n" + + "foo{method=\"GET\"} {count:17,sum:324789.3,bucket:[0.1:8,+Inf:17]}" + + " 1520879607.789 st@1520430000.123\n" + + "# EOF\n"); + } + + @Test + void testCompositeHistogramWithExemplar() throws IOException { + Exemplar exemplar = + Exemplar.builder().value(0.67).traceId("shaZ8oxi").timestampMillis(1520879607789L).build(); + + MetricSnapshots snapshots = + MetricSnapshots.of( + HistogramSnapshot.builder() + .name("foo") + .dataPoint( + HistogramSnapshot.HistogramDataPointSnapshot.builder() + .sum(1.5) + .classicHistogramBuckets( + ClassicHistogramBuckets.builder() + .bucket(1.0, 1) + .bucket(Double.POSITIVE_INFINITY, 0) + .build()) + .exemplars(Exemplars.of(exemplar)) + .build()) + .build()); + + String output = writeWithCompositeValues(snapshots); + + assertThat(output) + .isEqualTo( + "# TYPE foo histogram\n" + + "foo {count:1,sum:1.5,bucket:[1.0:1,+Inf:1]}" + + " # {trace_id=\"shaZ8oxi\"} 0.67 1520879607.789\n" + + "# EOF\n"); + } + + @Test + void testCompositeGaugeHistogram() throws IOException { + MetricSnapshots snapshots = + MetricSnapshots.of( + HistogramSnapshot.builder() + .name("queue_size") + .gaugeHistogram(true) + .dataPoint( + HistogramSnapshot.HistogramDataPointSnapshot.builder() + .sum(3289.3) + .classicHistogramBuckets( + ClassicHistogramBuckets.builder() + .bucket(0.1, 20) + .bucket(1.0, 14) + .bucket(Double.POSITIVE_INFINITY, 8) + .build()) + .build()) + .build()); + + String output = writeWithCompositeValues(snapshots); + + // GaugeHistogram uses gcount/gsum per spec + assertThat(output) + .isEqualTo( + "# TYPE queue_size gaugehistogram\n" + + "queue_size {gcount:42,gsum:3289.3,bucket:[0.1:20,1.0:34,+Inf:42]}\n" + + "# EOF\n"); + } + + @Test + void testCompositeSummary() throws IOException { + MetricSnapshots snapshots = + MetricSnapshots.of( + SummarySnapshot.builder() + .name("rpc_duration_seconds") + .help("RPC duration") + .dataPoint( + SummarySnapshot.SummaryDataPointSnapshot.builder() + .count(17) + .sum(324789.3) + .quantiles( + Quantiles.builder().quantile(0.95, 123.7).quantile(0.99, 150.0).build()) + .build()) + .build()); + + String output = writeWithCompositeValues(snapshots); + + assertThat(output) + .isEqualTo( + "# TYPE rpc_duration_seconds summary\n" + + "# HELP rpc_duration_seconds RPC duration\n" + + "rpc_duration_seconds" + + " {count:17,sum:324789.3,quantile:[0.95:123.7,0.99:150.0]}\n" + + "# EOF\n"); + } + + @Test + void testCompositeSummaryWithCreatedAndExemplar() throws IOException { + Exemplar exemplar = + Exemplar.builder().value(0.5).traceId("abc123").timestampMillis(1520879607000L).build(); + + MetricSnapshots snapshots = + MetricSnapshots.of( + SummarySnapshot.builder() + .name("rpc_duration_seconds") + .dataPoint( + SummarySnapshot.SummaryDataPointSnapshot.builder() + .count(10) + .sum(100.0) + .createdTimestampMillis(1520430000000L) + .exemplars(Exemplars.of(exemplar)) + .build()) + .build()); + + String output = writeWithCompositeValues(snapshots); + + assertThat(output) + .isEqualTo( + "# TYPE rpc_duration_seconds summary\n" + + "rpc_duration_seconds {count:10,sum:100.0} st@1520430000.000" + + " # {trace_id=\"abc123\"} 0.5 1520879607.000\n" + + "# EOF\n"); + } + + @Test + void testExemplarComplianceSkipsExemplarWithoutTimestamp() throws IOException { + Exemplar exemplarWithTs = + Exemplar.builder().value(1.0).traceId("aaa").timestampMillis(1672850685829L).build(); + Exemplar exemplarWithoutTs = Exemplar.builder().value(2.0).traceId("bbb").build(); + + OpenMetrics2TextFormatWriter complianceWriter = + OpenMetrics2TextFormatWriter.builder() + .setOpenMetrics2Properties( + OpenMetrics2Properties.builder().exemplarCompliance(true).build()) + .build(); + OpenMetrics2TextFormatWriter defaultWriter = OpenMetrics2TextFormatWriter.create(); + + MetricSnapshots withTs = + MetricSnapshots.of( + CounterSnapshot.builder() + .name("requests") + .dataPoint( + CounterSnapshot.CounterDataPointSnapshot.builder() + .value(1.0) + .exemplar(exemplarWithTs) + .build()) + .build()); + MetricSnapshots withoutTs = + MetricSnapshots.of( + CounterSnapshot.builder() + .name("requests") + .dataPoint( + CounterSnapshot.CounterDataPointSnapshot.builder() + .value(1.0) + .exemplar(exemplarWithoutTs) + .build()) + .build()); + + // Compliance mode: exemplar WITH timestamp is emitted + assertThat(write(withTs, complianceWriter)).contains("# {trace_id=\"aaa\"} 1.0 1672850685.829"); + + // Compliance mode: exemplar WITHOUT timestamp is skipped + assertThat(write(withoutTs, complianceWriter)).doesNotContain("# {"); + + // Default mode: exemplar without timestamp is still emitted (just no timestamp) + assertThat(write(withoutTs, defaultWriter)).contains("# {trace_id=\"bbb\"} 2.0\n"); + } + + private String writeWithCompositeValues(MetricSnapshots snapshots) throws IOException { + OpenMetrics2TextFormatWriter writer = + OpenMetrics2TextFormatWriter.builder() + .setOpenMetrics2Properties( + OpenMetrics2Properties.builder().compositeValues(true).build()) + .build(); + return write(snapshots, writer); + } + private String writeWithOM1(MetricSnapshots snapshots) throws IOException { OpenMetricsTextFormatWriter writer = OpenMetricsTextFormatWriter.create(); return write(snapshots, writer);