diff --git a/.changelog/5895.fixed b/.changelog/5895.fixed new file mode 100644 index 00000000000..8095efb304d --- /dev/null +++ b/.changelog/5895.fixed @@ -0,0 +1 @@ +`opentelemetry-sdk`: add empty explicit bucket boundaries advisory to duration histograms diff --git a/exporter/opentelemetry-exporter-otlp-common/src/opentelemetry/exporter/otlp/common/_exporter_metrics.py b/exporter/opentelemetry-exporter-otlp-common/src/opentelemetry/exporter/otlp/common/_exporter_metrics.py index 8d51674ef47..28bbc16a8a4 100644 --- a/exporter/opentelemetry-exporter-otlp-common/src/opentelemetry/exporter/otlp/common/_exporter_metrics.py +++ b/exporter/opentelemetry-exporter-otlp-common/src/opentelemetry/exporter/otlp/common/_exporter_metrics.py @@ -18,11 +18,11 @@ OtelComponentTypeValues, ) from opentelemetry.semconv._incubating.metrics.otel_metrics import ( + OTEL_SDK_EXPORTER_OPERATION_DURATION, create_otel_sdk_exporter_log_exported, create_otel_sdk_exporter_log_inflight, create_otel_sdk_exporter_metric_data_point_exported, create_otel_sdk_exporter_metric_data_point_inflight, - create_otel_sdk_exporter_operation_duration, create_otel_sdk_exporter_span_exported, create_otel_sdk_exporter_span_inflight, ) @@ -110,7 +110,12 @@ def __init__( meter = meter_provider.get_meter("opentelemetry-sdk") self._inflight = create_inflight(meter) self._exported = create_exported(meter) - self._duration = create_otel_sdk_exporter_operation_duration(meter) + self._duration = meter.create_histogram( + name=OTEL_SDK_EXPORTER_OPERATION_DURATION, + description="The duration of exporting a batch of telemetry records.", + unit="s", + explicit_bucket_boundaries_advisory=[], + ) @contextmanager def export_operation(self, num_items: int) -> Iterator[ExportResult]: diff --git a/exporter/opentelemetry-exporter-otlp-common/tests/test_exporter_metrics.py b/exporter/opentelemetry-exporter-otlp-common/tests/test_exporter_metrics.py index f4830e54e81..e77883ef178 100644 --- a/exporter/opentelemetry-exporter-otlp-common/tests/test_exporter_metrics.py +++ b/exporter/opentelemetry-exporter-otlp-common/tests/test_exporter_metrics.py @@ -51,6 +51,13 @@ def test_factory_returns_exporter_metrics_when_enabled(self): self.assertIsInstance(metrics, ExporterMetrics) meter_provider.get_meter.assert_called_once_with("opentelemetry-sdk") + meter = meter_provider.get_meter.return_value + meter.create_histogram.assert_called_once_with( + name="otel.sdk.exporter.operation.duration", + description="The duration of exporting a batch of telemetry records.", + unit="s", + explicit_bucket_boundaries_advisory=[], + ) def test_noop_export_operation_yields_result(self): metrics = NoOpExporterMetrics() diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_span_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_span_exporter.py index b0dbb47a7cc..745eff3a1f9 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_span_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_span_exporter.py @@ -683,6 +683,7 @@ def test_retry_timeout_records_metrics(self): metrics = sorted(scope_metrics.metrics, key=lambda m: m.name) self.assertEqual(len(metrics), 3) self.assertEqual(metrics[0].name, "otel.sdk.exporter.operation.duration") + self.assertEqual(metrics[0].data.data_points[0].explicit_bounds, ()) self.assert_standard_metric_attrs(metrics[0].data.data_points[0].attributes) self.assertEqual( metrics[0].data.data_points[0].attributes["http.response.status_code"], @@ -716,6 +717,7 @@ def test_export_connection_error_records_metrics(self): scope_metrics = metrics_data.resource_metrics[0].scope_metrics[0] metrics = sorted(scope_metrics.metrics, key=lambda m: m.name) self.assertEqual(metrics[0].name, "otel.sdk.exporter.operation.duration") + self.assertEqual(metrics[0].data.data_points[0].explicit_bounds, ()) self.assertIn("error.type", metrics[0].data.data_points[0].attributes) self.assertNotIn( "http.response.status_code", diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/export/_metric_reader_metrics.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/export/_metric_reader_metrics.py index 019f1cc1725..ad465eccb9a 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/export/_metric_reader_metrics.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/export/_metric_reader_metrics.py @@ -10,7 +10,7 @@ OTEL_COMPONENT_TYPE, ) from opentelemetry.semconv._incubating.metrics.otel_metrics import ( - create_otel_sdk_metric_reader_collection_duration, + OTEL_SDK_METRIC_READER_COLLECTION_DURATION, ) _component_counter = Counter() @@ -37,7 +37,12 @@ def __init__(self, component_type: str, meter_provider: MeterProvider) -> None: OTEL_COMPONENT_NAME: f"{component_type}/{count}", } - self._collection_duration = create_otel_sdk_metric_reader_collection_duration(meter) + self._collection_duration = meter.create_histogram( + name=OTEL_SDK_METRIC_READER_COLLECTION_DURATION, + description="The duration of the collect operation of the metric reader.", + unit="s", + explicit_bucket_boundaries_advisory=[], + ) def record_collection(self, duration: float) -> None: self._collection_duration.record(duration, self._standard_attrs) diff --git a/opentelemetry-sdk/tests/metrics/test_in_memory_metric_reader.py b/opentelemetry-sdk/tests/metrics/test_in_memory_metric_reader.py index 47aea81a424..aaedcba7793 100644 --- a/opentelemetry-sdk/tests/metrics/test_in_memory_metric_reader.py +++ b/opentelemetry-sdk/tests/metrics/test_in_memory_metric_reader.py @@ -5,7 +5,7 @@ from time import sleep from unittest import TestCase -from unittest.mock import Mock +from unittest.mock import Mock, patch from opentelemetry.metrics import Observation from opentelemetry.sdk.metrics import Counter, MeterProvider @@ -166,6 +166,21 @@ def test_synchronous_gauge_cumulative_multiple_collect(self): [({"key": "value"}, 5)], ) + @patch.dict("os.environ", {"OTEL_PYTHON_SDK_INTERNAL_METRICS_ENABLED": "true"}) + def test_metric_reader_metrics(self): + reader = InMemoryMetricReader() + meter_provider = Mock() + meter = Mock() + meter_provider.get_meter.return_value = meter + reader._set_meter_provider(meter_provider) + meter_provider.get_meter.assert_called_once_with("opentelemetry-sdk") + meter.create_histogram.assert_called_once_with( + name="otel.sdk.metric_reader.collection.duration", + description="The duration of the collect operation of the metric reader.", + unit="s", + explicit_bucket_boundaries_advisory=[], + ) + class TestInMemoryMetricReaderCardinalityLimits(TestCase): def test_default_limit(self): diff --git a/opentelemetry-sdk/tests/metrics/test_periodic_exporting_metric_reader.py b/opentelemetry-sdk/tests/metrics/test_periodic_exporting_metric_reader.py index 3157c6dc7e4..5bbc7e3ff16 100644 --- a/opentelemetry-sdk/tests/metrics/test_periodic_exporting_metric_reader.py +++ b/opentelemetry-sdk/tests/metrics/test_periodic_exporting_metric_reader.py @@ -405,6 +405,7 @@ def test_metric_reader_metrics(self): point = metric.data.data_points[0] histogram = cast(HistogramDataPoint, point) self.assertEqual(histogram.count, 1) + self.assertEqual(histogram.explicit_bounds, ()) attrs = histogram.attributes assert attrs is not None self.assertEqual(attrs["otel.component.type"], "periodic_metric_reader")