Skip to content

fix: drop +Inf bound from OpenTelemetry classic histogram boundaries - #2458

Merged
zeitlinger merged 2 commits into
prometheus:mainfrom
KR-Ravindra:fix/otel-classic-histogram-inf-boundary
Sep 14, 2026
Merged

fix: drop +Inf bound from OpenTelemetry classic histogram boundaries#2458
zeitlinger merged 2 commits into
prometheus:mainfrom
KR-Ravindra:fix/otel-classic-histogram-inf-boundary

Conversation

@KR-Ravindra

Copy link
Copy Markdown
Contributor

Problem

Classic histograms exported through the OpenTelemetry bridge carry the Prometheus +Inf upper
bound inside HistogramPointData.getBoundaries(), and getCounts() has the same length as
getBoundaries(). The OpenTelemetry data model expects finite explicit bounds with
counts.size() == boundaries.size() + 1 (the +Inf bucket is implicit).

Root cause

PrometheusClassicHistogram.makeBoundaries()
(prometheus-metrics-exporter-opentelemetry/.../otelmodel/PrometheusClassicHistogram.java:70-76)
copies every ClassicHistogramBuckets upper bound, including the trailing +Inf.
makeCounts() is already correct: ClassicHistogramBuckets counts are non-cumulative and the
last entry is the +Inf bucket count.

Fix

Skip the +Inf upper bound in makeBoundaries(). Counts are unchanged, so for Prometheus
buckets [1, 5, +Inf] / [c0, c1, c2] the OTel point is now boundaries [1, 5],
counts [c0, c1, c2], total c0 + c1 + c2 (as described in #2416).

The existing ExportTest.histogram expectation hasBucketBoundaries(1, 2, 3, +Inf) encoded
the old behaviour and is updated to hasBucketBoundaries(1, 2, 3); its
hasBucketCounts(1, 0, 0, 0) is unchanged.

How tested

New PrometheusClassicHistogramTest (buckets [1, 5, +Inf], counts [2, 3, 4]) checks the
HistogramPointData, feeds it through the OTel SDK's own ImmutableHistogramPointData.create
validation, and parses the OTLP payload produced by MetricsRequestMarshaler.

Before the fix:

Expecting actual: [1.0, 5.0, Infinity]  to contain exactly: [1.0, 5.0]   (point and OTLP explicit_bounds)
java.lang.IllegalArgumentException: invalid counts: size should be 4 instead of 3
    at io.opentelemetry.sdk.metrics.internal.data.ImmutableHistogramPointData.create
Tests run: 4, Failures: 4

After the fix (./mvnw test -pl prometheus-metrics-exporter-opentelemetry -Dcoverage.skip=true):

Tests run: 39, Failures: 0, Errors: 0, Skipped: 0 -- BUILD SUCCESS

Links

This change was prepared with an AI agent operated by KR-Ravindra, who reviewed and tested it.

OpenTelemetry explicit bucket boundaries must be finite, with the +Inf
bucket implicit (counts.size() == boundaries.size() + 1).
PrometheusClassicHistogram.makeBoundaries() copied the trailing +Inf
upper bound, so the exported HistogramPointData had as many counts as
boundaries and the OTLP explicit_bounds contained Infinity.

Skip the +Inf bound; the non-cumulative counts already end with the
+Inf bucket count.

Signed-off-by: KR Ravindra <42912207+KR-Ravindra@users.noreply.github.com>
@KR-Ravindra

Copy link
Copy Markdown
Contributor Author

Round 1 self-review.

Verified against main:

  1. makeBoundaries() in otelmodel/PrometheusClassicHistogram.java copies every upper bound including +Inf, while makeCounts() already yields one non-cumulative count per bucket, so today boundaries.size() == counts.size() with an infinite last bound. The fix skips only the +Inf bound; the +Inf count stays as the last entry, giving counts == bounds + 1, the shape jaydeluca described on OpenTelemetry bridge: classic histograms exported with +Inf boundary included, violating counts == boundaries + 1 contract #2416.
  2. Ran the module tests locally on the PR head: 18/18 pass in PrometheusClassicHistogramTest + ExportTest. With the main-branch PrometheusClassicHistogram.java restored, 4 fail, including invalid counts: size should be 4 instead of 3 from the OTel SDK's own ImmutableHistogramPointData.create.
  3. The new test also marshals through MetricsRequestMarshaler and parses the OTLP request back, asserting explicit_bounds=[1,5], bucket_counts=[2,3,4], count=9; that is the reproducer OpenTelemetry bridge: classic histograms exported with +Inf boundary included, violating counts == boundaries + 1 contract #2416 asked for.
  4. google-java-format 1.36.1 (the mise.toml pin) reports no changes on the three files. DCO passes; the remaining workflows await maintainer approval.

No duplicate: #2416 cross-references only this PR. Marking ready.

@KR-Ravindra
KR-Ravindra marked this pull request as ready for review September 13, 2026 06:56

@zeitlinger zeitlinger left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Verified that ClassicHistogramBuckets always has one sorted final +Inf bucket with non-cumulative counts. Dropping only that explicit boundary while retaining its count satisfies the OTel counts = boundaries + 1 contract, and the SDK-construction plus OTLP serialization tests cover the model and wire representation.

@zeitlinger
zeitlinger merged commit a3bce9a into prometheus:main Sep 14, 2026
23 checks passed
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