Skip to content

Commit 8a18993

Browse files
committed
feat: OM2 writer outputs names as provided, no suffix appending
The OM2 writer now uses expositionBaseName instead of appending _total (counters) or unit suffixes. The _info suffix is enforced per the OM2 spec (MUST). Tests updated to verify OM2-specific output rather than asserting identity with OM1. Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
1 parent e63ef43 commit 8a18993

File tree

2 files changed

+110
-68
lines changed

2 files changed

+110
-68
lines changed

prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetrics2TextFormatWriter.java

Lines changed: 62 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLong;
77
import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeName;
88
import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeOpenMetricsTimestamp;
9-
import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getMetadataName;
9+
import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getExpositionBaseMetadataName;
1010
import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName;
1111

1212
import io.prometheus.metrics.config.EscapingScheme;
@@ -40,9 +40,9 @@
4040
import javax.annotation.Nullable;
4141

4242
/**
43-
* Write the OpenMetrics 2.0 text format. This is currently a skeleton implementation that produces
44-
* identical output to OpenMetrics 1.0, with infrastructure for future OM2 features. This is
45-
* experimental and subject to change as the <a
43+
* Write the OpenMetrics 2.0 text format. Unlike the OM1 writer, this writer outputs metric names as
44+
* provided by the user — no {@code _total} or unit suffix appending. The {@code _info} suffix is
45+
* enforced per the OM2 spec (MUST). This is experimental and subject to change as the <a
4646
* href="https://github.com/prometheus/docs/blob/main/docs/specs/om/open_metrics_spec_2_0.md">OpenMetrics
4747
* 2.0 specification</a> evolves.
4848
*/
@@ -171,22 +171,24 @@ public void write(OutputStream out, MetricSnapshots metricSnapshots, EscapingSch
171171
private void writeCounter(Writer writer, CounterSnapshot snapshot, EscapingScheme scheme)
172172
throws IOException {
173173
MetricMetadata metadata = snapshot.getMetadata();
174-
writeMetadata(writer, "counter", metadata, scheme);
174+
// OM2: use the name as provided by the user, no _total appending
175+
String counterName = getExpositionBaseMetadataName(metadata, scheme);
176+
writeMetadataWithName(writer, counterName, "counter", metadata);
175177
for (CounterSnapshot.CounterDataPointSnapshot data : snapshot.getDataPoints()) {
176-
writeNameAndLabels(
177-
writer, getMetadataName(metadata, scheme), "_total", data.getLabels(), scheme);
178+
writeNameAndLabels(writer, counterName, null, data.getLabels(), scheme);
178179
writeDouble(writer, data.getValue());
179180
writeScrapeTimestampAndExemplar(writer, data, data.getExemplar(), scheme);
180-
writeCreated(writer, metadata, data, scheme);
181+
writeCreated(writer, counterName, data, scheme);
181182
}
182183
}
183184

184185
private void writeGauge(Writer writer, GaugeSnapshot snapshot, EscapingScheme scheme)
185186
throws IOException {
186187
MetricMetadata metadata = snapshot.getMetadata();
187-
writeMetadata(writer, "gauge", metadata, scheme);
188+
String name = getExpositionBaseMetadataName(metadata, scheme);
189+
writeMetadataWithName(writer, name, "gauge", metadata);
188190
for (GaugeSnapshot.GaugeDataPointSnapshot data : snapshot.getDataPoints()) {
189-
writeNameAndLabels(writer, getMetadataName(metadata, scheme), null, data.getLabels(), scheme);
191+
writeNameAndLabels(writer, name, null, data.getLabels(), scheme);
190192
writeDouble(writer, data.getValue());
191193
if (exemplarsOnAllMetricTypesEnabled) {
192194
writeScrapeTimestampAndExemplar(writer, data, data.getExemplar(), scheme);
@@ -199,20 +201,21 @@ private void writeGauge(Writer writer, GaugeSnapshot snapshot, EscapingScheme sc
199201
private void writeHistogram(Writer writer, HistogramSnapshot snapshot, EscapingScheme scheme)
200202
throws IOException {
201203
MetricMetadata metadata = snapshot.getMetadata();
204+
String name = getExpositionBaseMetadataName(metadata, scheme);
202205
if (snapshot.isGaugeHistogram()) {
203-
writeMetadata(writer, "gaugehistogram", metadata, scheme);
206+
writeMetadataWithName(writer, name, "gaugehistogram", metadata);
204207
writeClassicHistogramBuckets(
205-
writer, metadata, "_gcount", "_gsum", snapshot.getDataPoints(), scheme);
208+
writer, name, "_gcount", "_gsum", snapshot.getDataPoints(), scheme);
206209
} else {
207-
writeMetadata(writer, "histogram", metadata, scheme);
210+
writeMetadataWithName(writer, name, "histogram", metadata);
208211
writeClassicHistogramBuckets(
209-
writer, metadata, "_count", "_sum", snapshot.getDataPoints(), scheme);
212+
writer, name, "_count", "_sum", snapshot.getDataPoints(), scheme);
210213
}
211214
}
212215

213216
private void writeClassicHistogramBuckets(
214217
Writer writer,
215-
MetricMetadata metadata,
218+
String name,
216219
String countSuffix,
217220
String sumSuffix,
218221
List<HistogramSnapshot.HistogramDataPointSnapshot> dataList,
@@ -225,13 +228,7 @@ private void writeClassicHistogramBuckets(
225228
for (int i = 0; i < buckets.size(); i++) {
226229
cumulativeCount += buckets.getCount(i);
227230
writeNameAndLabels(
228-
writer,
229-
getMetadataName(metadata, scheme),
230-
"_bucket",
231-
data.getLabels(),
232-
scheme,
233-
"le",
234-
buckets.getUpperBound(i));
231+
writer, name, "_bucket", data.getLabels(), scheme, "le", buckets.getUpperBound(i));
235232
writeLong(writer, cumulativeCount);
236233
Exemplar exemplar;
237234
if (i == 0) {
@@ -243,9 +240,9 @@ private void writeClassicHistogramBuckets(
243240
}
244241
// In OpenMetrics format, histogram _count and _sum are either both present or both absent.
245242
if (data.hasCount() && data.hasSum()) {
246-
writeCountAndSum(writer, metadata, data, countSuffix, sumSuffix, exemplars, scheme);
243+
writeCountAndSum(writer, name, data, countSuffix, sumSuffix, exemplars, scheme);
247244
}
248-
writeCreated(writer, metadata, data, scheme);
245+
writeCreated(writer, name, data, scheme);
249246
}
250247
}
251248

@@ -263,12 +260,13 @@ private void writeSummary(Writer writer, SummarySnapshot snapshot, EscapingSchem
263260
throws IOException {
264261
boolean metadataWritten = false;
265262
MetricMetadata metadata = snapshot.getMetadata();
263+
String name = getExpositionBaseMetadataName(metadata, scheme);
266264
for (SummarySnapshot.SummaryDataPointSnapshot data : snapshot.getDataPoints()) {
267265
if (data.getQuantiles().size() == 0 && !data.hasCount() && !data.hasSum()) {
268266
continue;
269267
}
270268
if (!metadataWritten) {
271-
writeMetadata(writer, "summary", metadata, scheme);
269+
writeMetadataWithName(writer, name, "summary", metadata);
272270
metadataWritten = true;
273271
}
274272
Exemplars exemplars = data.getExemplars();
@@ -280,13 +278,7 @@ private void writeSummary(Writer writer, SummarySnapshot snapshot, EscapingSchem
280278
int exemplarIndex = 1;
281279
for (Quantile quantile : data.getQuantiles()) {
282280
writeNameAndLabels(
283-
writer,
284-
getMetadataName(metadata, scheme),
285-
null,
286-
data.getLabels(),
287-
scheme,
288-
"quantile",
289-
quantile.getQuantile());
281+
writer, name, null, data.getLabels(), scheme, "quantile", quantile.getQuantile());
290282
writeDouble(writer, quantile.getValue());
291283
if (exemplars.size() > 0 && exemplarsOnAllMetricTypesEnabled) {
292284
exemplarIndex = (exemplarIndex + 1) % exemplars.size();
@@ -296,18 +288,20 @@ private void writeSummary(Writer writer, SummarySnapshot snapshot, EscapingSchem
296288
}
297289
}
298290
// Unlike histograms, summaries can have only a count or only a sum according to OpenMetrics.
299-
writeCountAndSum(writer, metadata, data, "_count", "_sum", exemplars, scheme);
300-
writeCreated(writer, metadata, data, scheme);
291+
writeCountAndSum(writer, name, data, "_count", "_sum", exemplars, scheme);
292+
writeCreated(writer, name, data, scheme);
301293
}
302294
}
303295

304296
private void writeInfo(Writer writer, InfoSnapshot snapshot, EscapingScheme scheme)
305297
throws IOException {
306298
MetricMetadata metadata = snapshot.getMetadata();
307-
writeMetadata(writer, "info", metadata, scheme);
299+
// OM2 spec: Info MetricFamily name MUST end in _info
300+
String infoName = ensureSuffix(getExpositionBaseMetadataName(metadata, scheme), "_info");
301+
String baseName = removeSuffix(infoName, "_info");
302+
writeMetadataWithName(writer, baseName, "info", metadata);
308303
for (InfoSnapshot.InfoDataPointSnapshot data : snapshot.getDataPoints()) {
309-
writeNameAndLabels(
310-
writer, getMetadataName(metadata, scheme), "_info", data.getLabels(), scheme);
304+
writeNameAndLabels(writer, infoName, null, data.getLabels(), scheme);
311305
writer.write("1");
312306
writeScrapeTimestampAndExemplar(writer, data, null, scheme);
313307
}
@@ -316,10 +310,11 @@ private void writeInfo(Writer writer, InfoSnapshot snapshot, EscapingScheme sche
316310
private void writeStateSet(Writer writer, StateSetSnapshot snapshot, EscapingScheme scheme)
317311
throws IOException {
318312
MetricMetadata metadata = snapshot.getMetadata();
319-
writeMetadata(writer, "stateset", metadata, scheme);
313+
String name = getExpositionBaseMetadataName(metadata, scheme);
314+
writeMetadataWithName(writer, name, "stateset", metadata);
320315
for (StateSetSnapshot.StateSetDataPointSnapshot data : snapshot.getDataPoints()) {
321316
for (int i = 0; i < data.size(); i++) {
322-
writer.write(getMetadataName(metadata, scheme));
317+
writer.write(name);
323318
writer.write('{');
324319
Labels labels = data.getLabels();
325320
for (int j = 0; j < labels.size(); j++) {
@@ -334,7 +329,7 @@ private void writeStateSet(Writer writer, StateSetSnapshot snapshot, EscapingSch
334329
if (!labels.isEmpty()) {
335330
writer.write(",");
336331
}
337-
writer.write(getMetadataName(metadata, scheme));
332+
writer.write(name);
338333
writer.write("=\"");
339334
writeEscapedString(writer, data.getName(i));
340335
writer.write("\"} ");
@@ -351,9 +346,10 @@ private void writeStateSet(Writer writer, StateSetSnapshot snapshot, EscapingSch
351346
private void writeUnknown(Writer writer, UnknownSnapshot snapshot, EscapingScheme scheme)
352347
throws IOException {
353348
MetricMetadata metadata = snapshot.getMetadata();
354-
writeMetadata(writer, "unknown", metadata, scheme);
349+
String name = getExpositionBaseMetadataName(metadata, scheme);
350+
writeMetadataWithName(writer, name, "unknown", metadata);
355351
for (UnknownSnapshot.UnknownDataPointSnapshot data : snapshot.getDataPoints()) {
356-
writeNameAndLabels(writer, getMetadataName(metadata, scheme), null, data.getLabels(), scheme);
352+
writeNameAndLabels(writer, name, null, data.getLabels(), scheme);
357353
writeDouble(writer, data.getValue());
358354
if (exemplarsOnAllMetricTypesEnabled) {
359355
writeScrapeTimestampAndExemplar(writer, data, data.getExemplar(), scheme);
@@ -365,16 +361,15 @@ private void writeUnknown(Writer writer, UnknownSnapshot snapshot, EscapingSchem
365361

366362
private void writeCountAndSum(
367363
Writer writer,
368-
MetricMetadata metadata,
364+
String name,
369365
DistributionDataPointSnapshot data,
370366
String countSuffix,
371367
String sumSuffix,
372368
Exemplars exemplars,
373369
EscapingScheme scheme)
374370
throws IOException {
375371
if (data.hasCount()) {
376-
writeNameAndLabels(
377-
writer, getMetadataName(metadata, scheme), countSuffix, data.getLabels(), scheme);
372+
writeNameAndLabels(writer, name, countSuffix, data.getLabels(), scheme);
378373
writeLong(writer, data.getCount());
379374
if (exemplarsOnAllMetricTypesEnabled) {
380375
writeScrapeTimestampAndExemplar(writer, data, exemplars.getLatest(), scheme);
@@ -383,19 +378,17 @@ private void writeCountAndSum(
383378
}
384379
}
385380
if (data.hasSum()) {
386-
writeNameAndLabels(
387-
writer, getMetadataName(metadata, scheme), sumSuffix, data.getLabels(), scheme);
381+
writeNameAndLabels(writer, name, sumSuffix, data.getLabels(), scheme);
388382
writeDouble(writer, data.getSum());
389383
writeScrapeTimestampAndExemplar(writer, data, null, scheme);
390384
}
391385
}
392386

393387
private void writeCreated(
394-
Writer writer, MetricMetadata metadata, DataPointSnapshot data, EscapingScheme scheme)
388+
Writer writer, String name, DataPointSnapshot data, EscapingScheme scheme)
395389
throws IOException {
396390
if (createdTimestampsEnabled && data.hasCreatedTimestamp()) {
397-
writeNameAndLabels(
398-
writer, getMetadataName(metadata, scheme), "_created", data.getLabels(), scheme);
391+
writeNameAndLabels(writer, name, "_created", data.getLabels(), scheme);
399392
writeOpenMetricsTimestamp(writer, data.getCreatedTimestampMillis());
400393
if (data.hasScrapeTimestamp()) {
401394
writer.write(' ');
@@ -466,27 +459,40 @@ private void writeScrapeTimestampAndExemplar(
466459
writer.write('\n');
467460
}
468461

469-
private void writeMetadata(
470-
Writer writer, String typeName, MetricMetadata metadata, EscapingScheme scheme)
471-
throws IOException {
462+
private void writeMetadataWithName(
463+
Writer writer, String name, String typeName, MetricMetadata metadata) throws IOException {
472464
writer.write("# TYPE ");
473-
writeName(writer, getMetadataName(metadata, scheme), NameType.Metric);
465+
writeName(writer, name, NameType.Metric);
474466
writer.write(' ');
475467
writer.write(typeName);
476468
writer.write('\n');
477469
if (metadata.getUnit() != null) {
478470
writer.write("# UNIT ");
479-
writeName(writer, getMetadataName(metadata, scheme), NameType.Metric);
471+
writeName(writer, name, NameType.Metric);
480472
writer.write(' ');
481473
writeEscapedString(writer, metadata.getUnit().toString());
482474
writer.write('\n');
483475
}
484476
if (metadata.getHelp() != null && !metadata.getHelp().isEmpty()) {
485477
writer.write("# HELP ");
486-
writeName(writer, getMetadataName(metadata, scheme), NameType.Metric);
478+
writeName(writer, name, NameType.Metric);
487479
writer.write(' ');
488480
writeEscapedString(writer, metadata.getHelp());
489481
writer.write('\n');
490482
}
491483
}
484+
485+
private static String ensureSuffix(String name, String suffix) {
486+
if (name.endsWith(suffix)) {
487+
return name;
488+
}
489+
return name + suffix;
490+
}
491+
492+
private static String removeSuffix(String name, String suffix) {
493+
if (name.endsWith(suffix)) {
494+
return name.substring(0, name.length() - suffix.length());
495+
}
496+
return name;
497+
}
492498
}

prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/OpenMetrics2TextFormatWriterTest.java

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ void testGetOpenMetrics2Properties() {
8787
}
8888

8989
@Test
90-
void testOutputIdenticalToOM1ForCounter() throws IOException {
90+
void testCounterNoTotalSuffix() throws IOException {
9191
MetricSnapshots snapshots =
9292
MetricSnapshots.of(
9393
CounterSnapshot.builder()
@@ -101,10 +101,37 @@ void testOutputIdenticalToOM1ForCounter() throws IOException {
101101
.build())
102102
.build());
103103

104-
String om1Output = writeWithOM1(snapshots);
105104
String om2Output = writeWithOM2(snapshots);
106105

107-
assertThat(om2Output).isEqualTo(om1Output);
106+
// OM2: name as provided, no _total appending
107+
assertThat(om2Output)
108+
.isEqualTo(
109+
"# TYPE my_counter_seconds counter\n"
110+
+ "# UNIT my_counter_seconds seconds\n"
111+
+ "# HELP my_counter_seconds Test counter\n"
112+
+ "my_counter_seconds{method=\"GET\"} 42.0\n"
113+
+ "# EOF\n");
114+
}
115+
116+
@Test
117+
void testCounterWithTotalSuffix() throws IOException {
118+
MetricSnapshots snapshots =
119+
MetricSnapshots.of(
120+
CounterSnapshot.builder()
121+
.name("requests_total")
122+
.help("Total requests")
123+
.dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder().value(100.0).build())
124+
.build());
125+
126+
String om2Output = writeWithOM2(snapshots);
127+
128+
// OM2: preserves _total if user provided it
129+
assertThat(om2Output)
130+
.isEqualTo(
131+
"# TYPE requests_total counter\n"
132+
+ "# HELP requests_total Total requests\n"
133+
+ "requests_total 100.0\n"
134+
+ "# EOF\n");
108135
}
109136

110137
@Test
@@ -220,7 +247,7 @@ void testOutputIdenticalToOM1ForStateSet() throws IOException {
220247
}
221248

222249
@Test
223-
void testOutputIdenticalToOM1WithExemplars() throws IOException {
250+
void testCounterWithExemplars() throws IOException {
224251
Exemplar exemplar =
225252
Exemplar.builder()
226253
.value(100.0)
@@ -241,14 +268,20 @@ void testOutputIdenticalToOM1WithExemplars() throws IOException {
241268
.build())
242269
.build());
243270

244-
String om1Output = writeWithOM1(snapshots);
245271
String om2Output = writeWithOM2(snapshots);
246272

247-
assertThat(om2Output).isEqualTo(om1Output);
273+
// OM2: no _total, but exemplar is preserved
274+
assertThat(om2Output)
275+
.isEqualTo(
276+
"# TYPE requests counter\n"
277+
+ "# HELP requests Total requests\n"
278+
+ "requests 1000.0 # {span_id=\"12345\",trace_id=\"abcde\"}"
279+
+ " 100.0 1672850685.829\n"
280+
+ "# EOF\n");
248281
}
249282

250283
@Test
251-
void testOutputIdenticalToOM1WithCreatedTimestamps() throws IOException {
284+
void testCounterWithCreatedTimestamps() throws IOException {
252285
MetricSnapshots snapshots =
253286
MetricSnapshots.of(
254287
CounterSnapshot.builder()
@@ -261,16 +294,19 @@ void testOutputIdenticalToOM1WithCreatedTimestamps() throws IOException {
261294
.build())
262295
.build());
263296

264-
OpenMetricsTextFormatWriter om1Writer =
265-
OpenMetricsTextFormatWriter.builder().setCreatedTimestampsEnabled(true).build();
266-
267297
OpenMetrics2TextFormatWriter om2Writer =
268298
OpenMetrics2TextFormatWriter.builder().setCreatedTimestampsEnabled(true).build();
269299

270-
String om1Output = write(snapshots, om1Writer);
271300
String om2Output = write(snapshots, om2Writer);
272301

273-
assertThat(om2Output).isEqualTo(om1Output);
302+
// OM2: no _total, _created uses the counter name directly
303+
assertThat(om2Output)
304+
.isEqualTo(
305+
"# TYPE my_counter counter\n"
306+
+ "# HELP my_counter Test counter\n"
307+
+ "my_counter 42.0\n"
308+
+ "my_counter_created 1672850385.800\n"
309+
+ "# EOF\n");
274310
}
275311

276312
@Test

0 commit comments

Comments
 (0)