From e7d77546277c61dcd57f7c03644ab25fc7136e73 Mon Sep 17 00:00:00 2001 From: Rudra Dudhat Date: Mon, 22 Jun 2026 19:22:13 +0530 Subject: [PATCH 1/6] fix: raise ValueError for unsorted ExplicitBucketHistogramAggregation boundaries --- .../sdk/metrics/_internal/aggregation.py | 5 ++++ .../tests/metrics/test_aggregation.py | 24 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py index dea213f4282..6fc032c7c9c 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py @@ -475,6 +475,11 @@ def __init__( instrument_aggregation_temporality ) self._start_time_unix_nano = start_time_unix_nano + for i in range(1, len(boundaries)): + if boundaries[i - 1] >= boundaries[i]: + raise ValueError( + f"boundaries must be in increasing order, got {list(boundaries)}" + ) self._boundaries = tuple(boundaries) self._record_min_max = record_min_max diff --git a/opentelemetry-sdk/tests/metrics/test_aggregation.py b/opentelemetry-sdk/tests/metrics/test_aggregation.py index 1f11393cee0..948ee1e1661 100644 --- a/opentelemetry-sdk/tests/metrics/test_aggregation.py +++ b/opentelemetry-sdk/tests/metrics/test_aggregation.py @@ -477,6 +477,30 @@ def test_create_aggregation_on_instrument_without_boundaries(self): ) self.assertIsInstance(result, _ExplicitBucketHistogramAggregation) + def test_unsorted_boundaries_raise(self): + with self.assertRaises(ValueError): + _ExplicitBucketHistogramAggregation( + Mock(), + AggregationTemporality.DELTA, + 0, + _default_reservoir_factory( + _ExplicitBucketHistogramAggregation + ), + boundaries=[100, 10, 50], + ) + + def test_duplicate_boundaries_raise(self): + with self.assertRaises(ValueError): + _ExplicitBucketHistogramAggregation( + Mock(), + AggregationTemporality.DELTA, + 0, + _default_reservoir_factory( + _ExplicitBucketHistogramAggregation + ), + boundaries=[10, 50, 50, 100], + ) + class TestAggregationFactory(TestCase): def test_sum_factory(self): From ec560d45221cd04dc7ffebbe711f58aba0f32e57 Mon Sep 17 00:00:00 2001 From: Rudra Dudhat Date: Mon, 22 Jun 2026 19:23:30 +0530 Subject: [PATCH 2/6] docs: add changelog fragment for #5340 --- .changelog/5340.fixed | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changelog/5340.fixed diff --git a/.changelog/5340.fixed b/.changelog/5340.fixed new file mode 100644 index 00000000000..5921973ce66 --- /dev/null +++ b/.changelog/5340.fixed @@ -0,0 +1 @@ +`opentelemetry-sdk`: raise `ValueError` when `ExplicitBucketHistogramAggregation` boundaries are not strictly increasing From a94dac0b0a28766e9ea46347eb060f8e7b9facc9 Mon Sep 17 00:00:00 2001 From: Rudra Dudhat Date: Mon, 22 Jun 2026 19:54:56 +0530 Subject: [PATCH 3/6] fix: also reject NaN and infinite boundary values --- .../sdk/metrics/_internal/aggregation.py | 7 +++- .../tests/metrics/test_aggregation.py | 36 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py index 6fc032c7c9c..cf5dbfd5693 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py @@ -9,7 +9,7 @@ from enum import IntEnum from functools import partial from logging import getLogger -from math import inf +from math import inf, isfinite from threading import Lock from typing import ( Generic, @@ -475,6 +475,11 @@ def __init__( instrument_aggregation_temporality ) self._start_time_unix_nano = start_time_unix_nano + for b in boundaries: + if not isfinite(b): + raise ValueError( + f"boundaries must be finite, got {b}" + ) for i in range(1, len(boundaries)): if boundaries[i - 1] >= boundaries[i]: raise ValueError( diff --git a/opentelemetry-sdk/tests/metrics/test_aggregation.py b/opentelemetry-sdk/tests/metrics/test_aggregation.py index 948ee1e1661..12a42b31771 100644 --- a/opentelemetry-sdk/tests/metrics/test_aggregation.py +++ b/opentelemetry-sdk/tests/metrics/test_aggregation.py @@ -501,6 +501,42 @@ def test_duplicate_boundaries_raise(self): boundaries=[10, 50, 50, 100], ) + def test_nan_boundary_raises(self): + with self.assertRaises(ValueError): + _ExplicitBucketHistogramAggregation( + Mock(), + AggregationTemporality.DELTA, + 0, + _default_reservoir_factory( + _ExplicitBucketHistogramAggregation + ), + boundaries=[10, float("nan"), 100], + ) + + def test_inf_boundary_raises(self): + with self.assertRaises(ValueError): + _ExplicitBucketHistogramAggregation( + Mock(), + AggregationTemporality.DELTA, + 0, + _default_reservoir_factory( + _ExplicitBucketHistogramAggregation + ), + boundaries=[10, 50, float("inf")], + ) + + def test_negative_inf_boundary_raises(self): + with self.assertRaises(ValueError): + _ExplicitBucketHistogramAggregation( + Mock(), + AggregationTemporality.DELTA, + 0, + _default_reservoir_factory( + _ExplicitBucketHistogramAggregation + ), + boundaries=[float("-inf"), 50, 100], + ) + class TestAggregationFactory(TestCase): def test_sum_factory(self): From 6752f2fc7f9d054e7745c5818ed6c28c26cc2c88 Mon Sep 17 00:00:00 2001 From: Rudra Dudhat Date: Tue, 23 Jun 2026 15:14:40 +0530 Subject: [PATCH 4/6] Update .changelog/5340.fixed Co-authored-by: Riccardo Magliocchetti --- .changelog/5340.fixed | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/5340.fixed b/.changelog/5340.fixed index 5921973ce66..eb1ba3b8a0e 100644 --- a/.changelog/5340.fixed +++ b/.changelog/5340.fixed @@ -1 +1 @@ -`opentelemetry-sdk`: raise `ValueError` when `ExplicitBucketHistogramAggregation` boundaries are not strictly increasing +`opentelemetry-sdk`: raise `ValueError` when `ExplicitBucketHistogramAggregation` boundaries are not strictly increasing or finite From e27820cd46c58805500f9c3b7ed2e88fbae2321a Mon Sep 17 00:00:00 2001 From: Rudra Dudhat Date: Tue, 23 Jun 2026 15:24:32 +0530 Subject: [PATCH 5/6] fix: single-pass boundary validation with clearer error messages --- .../sdk/metrics/_internal/aggregation.py | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py index cf5dbfd5693..b63c3fd3523 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py @@ -9,7 +9,7 @@ from enum import IntEnum from functools import partial from logging import getLogger -from math import inf, isfinite +from math import inf, isnan from threading import Lock from typing import ( Generic, @@ -464,6 +464,21 @@ def __init__( boundaries = ( _DEFAULT_EXPLICIT_BUCKET_HISTOGRAM_AGGREGATION_BOUNDARIES ) + if boundaries: + if isnan(boundaries[0]): + raise ValueError("invalid boundary: NaN") + if boundaries[0] == -inf: + raise ValueError("invalid boundary: -Inf") + for i in range(1, len(boundaries)): + if isnan(boundaries[i]): + raise ValueError("invalid boundary: NaN") + if boundaries[i - 1] >= boundaries[i]: + raise ValueError( + f"boundaries must be strictly increasing:" + f" {boundaries[i - 1]} >= {boundaries[i]}" + ) + if boundaries[-1] == inf: + raise ValueError("invalid boundary: +Inf") super().__init__( attributes, reservoir_builder=partial( @@ -475,16 +490,6 @@ def __init__( instrument_aggregation_temporality ) self._start_time_unix_nano = start_time_unix_nano - for b in boundaries: - if not isfinite(b): - raise ValueError( - f"boundaries must be finite, got {b}" - ) - for i in range(1, len(boundaries)): - if boundaries[i - 1] >= boundaries[i]: - raise ValueError( - f"boundaries must be in increasing order, got {list(boundaries)}" - ) self._boundaries = tuple(boundaries) self._record_min_max = record_min_max From 9ed5b8f67bb691ba82a4f23ba78633a94026c385 Mon Sep 17 00:00:00 2001 From: Rudra Dudhat Date: Wed, 24 Jun 2026 21:51:14 +0530 Subject: [PATCH 6/6] Update aggregation.py Co-authored-by: Lukas Hering <40302054+herin049@users.noreply.github.com> --- .../sdk/metrics/_internal/aggregation.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py index b63c3fd3523..9c12e1a3cdf 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py @@ -465,20 +465,11 @@ def __init__( _DEFAULT_EXPLICIT_BUCKET_HISTOGRAM_AGGREGATION_BOUNDARIES ) if boundaries: - if isnan(boundaries[0]): - raise ValueError("invalid boundary: NaN") - if boundaries[0] == -inf: - raise ValueError("invalid boundary: -Inf") - for i in range(1, len(boundaries)): - if isnan(boundaries[i]): - raise ValueError("invalid boundary: NaN") - if boundaries[i - 1] >= boundaries[i]: - raise ValueError( - f"boundaries must be strictly increasing:" - f" {boundaries[i - 1]} >= {boundaries[i]}" - ) - if boundaries[-1] == inf: - raise ValueError("invalid boundary: +Inf") + for i, x in enumerate(boundaries): + if not math.isfinite(x): + raise ValueError(f"invalid boundary: {x!r}") + if i and boundaries[i - 1] >= x: + raise ValueError("boundaries must be strictly increasing") super().__init__( attributes, reservoir_builder=partial(