From ba97fe99cc0b98049c25bc8ee96f10ebdaff542c Mon Sep 17 00:00:00 2001 From: Diego Hurtado Date: Thu, 14 Apr 2022 16:55:13 -0600 Subject: [PATCH] Add support for zero or more callbacks (#2602) * Add support for zero or more callbacks Fixes #2601 * Add changelog entry * Update opentelemetry-api/src/opentelemetry/_metrics/__init__.py Co-authored-by: Leighton Chen * Update opentelemetry-api/src/opentelemetry/_metrics/__init__.py Co-authored-by: Leighton Chen Co-authored-by: Leighton Chen --- CHANGELOG.md | 2 + .../src/opentelemetry/_metrics/__init__.py | 61 +++++----- .../src/opentelemetry/_metrics/instrument.py | 24 ++-- .../tests/metrics/test_instruments.py | 44 ++++---- .../tests/metrics/test_meter_provider.py | 18 +-- .../opentelemetry/sdk/_metrics/__init__.py | 12 +- .../opentelemetry/sdk/_metrics/instrument.py | 41 ++++--- .../metrics/integration_test/test_cpu_time.py | 4 +- .../tests/metrics/test_aggregation.py | 6 +- .../metrics/test_in_memory_metric_reader.py | 2 +- .../tests/metrics/test_instrument.py | 106 +++++++++++++++--- .../tests/metrics/test_metrics.py | 27 +++-- 12 files changed, 223 insertions(+), 124 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38288293362..29317e4f20b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/open-telemetry/opentelemetry-python/compare/v1.10.0-0.29b0...HEAD) +- Add support for zero or more callbacks + ([#2602](https://github.com/open-telemetry/opentelemetry-python/pull/2602)) - Fix parsing of trace flags when extracting traceparent ([#2577](https://github.com/open-telemetry/opentelemetry-python/pull/2577)) - Add default aggregation diff --git a/opentelemetry-api/src/opentelemetry/_metrics/__init__.py b/opentelemetry-api/src/opentelemetry/_metrics/__init__.py index b3a2ee5563c..0abe07b38bd 100644 --- a/opentelemetry-api/src/opentelemetry/_metrics/__init__.py +++ b/opentelemetry-api/src/opentelemetry/_metrics/__init__.py @@ -181,12 +181,12 @@ def create_up_down_counter( @abstractmethod def create_observable_counter( - self, name, callback, unit="", description="" + self, name, callbacks=None, unit="", description="" ) -> ObservableCounter: """Creates an `ObservableCounter` instrument An observable counter observes a monotonically increasing count by - calling a provided callback which returns multiple + calling provided callbacks which returns multiple :class:`~opentelemetry._metrics.measurement.Measurement`. For example, an observable counter could be used to report system CPU @@ -207,7 +207,7 @@ def cpu_time_callback() -> Iterable[Measurement]: meter.create_observable_counter( "system.cpu.time", - callback=cpu_time_callback, + callbacks=[cpu_time_callback], unit="s", description="CPU time" ) @@ -225,8 +225,8 @@ def cpu_time_callback() -> Iterable[Measurement]: yield Measurement(int(states[1]) // 100, {"cpu": cpu, "state": "nice"}) # ... other states - Alternatively, you can pass a generator directly instead of a callback, - which should return iterables of + Alternatively, you can pass a sequence of generators directly instead + of a sequence of callbacks, which each should return iterables of :class:`~opentelemetry._metrics.measurement.Measurement`:: def cpu_time_callback(states_to_include: set[str]) -> Iterable[Iterable[Measurement]]: @@ -246,16 +246,17 @@ def cpu_time_callback(states_to_include: set[str]) -> Iterable[Iterable[Measurem meter.create_observable_counter( "system.cpu.time", - callback=cpu_time_callback({"user", "system"}), + callbacks=[cpu_time_callback({"user", "system"})], unit="s", description="CPU time" ) Args: name: The name of the instrument to be created - callback: A callback that returns an iterable of + callbacks: A sequence of callbacks that return an iterable of :class:`~opentelemetry._metrics.measurement.Measurement`. - Alternatively, can be a generator that yields iterables of + Alternatively, can be a sequence of generators that each yields + iterables of :class:`~opentelemetry._metrics.measurement.Measurement`. unit: The unit for measurements this instrument reports. For example, ``By`` for bytes. UCUM units are recommended. @@ -275,13 +276,13 @@ def create_histogram(self, name, unit="", description="") -> Histogram: @abstractmethod def create_observable_gauge( - self, name, callback, unit="", description="" + self, name, callbacks=None, unit="", description="" ) -> ObservableGauge: """Creates an `ObservableGauge` instrument Args: name: The name of the instrument to be created - callback: A callback that returns an iterable of + callbacks: A sequence of callbacks that return an iterable of :class:`~opentelemetry._metrics.measurement.Measurement`. Alternatively, can be a generator that yields iterables of :class:`~opentelemetry._metrics.measurement.Measurement`. @@ -292,13 +293,13 @@ def create_observable_gauge( @abstractmethod def create_observable_up_down_counter( - self, name, callback, unit="", description="" + self, name, callbacks=None, unit="", description="" ) -> ObservableUpDownCounter: """Creates an `ObservableUpDownCounter` instrument Args: name: The name of the instrument to be created - callback: A callback that returns an iterable of + callbacks: A sequence of callbacks that return an iterable of :class:`~opentelemetry._metrics.measurement.Measurement`. Alternatively, can be a generator that yields iterables of :class:`~opentelemetry._metrics.measurement.Measurement`. @@ -358,15 +359,15 @@ def create_up_down_counter( return proxy def create_observable_counter( - self, name, callback, unit="", description="" + self, name, callbacks=None, unit="", description="" ) -> ObservableCounter: with self._lock: if self._real_meter: return self._real_meter.create_observable_counter( - name, callback, unit, description + name, callbacks, unit, description ) proxy = _ProxyObservableCounter( - name, callback, unit=unit, description=description + name, callbacks, unit=unit, description=description ) self._instruments.append(proxy) return proxy @@ -382,32 +383,32 @@ def create_histogram(self, name, unit="", description="") -> Histogram: return proxy def create_observable_gauge( - self, name, callback, unit="", description="" + self, name, callbacks=None, unit="", description="" ) -> ObservableGauge: with self._lock: if self._real_meter: return self._real_meter.create_observable_gauge( - name, callback, unit, description + name, callbacks, unit, description ) proxy = _ProxyObservableGauge( - name, callback, unit=unit, description=description + name, callbacks, unit=unit, description=description ) self._instruments.append(proxy) return proxy def create_observable_up_down_counter( - self, name, callback, unit="", description="" + self, name, callbacks=None, unit="", description="" ) -> ObservableUpDownCounter: with self._lock: if self._real_meter: return self._real_meter.create_observable_up_down_counter( name, - callback, + callbacks, unit, description, ) proxy = _ProxyObservableUpDownCounter( - name, callback, unit=unit, description=description + name, callbacks, unit=unit, description=description ) self._instruments.append(proxy) return proxy @@ -454,11 +455,11 @@ def create_up_down_counter( return DefaultUpDownCounter(name, unit=unit, description=description) def create_observable_counter( - self, name, callback, unit="", description="" + self, name, callbacks=None, unit="", description="" ) -> ObservableCounter: """Returns a no-op ObservableCounter.""" super().create_observable_counter( - name, callback, unit=unit, description=description + name, callbacks, unit=unit, description=description ) if self._check_instrument_id( name, DefaultObservableCounter, unit, description @@ -473,7 +474,7 @@ def create_observable_counter( ) return DefaultObservableCounter( name, - callback, + callbacks, unit=unit, description=description, ) @@ -495,11 +496,11 @@ def create_histogram(self, name, unit="", description="") -> Histogram: return DefaultHistogram(name, unit=unit, description=description) def create_observable_gauge( - self, name, callback, unit="", description="" + self, name, callbacks=None, unit="", description="" ) -> ObservableGauge: """Returns a no-op ObservableGauge.""" super().create_observable_gauge( - name, callback, unit=unit, description=description + name, callbacks, unit=unit, description=description ) if self._check_instrument_id( name, DefaultObservableGauge, unit, description @@ -514,17 +515,17 @@ def create_observable_gauge( ) return DefaultObservableGauge( name, - callback, + callbacks, unit=unit, description=description, ) def create_observable_up_down_counter( - self, name, callback, unit="", description="" + self, name, callbacks=None, unit="", description="" ) -> ObservableUpDownCounter: """Returns a no-op ObservableUpDownCounter.""" super().create_observable_up_down_counter( - name, callback, unit=unit, description=description + name, callbacks, unit=unit, description=description ) if self._check_instrument_id( name, DefaultObservableUpDownCounter, unit, description @@ -539,7 +540,7 @@ def create_observable_up_down_counter( ) return DefaultObservableUpDownCounter( name, - callback, + callbacks, unit=unit, description=description, ) diff --git a/opentelemetry-api/src/opentelemetry/_metrics/instrument.py b/opentelemetry-api/src/opentelemetry/_metrics/instrument.py index aee2a5359e6..f707b05e9fd 100644 --- a/opentelemetry-api/src/opentelemetry/_metrics/instrument.py +++ b/opentelemetry-api/src/opentelemetry/_metrics/instrument.py @@ -73,9 +73,9 @@ def _create_real_instrument(self, meter: "metrics.Meter") -> InstrumentT: class _ProxyAsynchronousInstrument(_ProxyInstrument[InstrumentT]): - def __init__(self, name, callback, unit, description) -> None: + def __init__(self, name, callbacks, unit, description) -> None: super().__init__(name, unit, description) - self._callback = callback + self._callbacks = callbacks class Synchronous(Instrument): @@ -87,7 +87,7 @@ class Asynchronous(Instrument): def __init__( self, name, - callback, + callbacks=None, unit="", description="", ): @@ -170,8 +170,8 @@ class ObservableCounter(_Monotonic, Asynchronous): class DefaultObservableCounter(ObservableCounter): - def __init__(self, name, callback, unit="", description=""): - super().__init__(name, callback, unit=unit, description=description) + def __init__(self, name, callbacks=None, unit="", description=""): + super().__init__(name, callbacks, unit=unit, description=description) class _ProxyObservableCounter( @@ -181,7 +181,7 @@ def _create_real_instrument( self, meter: "metrics.Meter" ) -> ObservableCounter: return meter.create_observable_counter( - self._name, self._callback, self._unit, self._description + self._name, self._callbacks, self._unit, self._description ) @@ -193,8 +193,8 @@ class ObservableUpDownCounter(_NonMonotonic, Asynchronous): class DefaultObservableUpDownCounter(ObservableUpDownCounter): - def __init__(self, name, callback, unit="", description=""): - super().__init__(name, callback, unit=unit, description=description) + def __init__(self, name, callbacks=None, unit="", description=""): + super().__init__(name, callbacks, unit=unit, description=description) class _ProxyObservableUpDownCounter( @@ -205,7 +205,7 @@ def _create_real_instrument( self, meter: "metrics.Meter" ) -> ObservableUpDownCounter: return meter.create_observable_up_down_counter( - self._name, self._callback, self._unit, self._description + self._name, self._callbacks, self._unit, self._description ) @@ -247,8 +247,8 @@ class ObservableGauge(_Grouping, Asynchronous): class DefaultObservableGauge(ObservableGauge): - def __init__(self, name, callback, unit="", description=""): - super().__init__(name, callback, unit=unit, description=description) + def __init__(self, name, callbacks=None, unit="", description=""): + super().__init__(name, callbacks, unit=unit, description=description) class _ProxyObservableGauge( @@ -259,5 +259,5 @@ def _create_real_instrument( self, meter: "metrics.Meter" ) -> ObservableGauge: return meter.create_observable_gauge( - self._name, self._callback, self._unit, self._description + self._name, self._callbacks, self._unit, self._description ) diff --git a/opentelemetry-api/tests/metrics/test_instruments.py b/opentelemetry-api/tests/metrics/test_instruments.py index b97a1640bd5..34bc4cec80a 100644 --- a/opentelemetry-api/tests/metrics/test_instruments.py +++ b/opentelemetry-api/tests/metrics/test_instruments.py @@ -118,7 +118,7 @@ def callback(): self.assertTrue( isinstance( NoOpMeter("name").create_observable_counter( - "name", callback() + "name", callbacks=[callback()] ), ObservableCounter, ) @@ -134,7 +134,7 @@ def test_api_observable_counter_abstract(self): def test_create_observable_counter_api(self): """ Test that the API for creating a observable_counter accepts the name of the instrument. - Test that the API for creating a observable_counter accepts a callback. + Test that the API for creating a observable_counter accepts a sequence of callbacks. Test that the API for creating a observable_counter accepts the unit of the instrument. Test that the API for creating a observable_counter accepts the description of the instrument """ @@ -153,11 +153,13 @@ def test_create_observable_counter_api(self): Meter.create_observable_counter ) self.assertIn( - "callback", create_observable_counter_signature.parameters.keys() + "callbacks", create_observable_counter_signature.parameters.keys() ) self.assertIs( - create_observable_counter_signature.parameters["callback"].default, - Signature.empty, + create_observable_counter_signature.parameters[ + "callbacks" + ].default, + None, ) create_observable_counter_signature = signature( Meter.create_observable_counter @@ -196,7 +198,7 @@ def test_observable_counter_generator(self): Meter.create_observable_counter ) self.assertIn( - "callback", create_observable_counter_signature.parameters.keys() + "callbacks", create_observable_counter_signature.parameters.keys() ) self.assertIs( create_observable_counter_signature.parameters["name"].default, @@ -285,7 +287,9 @@ def callback(): self.assertTrue( isinstance( - NoOpMeter("name").create_observable_gauge("name", callback()), + NoOpMeter("name").create_observable_gauge( + "name", [callback()] + ), ObservableGauge, ) ) @@ -300,7 +304,7 @@ def test_api_observable_gauge_abstract(self): def test_create_observable_gauge_api(self): """ Test that the API for creating a observable_gauge accepts the name of the instrument. - Test that the API for creating a observable_gauge accepts a callback. + Test that the API for creating a observable_gauge accepts a sequence of callbacks. Test that the API for creating a observable_gauge accepts the unit of the instrument. Test that the API for creating a observable_gauge accepts the description of the instrument """ @@ -319,11 +323,11 @@ def test_create_observable_gauge_api(self): Meter.create_observable_gauge ) self.assertIn( - "callback", create_observable_gauge_signature.parameters.keys() + "callbacks", create_observable_gauge_signature.parameters.keys() ) self.assertIs( - create_observable_gauge_signature.parameters["callback"].default, - Signature.empty, + create_observable_gauge_signature.parameters["callbacks"].default, + None, ) create_observable_gauge_signature = signature( Meter.create_observable_gauge @@ -350,7 +354,7 @@ def test_create_observable_gauge_api(self): def test_observable_gauge_callback(self): """ - Test that the API for creating a asynchronous gauge accepts a callback. + Test that the API for creating a asynchronous gauge accepts a sequence of callbacks. Test that the callback function reports measurements. Test that there is a way to pass state to the callback. """ @@ -359,7 +363,7 @@ def test_observable_gauge_callback(self): Meter.create_observable_gauge ) self.assertIn( - "callback", create_observable_gauge_signature.parameters.keys() + "callbacks", create_observable_gauge_signature.parameters.keys() ) self.assertIs( create_observable_gauge_signature.parameters["name"].default, @@ -461,7 +465,7 @@ def callback(): self.assertTrue( isinstance( NoOpMeter("name").create_observable_up_down_counter( - "name", callback() + "name", [callback()] ), ObservableUpDownCounter, ) @@ -477,7 +481,7 @@ def test_api_observable_up_down_counter_abstract(self): def test_create_observable_up_down_counter_api(self): """ Test that the API for creating a observable_up_down_counter accepts the name of the instrument. - Test that the API for creating a observable_up_down_counter accepts a callback. + Test that the API for creating a observable_up_down_counter accepts a sequence of callbacks. Test that the API for creating a observable_up_down_counter accepts the unit of the instrument. Test that the API for creating a observable_up_down_counter accepts the description of the instrument """ @@ -499,14 +503,14 @@ def test_create_observable_up_down_counter_api(self): Meter.create_observable_up_down_counter ) self.assertIn( - "callback", + "callbacks", create_observable_up_down_counter_signature.parameters.keys(), ) self.assertIs( create_observable_up_down_counter_signature.parameters[ - "callback" + "callbacks" ].default, - Signature.empty, + None, ) create_observable_up_down_counter_signature = signature( Meter.create_observable_up_down_counter @@ -538,7 +542,7 @@ def test_create_observable_up_down_counter_api(self): def test_observable_up_down_counter_callback(self): """ - Test that the API for creating a asynchronous up_down_counter accepts a callback. + Test that the API for creating a asynchronous up_down_counter accepts a sequence of callbacks. Test that the callback function reports measurements. Test that there is a way to pass state to the callback. Test that the instrument accepts positive and negative values. @@ -548,7 +552,7 @@ def test_observable_up_down_counter_callback(self): Meter.create_observable_up_down_counter ) self.assertIn( - "callback", + "callbacks", create_observable_up_down_counter_signature.parameters.keys(), ) self.assertIs( diff --git a/opentelemetry-api/tests/metrics/test_meter_provider.py b/opentelemetry-api/tests/metrics/test_meter_provider.py index 17325c4c03a..9efcfeb33a9 100644 --- a/opentelemetry-api/tests/metrics/test_meter_provider.py +++ b/opentelemetry-api/tests/metrics/test_meter_provider.py @@ -195,15 +195,15 @@ def test_proxy_meter(self): name, unit=unit, description=description ) proxy_observable_counter = proxy_meter.create_observable_counter( - name, callback=callback, unit=unit, description=description + name, callbacks=[callback], unit=unit, description=description ) proxy_observable_updowncounter = ( proxy_meter.create_observable_up_down_counter( - name, callback=callback, unit=unit, description=description + name, callbacks=[callback], unit=unit, description=description ) ) proxy_overvable_gauge = proxy_meter.create_observable_gauge( - name, callback=callback, unit=unit, description=description + name, callbacks=[callback], unit=unit, description=description ) self.assertIsInstance(proxy_counter, _ProxyCounter) self.assertIsInstance(proxy_updowncounter, _ProxyUpDownCounter) @@ -243,13 +243,13 @@ def test_proxy_meter(self): name, unit, description ) real_meter.create_observable_counter.assert_called_once_with( - name, callback, unit, description + name, [callback], unit, description ) real_meter.create_observable_up_down_counter.assert_called_once_with( - name, callback, unit, description + name, [callback], unit, description ) real_meter.create_observable_gauge.assert_called_once_with( - name, callback, unit, description + name, [callback], unit, description ) # The synchronous instrument measurement methods should call through to @@ -292,15 +292,15 @@ def test_proxy_meter_with_real_meter(self) -> None: name, unit=unit, description=description ) observable_counter = proxy_meter.create_observable_counter( - name, callback=callback, unit=unit, description=description + name, callbacks=[callback], unit=unit, description=description ) observable_updowncounter = ( proxy_meter.create_observable_up_down_counter( - name, callback=callback, unit=unit, description=description + name, callbacks=[callback], unit=unit, description=description ) ) observable_gauge = proxy_meter.create_observable_gauge( - name, callback=callback, unit=unit, description=description + name, callbacks=[callback], unit=unit, description=description ) real_meter: Mock = real_meter_provider.get_meter() diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/__init__.py index e78e69454ca..29e4972d803 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/__init__.py @@ -111,7 +111,7 @@ def create_up_down_counter( ) def create_observable_counter( - self, name, callback, unit="", description="" + self, name, callbacks=None, unit="", description="" ) -> APIObservableCounter: if self._check_instrument_id( name, ObservableCounter, unit, description @@ -131,7 +131,7 @@ def create_observable_counter( name, self._instrumentation_info, self._measurement_consumer, - callback, + callbacks, unit, description, ) @@ -162,7 +162,7 @@ def create_histogram(self, name, unit="", description="") -> APIHistogram: ) def create_observable_gauge( - self, name, callback, unit="", description="" + self, name, callbacks=None, unit="", description="" ) -> APIObservableGauge: if self._check_instrument_id(name, ObservableGauge, unit, description): # FIXME #2558 go through all views here and check if this @@ -181,7 +181,7 @@ def create_observable_gauge( name, self._instrumentation_info, self._measurement_consumer, - callback, + callbacks, unit, description, ) @@ -191,7 +191,7 @@ def create_observable_gauge( return instrument def create_observable_up_down_counter( - self, name, callback, unit="", description="" + self, name, callbacks=None, unit="", description="" ) -> APIObservableUpDownCounter: if self._check_instrument_id( name, ObservableUpDownCounter, unit, description @@ -212,7 +212,7 @@ def create_observable_up_down_counter( name, self._instrumentation_info, self._measurement_consumer, - callback, + callbacks, unit, description, ) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/instrument.py b/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/instrument.py index f2ac050d975..c6e6e4c273b 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/instrument.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/instrument.py @@ -15,7 +15,7 @@ # pylint: disable=too-many-ancestors import logging -from typing import Callable, Dict, Generator, Iterable, Union +from typing import Dict, Generator, Iterable, Optional, Union from opentelemetry._metrics.instrument import CallbackT from opentelemetry._metrics.instrument import Counter as APICounter @@ -30,7 +30,6 @@ ObservableUpDownCounter as APIObservableUpDownCounter, ) from opentelemetry._metrics.instrument import UpDownCounter as APIUpDownCounter -from opentelemetry._metrics.measurement import Measurement as APIMeasurement from opentelemetry.sdk._metrics.measurement import Measurement from opentelemetry.sdk._metrics.measurement_consumer import MeasurementConsumer from opentelemetry.sdk.util.instrumentation import InstrumentationInfo @@ -61,7 +60,7 @@ def __init__( name: str, instrumentation_info: InstrumentationInfo, measurement_consumer: MeasurementConsumer, - callback: CallbackT, + callbacks: Optional[Iterable[CallbackT]] = None, unit: str = "", description: str = "", ): @@ -70,26 +69,34 @@ def __init__( self.description = description self.instrumentation_info = instrumentation_info self._measurement_consumer = measurement_consumer - super().__init__(name, callback, unit=unit, description=description) + super().__init__(name, callbacks, unit=unit, description=description) - self._callback: Callable[[], Iterable[APIMeasurement]] + self._callbacks = [] - if isinstance(callback, Generator): + if callbacks is not None: - def inner() -> Iterable[Measurement]: - return next(callback) + for callback in callbacks: - self._callback = inner - else: - self._callback = callback + if isinstance(callback, Generator): + + def inner(callback=callback) -> Iterable[Measurement]: + return next(callback) + + self._callbacks.append(inner) + else: + self._callbacks.append(callback) def callback(self) -> Iterable[Measurement]: - for api_measurement in self._callback(): - yield Measurement( - api_measurement.value, - instrument=self, - attributes=api_measurement.attributes, - ) + for callback in self._callbacks: + try: + for api_measurement in callback(): + yield Measurement( + api_measurement.value, + instrument=self, + attributes=api_measurement.attributes, + ) + except StopIteration: + pass class Counter(_Synchronous, APICounter): diff --git a/opentelemetry-sdk/tests/metrics/integration_test/test_cpu_time.py b/opentelemetry-sdk/tests/metrics/integration_test/test_cpu_time.py index 0135faa7722..b21091c3c8f 100644 --- a/opentelemetry-sdk/tests/metrics/integration_test/test_cpu_time.py +++ b/opentelemetry-sdk/tests/metrics/integration_test/test_cpu_time.py @@ -177,7 +177,7 @@ def cpu_time_callback() -> Iterable[APIMeasurement]: meter = MeterProvider().get_meter("name") observable_counter = meter.create_observable_counter( "system.cpu.time", - cpu_time_callback, + callbacks=[cpu_time_callback], unit="s", description="CPU time", ) @@ -256,7 +256,7 @@ def cpu_time_generator() -> Generator[ meter = MeterProvider().get_meter("name") observable_counter = meter.create_observable_counter( "system.cpu.time", - callback=cpu_time_generator(), + callbacks=[cpu_time_generator()], unit="s", description="CPU time", ) diff --git a/opentelemetry-sdk/tests/metrics/test_aggregation.py b/opentelemetry-sdk/tests/metrics/test_aggregation.py index 99f34782670..ad483a41d4c 100644 --- a/opentelemetry-sdk/tests/metrics/test_aggregation.py +++ b/opentelemetry-sdk/tests/metrics/test_aggregation.py @@ -897,7 +897,7 @@ def test_up_down_counter(self): def test_observable_counter(self): aggregation = self.default_aggregation._create_aggregation( - ObservableCounter(Mock(), Mock(), Mock(), Mock()) + ObservableCounter(Mock(), Mock(), Mock(), callbacks=[Mock()]) ) self.assertIsInstance(aggregation, _SumAggregation) self.assertTrue(aggregation._instrument_is_monotonic) @@ -909,7 +909,7 @@ def test_observable_counter(self): def test_observable_up_down_counter(self): aggregation = self.default_aggregation._create_aggregation( - ObservableUpDownCounter(Mock(), Mock(), Mock(), Mock()) + ObservableUpDownCounter(Mock(), Mock(), Mock(), callbacks=[Mock()]) ) self.assertIsInstance(aggregation, _SumAggregation) self.assertFalse(aggregation._instrument_is_monotonic) @@ -938,7 +938,7 @@ def test_observable_gauge(self): Mock(), Mock(), Mock(), - Mock(), + callbacks=[Mock()], ) ) self.assertIsInstance(aggregation, _LastValueAggregation) 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 441b6df2d89..fcafea38db9 100644 --- a/opentelemetry-sdk/tests/metrics/test_in_memory_metric_reader.py +++ b/opentelemetry-sdk/tests/metrics/test_in_memory_metric_reader.py @@ -70,7 +70,7 @@ def test_integration(self): meter = MeterProvider(metric_readers=[reader]).get_meter("test_meter") counter1 = meter.create_counter("counter1") meter.create_observable_gauge( - "observable_gauge1", lambda: [Measurement(value=12)] + "observable_gauge1", callbacks=[lambda: [Measurement(value=12)]] ) counter1.add(1, {"foo": "1"}) counter1.add(1, {"foo": "2"}) diff --git a/opentelemetry-sdk/tests/metrics/test_instrument.py b/opentelemetry-sdk/tests/metrics/test_instrument.py index 8e0b3b2a6b3..b52216560f9 100644 --- a/opentelemetry-sdk/tests/metrics/test_instrument.py +++ b/opentelemetry-sdk/tests/metrics/test_instrument.py @@ -58,7 +58,7 @@ def test_add_non_monotonic(self): TEST_ATTRIBUTES = {"foo": "bar"} -def callable_callback(): +def callable_callback_0(): return [ APIMeasurement(1, attributes=TEST_ATTRIBUTES), APIMeasurement(2, attributes=TEST_ATTRIBUTES), @@ -66,7 +66,15 @@ def callable_callback(): ] -def generator_callback(): +def callable_callback_1(): + return [ + APIMeasurement(4, attributes=TEST_ATTRIBUTES), + APIMeasurement(5, attributes=TEST_ATTRIBUTES), + APIMeasurement(6, attributes=TEST_ATTRIBUTES), + ] + + +def generator_callback_0(): yield [ APIMeasurement(1, attributes=TEST_ATTRIBUTES), APIMeasurement(2, attributes=TEST_ATTRIBUTES), @@ -74,10 +82,67 @@ def generator_callback(): ] +def generator_callback_1(): + yield [ + APIMeasurement(4, attributes=TEST_ATTRIBUTES), + APIMeasurement(5, attributes=TEST_ATTRIBUTES), + APIMeasurement(6, attributes=TEST_ATTRIBUTES), + ] + + class TestObservableGauge(TestCase): - def test_callable_callback(self): + def test_callable_callback_0(self): + observable_gauge = ObservableGauge( + "name", Mock(), Mock(), [callable_callback_0] + ) + + self.assertEqual( + list(observable_gauge.callback()), + [ + Measurement( + 1, instrument=observable_gauge, attributes=TEST_ATTRIBUTES + ), + Measurement( + 2, instrument=observable_gauge, attributes=TEST_ATTRIBUTES + ), + Measurement( + 3, instrument=observable_gauge, attributes=TEST_ATTRIBUTES + ), + ], + ) + + def test_callable_multiple_callable_callback(self): + observable_gauge = ObservableGauge( + "name", Mock(), Mock(), [callable_callback_0, callable_callback_1] + ) + + self.assertEqual( + list(observable_gauge.callback()), + [ + Measurement( + 1, instrument=observable_gauge, attributes=TEST_ATTRIBUTES + ), + Measurement( + 2, instrument=observable_gauge, attributes=TEST_ATTRIBUTES + ), + Measurement( + 3, instrument=observable_gauge, attributes=TEST_ATTRIBUTES + ), + Measurement( + 4, instrument=observable_gauge, attributes=TEST_ATTRIBUTES + ), + Measurement( + 5, instrument=observable_gauge, attributes=TEST_ATTRIBUTES + ), + Measurement( + 6, instrument=observable_gauge, attributes=TEST_ATTRIBUTES + ), + ], + ) + + def test_generator_callback_0(self): observable_gauge = ObservableGauge( - "name", Mock(), Mock(), callable_callback + "name", Mock(), Mock(), [generator_callback_0()] ) self.assertEqual( @@ -95,9 +160,13 @@ def test_callable_callback(self): ], ) - def test_generator_callback(self): + def test_generator_multiple_generator_callback(self): + self.maxDiff = None observable_gauge = ObservableGauge( - "name", Mock(), Mock(), generator_callback() + "name", + Mock(), + Mock(), + callbacks=[generator_callback_0(), generator_callback_1()], ) self.assertEqual( @@ -112,14 +181,23 @@ def test_generator_callback(self): Measurement( 3, instrument=observable_gauge, attributes=TEST_ATTRIBUTES ), + Measurement( + 4, instrument=observable_gauge, attributes=TEST_ATTRIBUTES + ), + Measurement( + 5, instrument=observable_gauge, attributes=TEST_ATTRIBUTES + ), + Measurement( + 6, instrument=observable_gauge, attributes=TEST_ATTRIBUTES + ), ], ) class TestObservableCounter(TestCase): - def test_callable_callback(self): + def test_callable_callback_0(self): observable_counter = ObservableCounter( - "name", Mock(), Mock(), callable_callback + "name", Mock(), Mock(), [callable_callback_0] ) self.assertEqual( @@ -143,9 +221,9 @@ def test_callable_callback(self): ], ) - def test_generator_callback(self): + def test_generator_callback_0(self): observable_counter = ObservableCounter( - "name", Mock(), Mock(), generator_callback() + "name", Mock(), Mock(), [generator_callback_0()] ) self.assertEqual( @@ -171,9 +249,9 @@ def test_generator_callback(self): class TestObservableUpDownCounter(TestCase): - def test_callable_callback(self): + def test_callable_callback_0(self): observable_up_down_counter = ObservableUpDownCounter( - "name", Mock(), Mock(), callable_callback + "name", Mock(), Mock(), [callable_callback_0] ) self.assertEqual( @@ -197,9 +275,9 @@ def test_callable_callback(self): ], ) - def test_generator_callback(self): + def test_generator_callback_0(self): observable_up_down_counter = ObservableUpDownCounter( - "name", Mock(), Mock(), generator_callback() + "name", Mock(), Mock(), [generator_callback_0()] ) self.assertEqual( diff --git a/opentelemetry-sdk/tests/metrics/test_metrics.py b/opentelemetry-sdk/tests/metrics/test_metrics.py index df51ee668c9..2c2d48a7930 100644 --- a/opentelemetry-sdk/tests/metrics/test_metrics.py +++ b/opentelemetry-sdk/tests/metrics/test_metrics.py @@ -255,17 +255,17 @@ def test_register_asynchronous_instrument( meter_provider._measurement_consumer.register_asynchronous_instrument.assert_called_with( meter_provider.get_meter("name").create_observable_counter( - "name0", Mock() + "name0", callbacks=[Mock()] ) ) meter_provider._measurement_consumer.register_asynchronous_instrument.assert_called_with( meter_provider.get_meter("name").create_observable_up_down_counter( - "name1", Mock() + "name1", callbacks=[Mock()] ) ) meter_provider._measurement_consumer.register_asynchronous_instrument.assert_called_with( meter_provider.get_meter("name").create_observable_gauge( - "name2", Mock() + "name2", callbacks=[Mock()] ) ) @@ -314,11 +314,15 @@ def test_repeated_instrument_names(self): try: self.meter.create_counter("counter") self.meter.create_up_down_counter("up_down_counter") - self.meter.create_observable_counter("observable_counter", Mock()) + self.meter.create_observable_counter( + "observable_counter", callbacks=[Mock()] + ) self.meter.create_histogram("histogram") - self.meter.create_observable_gauge("observable_gauge", Mock()) + self.meter.create_observable_gauge( + "observable_gauge", callbacks=[Mock()] + ) self.meter.create_observable_up_down_counter( - "observable_up_down_counter", Mock() + "observable_up_down_counter", callbacks=[Mock()] ) except Exception as error: self.fail(f"Unexpected exception raised {error}") @@ -340,7 +344,7 @@ def test_repeated_instrument_names(self): ]: with self.assertLogs(level=WARNING): getattr(self.meter, f"create_{instrument_name}")( - instrument_name, Mock() + instrument_name, callbacks=[Mock()] ) def test_create_counter(self): @@ -361,7 +365,7 @@ def test_create_up_down_counter(self): def test_create_observable_counter(self): observable_counter = self.meter.create_observable_counter( - "name", Mock(), unit="unit", description="description" + "name", callbacks=[Mock()], unit="unit", description="description" ) self.assertIsInstance(observable_counter, ObservableCounter) @@ -377,7 +381,7 @@ def test_create_histogram(self): def test_create_observable_gauge(self): observable_gauge = self.meter.create_observable_gauge( - "name", Mock(), unit="unit", description="description" + "name", callbacks=[Mock()], unit="unit", description="description" ) self.assertIsInstance(observable_gauge, ObservableGauge) @@ -386,7 +390,10 @@ def test_create_observable_gauge(self): def test_create_observable_up_down_counter(self): observable_up_down_counter = ( self.meter.create_observable_up_down_counter( - "name", Mock(), unit="unit", description="description" + "name", + callbacks=[Mock()], + unit="unit", + description="description", ) ) self.assertIsInstance(