Skip to content

Commit 5ce2b57

Browse files
authored
feat: OM2 writer outputs names as provided, no suffix appending (#1957)
## Summary The OM2 writer now uses `expositionBaseName` directly instead of appending `_total` (counters) or unit suffixes. The `_info` suffix is enforced per the OM2 spec (MUST). Part of #1942. ### Key table | User provides | OM1 | OM2 | |---|---|---| | `Counter("events")` | `events_total` | `events` | | `Counter("events_total")` | `events_total` | `events_total` | | `Counter("req").unit(BYTES)` | `req_bytes_total` | `req_bytes` | | `Counter("req_bytes").unit(BYTES)` | `req_bytes_total` | `req_bytes` | | `Gauge("events_total")` | `events_total` | `events_total` | | `Info("target")` | `target_info` | `target_info` | ### PR stack 1. Core model + OM1/protobuf writers (#1955) 2. OTel `preserve_names` (independent) 3. **This PR** — OM2 writer no-suffix ## Test plan - [x] `mise run compile` passes - [x] OM2-specific tests verify no `_total`/unit suffix appending - [x] `_info` suffix enforcement tested Part of #1912. --------- Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
1 parent ce5867b commit 5ce2b57

File tree

2 files changed

+111
-71
lines changed

2 files changed

+111
-71
lines changed

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

Lines changed: 55 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+
// In OM2, TYPE/HELP use the same name as the data lines.
301+
String infoName = ensureSuffix(getExpositionBaseMetadataName(metadata, scheme), "_info");
302+
writeMetadataWithName(writer, infoName, "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,33 @@ 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+
}
492491
}

0 commit comments

Comments
 (0)