Skip to content

Commit 445bdad

Browse files
committed
New APIs to add/remove metric readers at run-time
1 parent 102fec2 commit 445bdad

File tree

3 files changed

+90
-2
lines changed

3 files changed

+90
-2
lines changed

opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,3 +580,16 @@ def get_meter(
580580
self._measurement_consumer,
581581
)
582582
return self._meters[info]
583+
584+
def add_metric_reader(
585+
self, metric_reader: "opentelemetry.sdk.metrics.export.MetricReader"
586+
) -> None:
587+
with self._lock:
588+
self._measurement_consumer.add_metric_reader(metric_reader)
589+
590+
def remove_metric_reader(
591+
self,
592+
metric_reader: "opentelemetry.sdk.metrics.export.MetricReader",
593+
) -> None:
594+
with self._lock:
595+
self._measurement_consumer.remove_metric_reader(metric_reader)

opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/measurement_consumer.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
# pylint: disable=unused-import
1616

1717
from abc import ABC, abstractmethod
18+
from logging import getLogger
1819
from threading import Lock
1920
from time import time_ns
2021
from typing import Iterable, List, Mapping, Optional
@@ -31,6 +32,8 @@
3132
)
3233
from opentelemetry.sdk.metrics._internal.point import Metric
3334

35+
_logger = getLogger(__name__)
36+
3437

3538
class MeasurementConsumer(ABC):
3639
@abstractmethod
@@ -143,3 +146,41 @@ def collect(
143146
result = self._reader_storages[metric_reader].collect()
144147

145148
return result
149+
150+
def add_metric_reader(
151+
self, metric_reader: "opentelemetry.sdk.metrics.MetricReader"
152+
) -> None:
153+
"""Registers a new metric reader."""
154+
with self._lock:
155+
if metric_reader in self._reader_storages:
156+
_logger.warning("'%s' already registered!", metric_reader)
157+
return False
158+
self._sdk_config.metric_readers += type(
159+
self._sdk_config.metric_readers
160+
)((metric_reader,))
161+
self._reader_storages[metric_reader] = MetricReaderStorage(
162+
self._sdk_config,
163+
metric_reader._instrument_class_temporality,
164+
metric_reader._instrument_class_aggregation,
165+
)
166+
metric_reader._set_collect_callback(self.collect)
167+
return True
168+
169+
def remove_metric_reader(
170+
self, metric_reader: "opentelemetry.sdk.metrics.MetricReader"
171+
) -> None:
172+
"""Unregisters the given metric reader."""
173+
with self._lock:
174+
if metric_reader not in self._reader_storages:
175+
_logger.warning("'%s' has not been registered!", metric_reader)
176+
self._reader_storages.pop(metric_reader, None)
177+
metric_reader._set_collect_callback(None)
178+
self._sdk_config.metric_readers = type(
179+
self._sdk_config.metric_readers
180+
)(
181+
(
182+
reader
183+
for reader in self._sdk_config.metric_readers
184+
if reader is not metric_reader
185+
)
186+
)

opentelemetry-sdk/tests/metrics/test_metrics.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@
1313
# limitations under the License.
1414

1515
# pylint: disable=protected-access,no-self-use
16-
1716
import weakref
18-
from logging import WARNING
17+
from logging import DEBUG, WARNING
1918
from time import sleep
2019
from typing import Iterable, Sequence
2120
from unittest.mock import MagicMock, Mock, patch
@@ -36,6 +35,7 @@
3635
)
3736
from opentelemetry.sdk.metrics._internal import SynchronousMeasurementConsumer
3837
from opentelemetry.sdk.metrics.export import (
38+
InMemoryMetricReader,
3939
Metric,
4040
MetricExporter,
4141
MetricExportResult,
@@ -426,6 +426,40 @@ def test_consume_measurement_gauge(self, mock_sync_measurement_consumer):
426426

427427
sync_consumer_instance.consume_measurement.assert_called()
428428

429+
def test_addition_of_metric_reader(self):
430+
# Suppress warnings for calling collect on an unregistered metric reader
431+
with self.assertLogs(
432+
"opentelemetry.sdk.metrics._internal.export", DEBUG
433+
):
434+
reader = InMemoryMetricReader()
435+
meter_provider = MeterProvider()
436+
meter = meter_provider.get_meter(__name__)
437+
counter = meter.create_counter("counter")
438+
counter.add(1)
439+
self.assertIsNone(reader.get_metrics_data())
440+
441+
meter_provider.add_metric_reader(reader)
442+
counter.add(1)
443+
self.assertIsNotNone(reader.get_metrics_data())
444+
445+
with self.assertLogs(
446+
"opentelemetry.sdk.metrics._internal.measurement_consumer",
447+
WARNING,
448+
) as logger:
449+
meter_provider.add_metric_reader(reader)
450+
self.assertIn("already registered!", logger.output[0])
451+
452+
meter_provider.remove_metric_reader(reader)
453+
counter.add(1)
454+
self.assertIsNone(reader.get_metrics_data())
455+
456+
with self.assertLogs(
457+
"opentelemetry.sdk.metrics._internal.measurement_consumer",
458+
WARNING,
459+
) as logger:
460+
meter_provider.remove_metric_reader(reader)
461+
self.assertIn("has not been registered!", logger.output[0])
462+
429463

430464
class TestMeter(TestCase):
431465
def setUp(self):

0 commit comments

Comments
 (0)