Skip to content

Add an experimental OpenMetrics 2.0 exposition (gauge, info, unknown) - #1244

Open
rbizos wants to merge 3 commits into
prometheus:masterfrom
rbizos:feature/om2/base
Open

rbizos wants to merge 3 commits into
prometheus:masterfrom
rbizos:feature/om2/base

Conversation

@rbizos

@rbizos rbizos commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Part of #1210.

Preparing for the OpenMetrics 2.0 exposition by making room for a separate exposition function.
Until every metric type is supported, version 2.0.0 is still served by the 1.0 writer, so nothing
changes for users.

The 2.0 exposition supports only gauge, info and unknown metric types.

Follow-ups

  • Implement the remaining metric types, then remove the flag so that 2.0.0 is
    chosen by content negotiation alone. The current native histogram implementation will be moved to OM2.0 exposition branch.
  • Maybe move the OM2.0 code into its own module

@csmarchbanks

…flag

Signed-off-by: Raphael Bizos <r.bizos@criteo.com>
Signed-off-by: Raphael Bizos <r.bizos@criteo.com>
@dashpole
dashpole self-requested a review October 28, 2026 16:04
@rbizos

rbizos commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

For the CI I created those 2 unrelated PRs:

#1245
#1246

def generate_latest(registry, escaping=UNDERSCORES, version="1.0.0"):
'''Returns the metrics from the registry in latest text format as a string.'''
if _openmetrics_2_enabled and parse_version(version) == (2, 0, 0):
return _generate_latest_2_0(registry, escaping)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenMetrics 2.0 natively supports UTF-8 names. Should generate_latest (and choose_encoder once 2.0 is enabled) default escaping to ALLOWUTF8 when version is 2.0.0 and no explicit escaping is passed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not, this would need to be documented as breaking somehow as without changing the escaping param this could break alerts/dashboards. I do like the idea though, maybe on a separate change ?

@rbizos rbizos Nov 2, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This made me think of another similar issue, on OM2. From https://prometheus.io/docs/guides/open_metrics_2_0_migration/#counter-suffix-rules

In OM 2.0, counter Metric names should end in _total. It is no longer mandatory.

Currently it gets added automatically and the users have no control. The way it works is that the suffix is stripped before hand and added while serializing the metrics. This would require to be reworked totally to allow for user choice. let me add that to the issue and do that separately from the implem of the actual metrics :)

escaped_name = escape_metric_name(family_name, escaping)
output = [f'# TYPE {escaped_name} {metric.type}\n']
if metric.unit:
output.append(f'# UNIT {escaped_name} {metric.unit}\n')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the OpenMetrics 2.0 ABNF (# UNIT <name> *metricname-char LF), units still only allow [a-zA-Z0-9_:] characters even when UTF-8 metric names are enabled. Because Metric.__init__ validates unit via _validate_metric_name(name) (which allows arbitrary UTF-8 by default), something like Gauge('foo', 'help', unit='req/s') succeeds at construction time and emits # UNIT "foo_req/s" req/s here. Should we validate or escape metric.unit so it conforms to *metricname-char?


def _format_sample_2_0(family_name, sample, escaping):
"""Formats a sample as ``name{labels} value [timestamp]``, named after its family as OpenMetrics 2.0 requires."""
line = f'{_format_name_and_labels_2_0(family_name, sample.labels, escaping)} {floatToGoString(sample.value)}'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a custom collector constructs a Metric and adds a sample whose sample.name does not match family_name, this will silently rewrite the sample name to family_name. Should we validate that sample.name == family_name and raise a ValueError on mismatch?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point, here I'm a bit greedy as I force it to the family_name so that its always a OM2 valid output. I'm very hesitant to failing the full scrape for a sample name mismatching.

@rbizos

rbizos commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor Author

@dashpole Thanks for your comments ! Definitely good points, I added some tasks in (#1210) to track them, I think they might require some dedicated discussions (like how to deal with errors that were not there on OM1/text that are only there if scraped with OM2)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants