From 768b28c71b50d9a85c25e7d09d208172740d8353 Mon Sep 17 00:00:00 2001 From: Diego Hurtado Date: Mon, 5 Jun 2023 16:41:46 +0200 Subject: [PATCH] Remove console exporter metric output if empty Fixes #3198 --- .../sdk/metrics/_internal/export/__init__.py | 10 +++---- .../metrics/_internal/measurement_consumer.py | 8 +++--- .../_internal/metric_reader_storage.py | 28 ++++++++++--------- .../integration_test/test_console_exporter.py | 16 +++++++++++ 4 files changed, 40 insertions(+), 22 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/export/__init__.py index 5bd94d5aacc..7096034d5fe 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/export/__init__.py @@ -344,7 +344,7 @@ def _set_collect_callback( @abstractmethod def _receive_metrics( self, - metrics_data: "opentelemetry.sdk.metrics.export.MetricsData", + metrics_data: Optional[MetricsData], timeout_millis: float = 10_000, **kwargs, ) -> None: @@ -386,9 +386,9 @@ def __init__( preferred_aggregation=preferred_aggregation, ) self._lock = RLock() - self._metrics_data: ( + self._metrics_data: Optional[ "opentelemetry.sdk.metrics.export.MetricsData" - ) = None + ] = None def get_metrics_data( self, @@ -402,7 +402,7 @@ def get_metrics_data( def _receive_metrics( self, - metrics_data: "opentelemetry.sdk.metrics.export.MetricsData", + metrics_data: Optional[MetricsData], timeout_millis: float = 10_000, **kwargs, ) -> None: @@ -511,7 +511,7 @@ def _ticker(self) -> None: def _receive_metrics( self, - metrics_data: MetricsData, + metrics_data: Optional[MetricsData], timeout_millis: float = 10_000, **kwargs, ) -> None: diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/measurement_consumer.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/measurement_consumer.py index 9daf1eff461..a0499dc3e82 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/measurement_consumer.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/measurement_consumer.py @@ -17,7 +17,7 @@ from abc import ABC, abstractmethod from threading import Lock from time import time_ns -from typing import Iterable, List, Mapping +from typing import List, Mapping, Optional # This kind of import is needed to avoid Sphinx errors. import opentelemetry.sdk.metrics @@ -29,7 +29,7 @@ from opentelemetry.sdk.metrics._internal.metric_reader_storage import ( MetricReaderStorage, ) -from opentelemetry.sdk.metrics._internal.point import Metric +from opentelemetry.sdk.metrics._internal.point import MetricsData class MeasurementConsumer(ABC): @@ -51,7 +51,7 @@ def collect( self, metric_reader: "opentelemetry.sdk.metrics.MetricReader", timeout_millis: float = 10_000, - ) -> Iterable[Metric]: + ) -> Optional[MetricsData]: pass @@ -94,7 +94,7 @@ def collect( self, metric_reader: "opentelemetry.sdk.metrics.MetricReader", timeout_millis: float = 10_000, - ) -> Iterable[Metric]: + ) -> Optional[MetricsData]: with self._lock: metric_reader_storage = self._reader_storages[metric_reader] diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/metric_reader_storage.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/metric_reader_storage.py index bef57eaab09..f2110559a38 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/metric_reader_storage.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/metric_reader_storage.py @@ -15,7 +15,7 @@ from logging import getLogger from threading import RLock from time import time_ns -from typing import Dict, List +from typing import Dict, List, Optional from opentelemetry.metrics import ( Asynchronous, @@ -119,7 +119,7 @@ def consume_measurement(self, measurement: Measurement) -> None: ): view_instrument_match.consume_measurement(measurement) - def collect(self) -> MetricsData: + def collect(self) -> Optional[MetricsData]: # Use a list instead of yielding to prevent a slow reader from holding # SDK locks @@ -231,17 +231,19 @@ def collect(self) -> MetricsData: instrument.instrumentation_scope ].metrics.extend(metrics) - return MetricsData( - resource_metrics=[ - ResourceMetrics( - resource=self._sdk_config.resource, - scope_metrics=list( - instrumentation_scope_scope_metrics.values() - ), - schema_url=self._sdk_config.resource.schema_url, - ) - ] - ) + scope_metrics = list(instrumentation_scope_scope_metrics.values()) + + if scope_metrics: + + return MetricsData( + resource_metrics=[ + ResourceMetrics( + resource=self._sdk_config.resource, + scope_metrics=scope_metrics, + schema_url=self._sdk_config.resource.schema_url, + ) + ] + ) def _handle_view_instrument_match( self, diff --git a/opentelemetry-sdk/tests/metrics/integration_test/test_console_exporter.py b/opentelemetry-sdk/tests/metrics/integration_test/test_console_exporter.py index 60c4227c3b2..1b3283717ae 100644 --- a/opentelemetry-sdk/tests/metrics/integration_test/test_console_exporter.py +++ b/opentelemetry-sdk/tests/metrics/integration_test/test_console_exporter.py @@ -72,3 +72,19 @@ def test_console_exporter(self): self.assertEqual(metrics["attributes"], {"a": "b"}) self.assertEqual(metrics["value"], 1) + + def test_console_exporter_no_export(self): + + output = StringIO() + exporter = ConsoleMetricExporter(out=output) + reader = PeriodicExportingMetricReader( + exporter, export_interval_millis=100 + ) + provider = MeterProvider(metric_readers=[reader]) + provider.shutdown() + + output.seek(0) + actual = "".join(output.readlines()) + expected = "" + + self.assertEqual(actual, expected)