diff --git a/CHANGELOG.md b/CHANGELOG.md index 07d3121668e..1935034aac0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#4494](https://github.com/open-telemetry/opentelemetry-python/pull/4494)) - Improve CI by cancelling stale runs and setting timeouts ([#4498](https://github.com/open-telemetry/opentelemetry-python/pull/4498)) +- Fix ExplicitBucketHistogramAggregation to handle multiple explicit bucket boundaries advisories + ([#4521](https://github.com/open-telemetry/opentelemetry-python/pull/4521)) ## Version 1.31.0/0.52b0 (2025-03-12) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py index 8443d9516cf..1779dac0bba 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py @@ -1387,18 +1387,17 @@ def _create_aggregation( AggregationTemporality.CUMULATIVE ) - if self._boundaries is None: - self._boundaries = ( - instrument._advisory.explicit_bucket_boundaries - or _DEFAULT_EXPLICIT_BUCKET_HISTOGRAM_AGGREGATION_BOUNDARIES - ) + if self._boundaries is not None: + boundaries = self._boundaries + else: + boundaries = instrument._advisory.explicit_bucket_boundaries return _ExplicitBucketHistogramAggregation( attributes, instrument_aggregation_temporality, start_time_unix_nano, reservoir_factory(_ExplicitBucketHistogramAggregation), - self._boundaries, + boundaries, self._record_min_max, ) diff --git a/opentelemetry-sdk/tests/metrics/integration_test/test_histogram_advisory_explicit_buckets.py b/opentelemetry-sdk/tests/metrics/integration_test/test_histogram_advisory_explicit_buckets.py index 2f46dca87a6..569d7fd1c2c 100644 --- a/opentelemetry-sdk/tests/metrics/integration_test/test_histogram_advisory_explicit_buckets.py +++ b/opentelemetry-sdk/tests/metrics/integration_test/test_histogram_advisory_explicit_buckets.py @@ -15,6 +15,9 @@ from unittest import TestCase from opentelemetry.sdk.metrics import MeterProvider +from opentelemetry.sdk.metrics._internal.aggregation import ( + _DEFAULT_EXPLICIT_BUCKET_HISTOGRAM_AGGREGATION_BOUNDARIES, +) from opentelemetry.sdk.metrics._internal.instrument import Histogram from opentelemetry.sdk.metrics.export import InMemoryMetricReader from opentelemetry.sdk.metrics.view import ( @@ -164,3 +167,78 @@ def test_explicit_aggregation(self): self.assertEqual( metric.data.data_points[0].explicit_bounds, (1.0, 2.0, 3.0) ) + + def test_explicit_aggregation_multiple_histograms(self): + reader = InMemoryMetricReader( + preferred_aggregation={ + Histogram: ExplicitBucketHistogramAggregation() + } + ) + meter_provider = MeterProvider( + metric_readers=[reader], + ) + meter = meter_provider.get_meter("testmeter") + + histogram1 = meter.create_histogram( + "testhistogram1", + explicit_bucket_boundaries_advisory=[1.0, 2.0, 3.0], + ) + histogram1.record(1, {"label": "value"}) + histogram1.record(2, {"label": "value"}) + histogram1.record(3, {"label": "value"}) + + histogram2 = meter.create_histogram( + "testhistogram2", + explicit_bucket_boundaries_advisory=[4.0, 5.0, 6.0], + ) + histogram2.record(4, {"label": "value"}) + histogram2.record(5, {"label": "value"}) + histogram2.record(6, {"label": "value"}) + + metrics = reader.get_metrics_data() + self.assertEqual(len(metrics.resource_metrics), 1) + self.assertEqual(len(metrics.resource_metrics[0].scope_metrics), 1) + self.assertEqual( + len(metrics.resource_metrics[0].scope_metrics[0].metrics), 2 + ) + metric1 = metrics.resource_metrics[0].scope_metrics[0].metrics[0] + self.assertEqual(metric1.name, "testhistogram1") + self.assertEqual( + metric1.data.data_points[0].explicit_bounds, (1.0, 2.0, 3.0) + ) + metric2 = metrics.resource_metrics[0].scope_metrics[0].metrics[1] + self.assertEqual(metric2.name, "testhistogram2") + self.assertEqual( + metric2.data.data_points[0].explicit_bounds, (4.0, 5.0, 6.0) + ) + + def test_explicit_aggregation_default_boundaries(self): + reader = InMemoryMetricReader( + preferred_aggregation={ + Histogram: ExplicitBucketHistogramAggregation() + } + ) + meter_provider = MeterProvider( + metric_readers=[reader], + ) + meter = meter_provider.get_meter("testmeter") + + histogram = meter.create_histogram( + "testhistogram", + ) + histogram.record(1, {"label": "value"}) + histogram.record(2, {"label": "value"}) + histogram.record(3, {"label": "value"}) + + metrics = reader.get_metrics_data() + self.assertEqual(len(metrics.resource_metrics), 1) + self.assertEqual(len(metrics.resource_metrics[0].scope_metrics), 1) + self.assertEqual( + len(metrics.resource_metrics[0].scope_metrics[0].metrics), 1 + ) + metric = metrics.resource_metrics[0].scope_metrics[0].metrics[0] + self.assertEqual(metric.name, "testhistogram") + self.assertEqual( + metric.data.data_points[0].explicit_bounds, + _DEFAULT_EXPLICIT_BUCKET_HISTOGRAM_AGGREGATION_BOUNDARIES, + )