Skip to content

Experimental OpenMetrics 2.0 support #1912

@zeitlinger

Description

@zeitlinger

The OpenMetrics 2.0 spec is nearing completion. We should start implementing experimental support to validate the design and provide early feedback to the spec authors.

All features are behind opt-in flags (off by default) so there's no risk to existing users. The spec is still in draft, so the API is explicitly experimental and subject to change.

Flag design

Top-level gate + individual feature flags. The top-level gate (enableOpenMetrics2())
sets enabled=true implicitly and enables the sub-flags. Each sub-flag
independently controls one OM2 feature.

Java builder API:

PrometheusProperties config = PrometheusProperties.builder()
    .enableOpenMetrics2(om2 -> om2
        .contentNegotiation(true)         // version=2.0.0 content type
        .compositeValues(true)            // single-line histogram/summary + st@
        .exemplarCompliance(true)         // mandatory timestamps, no size limit
        .nativeHistograms(true)           // exponential buckets
    )
    .build();

// Or enable everything at once
PrometheusProperties config = PrometheusProperties.builder()
    .enableOpenMetrics2(om2 -> om2.enableAll())
    .build();

Properties equivalent:

io.prometheus.openmetrics2.enabled=true
io.prometheus.openmetrics2.content_negotiation=true
io.prometheus.openmetrics2.composite_values=true
io.prometheus.openmetrics2.exemplar_compliance=true
io.prometheus.openmetrics2.native_histograms=true

For the minimal OM2 experience (names as provided, no suffix appending):

io.prometheus.openmetrics2.enabled=true
Flag Description Spec reference
enabled Activate the OM2 writer. Required — feature flags below have no effect without it. Set implicitly by enableOpenMetrics2()
contentNegotiation Gate OM2 features behind content negotiation: only apply them when the scraper requests version=2.0.0, and return OM1 format when the scraper requests OM1. Without this flag, OM2 features are applied even if the scraper requests OM1 Content Type
compositeValues Single-line Histogram/Summary/GaugeHistogram with inline st@ start timestamp. Also: reserved label prefix __, Histogram Count/Sum always present, bucket values may be float CompositeValue, Overall Structure
exemplarCompliance Exemplar timestamps always emitted (MUST in OM2), 128-char LabelSet hard limit removed Exemplar
nativeHistograms Exponential buckets with schema, zero threshold/count, pos/neg spans Native Buckets

All flags default to false.

Suffix handling (_total, unit suffixes) is not an OM2 config flag — it is handled
at scrape time by each format writer. See #1941 for the design.

PR structure

PR 1: Flag infrastructure (#1939) ✅
│
└── PR 2: OM2 writer skeleton (#1951) ✅
    │
    ├── PR 3: enabled flag (#1953)
    │   │
    │   ├── PR 4: Suffix handling (split from #1947):
    │   │   ├── PR 4a: Core + OM1 writers (#1955)
    │   │   ├── PR 4b: OTel preserve_names (#1956)
    │   │   └── PR 4c: OM2 writer no-suffix (#1957)
    │   │
    │   └── PR 8: Documentation (#1954)
    │
    ├── PR 5: contentNegotiation
    │
    ├── PR 6: compositeValues + exemplarCompliance
    │
    └── PR 7: nativeHistograms

PR 1: Flag infrastructure (#1939) ✅

  • Add enableOpenMetrics2() to PrometheusProperties.Builder
  • Add OpenMetrics2Properties class with all flag fields (defaulting to false)
  • Wire up properties parsing for io.prometheus.openmetrics2.*
  • No behavioral changes — just the config plumbing

PR 2: OM2 writer skeleton (#1951) ✅

  • OpenMetrics2TextFormatWriter implementing ExpositionFormatWriter
  • Wired into ExpositionFormats: when OM2 is enabled, selected for OpenMetrics requests (masquerades as OM1 for testability with current Prometheus)
  • Takes OpenMetrics2Properties from feat: Add OpenMetrics2 configuration support #1939 in builder

PR 3: enabled flag (#1953)

  • Add io.prometheus.openmetrics2.enabled as the explicit gate for OM2 writer selection
  • Feature flags alone no longer activate OM2 — enabled=true is required
  • enableOpenMetrics2() programmatic configurator sets enabled=true implicitly

PR 4a: Core + OM1 writers (#1955)

  • Move suffix handling to scrape time — OM1 smart-appends _total/_info/unit suffixes (skips if already present)
  • MetricMetadata stores originalName and expositionBaseName
  • Counter/Info builders no longer strip _total/_info at creation time
  • Remove all reserved metric name suffixes from PrometheusNaming
  • Cross-format collision detection in PrometheusRegistry
  • See Move suffix handling from creation time to scrape time #1942 for design

PR 4b: OTel preserve_names (#1956)

  • Add preserve_names config to ExporterOpenTelemetryProperties
  • Legacy path (default): strips _total + unit suffix
  • preserve_names=true: passes names through exactly as the user wrote them
  • Stacked on feat: move suffix handling to scrape time #1955

PR 4c: OM2 writer no-suffix (#1957)

PR 8: Documentation (#1954)

  • Document OM2 preview features and configuration
  • Document OTel preserve_names flag

PR 5: Content negotiation

  • contentNegotiation: only return OM2 format when the scraper explicitly requests version=2.0.0. Without this flag, OM2 features are applied even if the scraper requests OM1
  • Spec: Content Type

PR 6: CompositeValue + start timestamp + exemplar compliance

  • compositeValues:
    • Histogram as single line: foo {count:17,sum:324789.3,bucket:[0.1:8,0.25:10,0.5:11,1.0:14,+Inf:17]} 1.0 st@0.5
    • Summary as single line: bar {count:17,sum:324789.3,quantile:[0.95:123.7,0.99:150.0]}
    • GaugeHistogram as single line with count/sum (not gcount/gsum)
    • _created lines replaced by inline st@ start timestamp
    • _count, _sum, _bucket suffixed lines no longer emitted for these types
    • Reserved label prefix changed from _ to __
    • Histogram/GaugeHistogram Count and Sum always present (MUST, was SHOULD for Sum)
    • Histogram bucket values may be float (SHOULD be integer, was MUST)
    • Spec: CompositeValue, Histogram, Summary
  • exemplarCompliance:
    • Exemplar timestamps always emitted (OM2: MUST, was MAY in OM1)
    • 128-char LabelSet hard limit removed

PR 7: Native histograms

  • nativeHistograms:
    • Exponential bucket model: schema (-4 to 8), zero threshold/count, positive/negative spans and buckets
    • Text serialization: {count:X,sum:X,schema:N,zero_threshold:F,zero_count:X,positive_spans:[...],positive_buckets:[...]}
    • Can coexist with classic buckets on separate lines (native line MUST come first)
    • GaugeHistogram also supports native buckets (same syntax)
    • Multiple exemplars per native histogram sample
    • Spec: Native Buckets, Histogram
  • No known blockers:

Notes

  • UTF-8 metric/label names are already supported (shipped in a previous release)
  • The OM2 spec is still draft — some details may change, but no known blockers for the features covered here
  • See also: OM 2.0 upgrade guide for SDK maintainers

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions