diff --git a/README.md b/README.md index 734a824661e..84ce5244af6 100644 --- a/README.md +++ b/README.md @@ -77,20 +77,20 @@ from opentelemetry.sdk.metrics.export.controller import PushController metrics.set_preferred_meter_provider_implementation(lambda _: MeterProvider()) meter = metrics.get_meter(__name__) exporter = ConsoleMetricsExporter() -controller = PushController(meter, exporter, 5) +controller = PushController(meter=meter, exporter=exporter, interval=5) counter = meter.create_metric( - "available memory", - "available memory", - "bytes", - int, - Counter, - ("environment",), + name="available memory", + description="available memory", + unit="bytes", + value_type=int, + metric_type=Counter, + label_keys=("environment",), ) -label_values = ("staging",) -counter_handle = counter.get_handle(label_values) -counter_handle.add(100) +label_set = meter.get_label_set({"environment": "staging"}) +bound_counter = counter.bind(label_set) +bound_counter.add(100) ``` See the [API documentation](https://open-telemetry.github.io/opentelemetry-python/) for more detail, and the [examples folder](./examples) for a more sample code. diff --git a/docs/examples/metrics/record.py b/docs/examples/metrics/record.py index a376b2aafc0..d4c3d03d824 100644 --- a/docs/examples/metrics/record.py +++ b/docs/examples/metrics/record.py @@ -60,13 +60,15 @@ # The meter takes a dictionary of key value pairs label_set = meter.get_label_set({"environment": "staging"}) -# Handle usage -# You can record metrics with metric handles. Handles are created by passing in -# a labelset. A handle is essentially metric data that corresponds to a specific -# set of labels. Therefore, getting a handle using the same set of labels will -# yield the same metric handle. -counter_handle = counter.get_handle(label_set) -counter_handle.add(100) +# Bound instrument usage + +# You can record metrics with bound metric instruments. Bound metric +# instruments are created by passing in a labelset. A bound metric instrument +# is essentially metric data that corresponds to a specific set of labels. +# Therefore, getting a bound metric instrument using the same set of labels +# will yield the same bound metric instrument. +bound_counter = counter.bind(label_set) +bound_counter.add(100) # Direct metric usage # You can record metrics directly using the metric instrument. You pass in a diff --git a/opentelemetry-api/src/opentelemetry/metrics/__init__.py b/opentelemetry-api/src/opentelemetry/metrics/__init__.py index 3ba9bcad009..e521b2654c3 100644 --- a/opentelemetry-api/src/opentelemetry/metrics/__init__.py +++ b/opentelemetry-api/src/opentelemetry/metrics/__init__.py @@ -37,42 +37,42 @@ ValueT = TypeVar("ValueT", int, float) -class DefaultMetricHandle: - """The default MetricHandle. +class DefaultBoundInstrument: + """The default bound metric instrument. - Used when no MetricHandle implementation is available. + Used when no bound instrument implementation is available. """ def add(self, value: ValueT) -> None: - """No-op implementation of `CounterHandle` add. + """No-op implementation of `BoundCounter` add. Args: - value: The value to add to the handle. + value: The value to add to the bound metric instrument. """ def record(self, value: ValueT) -> None: - """No-op implementation of `MeasureHandle` record. + """No-op implementation of `BoundMeasure` record. Args: - value: The value to record to the handle. + value: The value to record to the bound metric instrument. """ -class CounterHandle: +class BoundCounter: def add(self, value: ValueT) -> None: - """Increases the value of the handle by ``value``. + """Increases the value of the bound counter by ``value``. Args: - value: The value to add to the handle. + value: The value to add to the bound counter. """ -class MeasureHandle: +class BoundMeasure: def record(self, value: ValueT) -> None: - """Records the given ``value`` to this handle. + """Records the given ``value`` to this bound measure. Args: - value: The value to record to the handle. + value: The value to record to the bound measure. """ @@ -80,11 +80,11 @@ class LabelSet(abc.ABC): """A canonicalized set of labels useful for preaggregation Re-usable LabelSet objects provide a potential optimization for scenarios - where handles might not be effective. For example, if the LabelSet will be - re-used but only used once per metrics, handles do not offer any - optimization. It may best to pre-compute a canonicalized LabelSet once and - re-use it with the direct calling convention. LabelSets are immutable and - should be opaque in implementation. + where bound metric instruments might not be effective. For example, if the + LabelSet will be re-used but only used once per metrics, bound metric + instruments do not offer any optimization. It may best to pre-compute a + canonicalized LabelSet once and re-use it with the direct calling + convention. LabelSets are immutable and should be opaque in implementation. """ @@ -99,42 +99,43 @@ class Metric(abc.ABC): """Base class for various types of metrics. Metric class that inherit from this class are specialized with the type of - handle that the metric holds. + bound metric instrument that the metric holds. """ @abc.abstractmethod - def get_handle(self, label_set: LabelSet) -> "object": - """Gets a handle, used for repeated-use of metrics instruments. + def bind(self, label_set: LabelSet) -> "object": + """Gets a bound metric instrument. - Handles are useful to reduce the cost of repeatedly recording a metric - with a pre-defined set of label values. All metric kinds (counter, - measure) support declaring a set of required label keys. The - values corresponding to these keys should be specified in every handle. - "Unspecified" label values, in cases where a handle is requested but - a value was not provided are permitted. + Bound metric instruments are useful to reduce the cost of repeatedly + recording a metric with a pre-defined set of label values. All metric + kinds (counter, measure) support declaring a set of required label + keys. The values corresponding to these keys should be specified in + every bound metric instrument. "Unspecified" label values, in cases + where a bound metric instrument is requested but a value was not + provided are permitted. Args: - label_set: `LabelSet` to associate with the returned handle. + label_set: `LabelSet` to associate with the bound instrument. """ class DefaultMetric(Metric): """The default Metric used when no Metric implementation is available.""" - def get_handle(self, label_set: LabelSet) -> "DefaultMetricHandle": - """Gets a `DefaultMetricHandle`. + def bind(self, label_set: LabelSet) -> "DefaultBoundInstrument": + """Gets a `DefaultBoundInstrument`. Args: - label_set: `LabelSet` to associate with the returned handle. + label_set: `LabelSet` to associate with the bound instrument. """ - return DefaultMetricHandle() + return DefaultBoundInstrument() def add(self, value: ValueT, label_set: LabelSet) -> None: """No-op implementation of `Counter` add. Args: value: The value to add to the counter metric. - label_set: `LabelSet` to associate with the returned handle. + label_set: `LabelSet` to associate with the bound instrument. """ def record(self, value: ValueT, label_set: LabelSet) -> None: @@ -142,23 +143,23 @@ def record(self, value: ValueT, label_set: LabelSet) -> None: Args: value: The value to record to this measure metric. - label_set: `LabelSet` to associate with the returned handle. + label_set: `LabelSet` to associate with the bound instrument. """ class Counter(Metric): """A counter type metric that expresses the computation of a sum.""" - def get_handle(self, label_set: LabelSet) -> "CounterHandle": - """Gets a `CounterHandle`.""" - return CounterHandle() + def bind(self, label_set: LabelSet) -> "BoundCounter": + """Gets a `BoundCounter`.""" + return BoundCounter() def add(self, value: ValueT, label_set: LabelSet) -> None: """Increases the value of the counter by ``value``. Args: value: The value to add to the counter metric. - label_set: `LabelSet` to associate with the returned handle. + label_set: `LabelSet` to associate with the returned bound counter. """ @@ -168,21 +169,22 @@ class Measure(Metric): Measure metrics represent raw statistics that are recorded. """ - def get_handle(self, label_set: LabelSet) -> "MeasureHandle": - """Gets a `MeasureHandle` with a float value.""" - return MeasureHandle() + def bind(self, label_set: LabelSet) -> "BoundMeasure": + """Gets a `BoundMeasure`.""" + return BoundMeasure() def record(self, value: ValueT, label_set: LabelSet) -> None: """Records the ``value`` to the measure. Args: value: The value to record to this measure metric. - label_set: `LabelSet` to associate with the returned handle. + label_set: `LabelSet` to associate with the returned bound measure. """ class Observer(abc.ABC): - """An observer type metric instrument used to capture a current set of values. + """An observer type metric instrument used to capture a current set of + values. Observer instruments are asynchronous, a callback is invoked with the @@ -283,16 +285,15 @@ def record_batch( ) -> None: """Atomically records a batch of `Metric` and value pairs. - Allows the functionality of acting upon multiple metrics with - a single API call. Implementations should find metric and handles that - match the key-value pairs in the label tuples. + Allows the functionality of acting upon multiple metrics with a single + API call. Implementations should find bound metric instruments that + match the key-value pairs in the labelset. - Args: - label_set: The `LabelSet` associated with all measurements in - the batch. A measurement is a tuple, representing the `Metric` - being recorded and the corresponding value to record. - record_tuples: A sequence of pairs of `Metric` s and the - corresponding value to record for that metric. + Args: label_set: The `LabelSet` associated with all measurements in the + batch. A measurement is a tuple, representing the `Metric` being + recorded and the corresponding value to record. record_tuples: A + sequence of pairs of `Metric` s and the corresponding value to + record for that metric. """ @abc.abstractmethod diff --git a/opentelemetry-api/tests/metrics/test_metrics.py b/opentelemetry-api/tests/metrics/test_metrics.py index 45913ca6720..9ecbede9f27 100644 --- a/opentelemetry-api/tests/metrics/test_metrics.py +++ b/opentelemetry-api/tests/metrics/test_metrics.py @@ -22,14 +22,16 @@ class TestMetrics(unittest.TestCase): def test_default(self): default = metrics.DefaultMetric() default_ls = metrics.DefaultLabelSet() - handle = default.get_handle(default_ls) - self.assertIsInstance(handle, metrics.DefaultMetricHandle) + bound_metric_instr = default.bind(default_ls) + self.assertIsInstance( + bound_metric_instr, metrics.DefaultBoundInstrument + ) def test_counter(self): counter = metrics.Counter() label_set = metrics.LabelSet() - handle = counter.get_handle(label_set) - self.assertIsInstance(handle, metrics.CounterHandle) + bound_counter = counter.bind(label_set) + self.assertIsInstance(bound_counter, metrics.BoundCounter) def test_counter_add(self): counter = metrics.Counter() @@ -39,21 +41,21 @@ def test_counter_add(self): def test_measure(self): measure = metrics.Measure() label_set = metrics.LabelSet() - handle = measure.get_handle(label_set) - self.assertIsInstance(handle, metrics.MeasureHandle) + bound_measure = measure.bind(label_set) + self.assertIsInstance(bound_measure, metrics.BoundMeasure) def test_measure_record(self): measure = metrics.Measure() label_set = metrics.LabelSet() measure.record(1, label_set) - def test_default_handle(self): - metrics.DefaultMetricHandle() + def test_default_bound_metric(self): + metrics.DefaultBoundInstrument() - def test_counter_handle(self): - handle = metrics.CounterHandle() - handle.add(1) + def test_bound_counter(self): + bound_counter = metrics.BoundCounter() + bound_counter.add(1) - def test_measure_handle(self): - handle = metrics.MeasureHandle() - handle.record(1) + def test_bound_measure(self): + bound_measure = metrics.BoundMeasure() + bound_measure.record(1) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/__init__.py index 5c616b39cf3..9c840da2984 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/__init__.py @@ -13,12 +13,11 @@ # limitations under the License. import logging -from collections import OrderedDict from typing import Dict, Sequence, Tuple, Type from opentelemetry import metrics as metrics_api from opentelemetry.sdk.metrics.export.aggregate import Aggregator -from opentelemetry.sdk.metrics.export.batcher import Batcher, UngroupedBatcher +from opentelemetry.sdk.metrics.export.batcher import UngroupedBatcher from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.util.instrumentation import InstrumentationInfo from opentelemetry.util import time_ns @@ -48,17 +47,18 @@ def __eq__(self, other): return self._encoded == other._encoded -class BaseHandle: - """The base handle class containing common behavior for all handles. +class BaseBoundInstrument: + """Class containing common behavior for all bound metric instruments. - Handles are responsible for operating on data for metric instruments for a - specific set of labels. + Bound metric instruments are responsible for operating on data for metric + instruments for a specific set of labels. Args: - value_type: The type of values this handle holds (int, float). + value_type: The type of values for this bound instrument (int, float). enabled: True if the originating instrument is enabled. - aggregator: The aggregator for this handle. Will handle aggregation - upon updates and checkpointing of values for exporting. + aggregator: The aggregator for this bound metric instrument. Will + handle aggregation upon updates and checkpointing of values for + exporting. """ def __init__( @@ -94,16 +94,16 @@ def __repr__(self): ) -class CounterHandle(metrics_api.CounterHandle, BaseHandle): +class BoundCounter(metrics_api.BoundCounter, BaseBoundInstrument): def add(self, value: metrics_api.ValueT) -> None: - """See `opentelemetry.metrics.CounterHandle.add`.""" + """See `opentelemetry.metrics.BoundCounter.add`.""" if self._validate_update(value): self.update(value) -class MeasureHandle(metrics_api.MeasureHandle, BaseHandle): +class BoundMeasure(metrics_api.BoundMeasure, BaseBoundInstrument): def record(self, value: metrics_api.ValueT) -> None: - """See `opentelemetry.metrics.MeasureHandle.record`.""" + """See `opentelemetry.metrics.BoundMeasure.record`.""" if self._validate_update(value): self.update(value) @@ -113,11 +113,11 @@ class Metric(metrics_api.Metric): Also known as metric instrument. This is the class that is used to represent a metric that is to be continuously recorded and tracked. Each - metric has a set of handles that are created from the metric. See - `BaseHandle` for information on handles. + metric has a set of bound metrics that are created from the metric. See + `BaseBoundInstrument` for information on bound metric instruments. """ - HANDLE_TYPE = BaseHandle + BOUND_INSTR_TYPE = BaseBoundInstrument def __init__( self, @@ -136,20 +136,20 @@ def __init__( self.meter = meter self.label_keys = label_keys self.enabled = enabled - self.handles = {} + self.bound_instruments = {} - def get_handle(self, label_set: LabelSet) -> BaseHandle: - """See `opentelemetry.metrics.Metric.get_handle`.""" - handle = self.handles.get(label_set) - if not handle: - handle = self.HANDLE_TYPE( + def bind(self, label_set: LabelSet) -> BaseBoundInstrument: + """See `opentelemetry.metrics.Metric.bind`.""" + bound_instrument = self.bound_instruments.get(label_set) + if not bound_instrument: + bound_instrument = self.BOUND_INSTR_TYPE( self.value_type, self.enabled, # Aggregator will be created based off type of metric self.meter.batcher.aggregator_for(self.__class__), ) - self.handles[label_set] = handle - return handle + self.bound_instruments[label_set] = bound_instrument + return bound_instrument def __repr__(self): return '{}(name="{}", description="{}")'.format( @@ -163,31 +163,11 @@ class Counter(Metric, metrics_api.Counter): """See `opentelemetry.metrics.Counter`. """ - HANDLE_TYPE = CounterHandle - - def __init__( - self, - name: str, - description: str, - unit: str, - value_type: Type[metrics_api.ValueT], - meter: "Meter", - label_keys: Sequence[str] = (), - enabled: bool = True, - ): - super().__init__( - name, - description, - unit, - value_type, - meter, - label_keys=label_keys, - enabled=enabled, - ) + BOUND_INSTR_TYPE = BoundCounter def add(self, value: metrics_api.ValueT, label_set: LabelSet) -> None: """See `opentelemetry.metrics.Counter.add`.""" - self.get_handle(label_set).add(value) + self.bind(label_set).add(value) UPDATE_FUNCTION = add @@ -195,11 +175,11 @@ def add(self, value: metrics_api.ValueT, label_set: LabelSet) -> None: class Measure(Metric, metrics_api.Measure): """See `opentelemetry.metrics.Measure`.""" - HANDLE_TYPE = MeasureHandle + BOUND_INSTR_TYPE = BoundMeasure def record(self, value: metrics_api.ValueT, label_set: LabelSet) -> None: """See `opentelemetry.metrics.Measure.record`.""" - self.get_handle(label_set).record(value) + self.bind(label_set).record(value) UPDATE_FUNCTION = record @@ -315,9 +295,9 @@ def collect(self) -> None: def _collect_metrics(self) -> None: for metric in self.metrics: if metric.enabled: - for label_set, handle in metric.handles.items(): + for label_set, bound_instr in metric.bound_instruments.items(): # TODO: Consider storing records in memory? - record = Record(metric, label_set, handle.aggregator) + record = Record(metric, label_set, bound_instr.aggregator) # Checkpoints the current aggregators # Applies different batching logic based on type of batcher self.batcher.process(record) diff --git a/opentelemetry-sdk/tests/metrics/export/test_export.py b/opentelemetry-sdk/tests/metrics/export/test_export.py index 3aab1632ec3..33d8d07c299 100644 --- a/opentelemetry-sdk/tests/metrics/export/test_export.py +++ b/opentelemetry-sdk/tests/metrics/export/test_export.py @@ -430,9 +430,7 @@ class TestObserverAggregator(unittest.TestCase): def test_update(self): observer = ObserverAggregator() # test current values without any update - self.assertEqual( - observer.mmsc.current, (None, None, None, 0), - ) + self.assertEqual(observer.mmsc.current, (None, None, None, 0)) self.assertIsNone(observer.current) # call update with some values @@ -452,9 +450,7 @@ def test_checkpoint(self): # take checkpoint wihtout any update observer.take_checkpoint() - self.assertEqual( - observer.checkpoint, (None, None, None, 0, None), - ) + self.assertEqual(observer.checkpoint, (None, None, None, 0, None)) # call update with some values values = (3, 50, 3, 97) diff --git a/opentelemetry-sdk/tests/metrics/test_metrics.py b/opentelemetry-sdk/tests/metrics/test_metrics.py index 6fcba4de633..4e7e532d867 100644 --- a/opentelemetry-sdk/tests/metrics/test_metrics.py +++ b/opentelemetry-sdk/tests/metrics/test_metrics.py @@ -103,7 +103,7 @@ def test_record_batch(self): label_set = meter.get_label_set(kvp) record_tuples = [(counter, 1.0)] meter.record_batch(label_set, record_tuples) - self.assertEqual(counter.get_handle(label_set).aggregator.current, 1.0) + self.assertEqual(counter.bind(label_set).aggregator.current, 1.0) def test_record_batch_multiple(self): meter = metrics.MeterProvider().get_meter(__name__) @@ -118,10 +118,9 @@ def test_record_batch_multiple(self): ) record_tuples = [(counter, 1.0), (measure, 3.0)] meter.record_batch(label_set, record_tuples) - self.assertEqual(counter.get_handle(label_set).aggregator.current, 1.0) + self.assertEqual(counter.bind(label_set).aggregator.current, 1.0) self.assertEqual( - measure.get_handle(label_set).aggregator.current, - (3.0, 3.0, 3.0, 1), + measure.bind(label_set).aggregator.current, (3.0, 3.0, 3.0, 1) ) def test_record_batch_exists(self): @@ -133,11 +132,11 @@ def test_record_batch_exists(self): "name", "desc", "unit", float, meter, label_keys ) counter.add(1.0, label_set) - handle = counter.get_handle(label_set) + bound_counter = counter.bind(label_set) record_tuples = [(counter, 1.0)] meter.record_batch(label_set, record_tuples) - self.assertEqual(counter.get_handle(label_set), handle) - self.assertEqual(handle.aggregator.current, 2.0) + self.assertEqual(counter.bind(label_set), bound_counter) + self.assertEqual(bound_counter.aggregator.current, 2.0) def test_create_metric(self): resource = mock.Mock(spec=resources.Resource) @@ -196,15 +195,17 @@ def test_get_label_set_empty(self): class TestMetric(unittest.TestCase): - def test_get_handle(self): + def test_bind(self): meter = metrics.MeterProvider().get_meter(__name__) metric_types = [metrics.Counter, metrics.Measure] for _type in metric_types: metric = _type("name", "desc", "unit", int, meter, ("key",)) kvp = {"key": "value"} label_set = meter.get_label_set(kvp) - handle = metric.get_handle(label_set) - self.assertEqual(metric.handles.get(label_set), handle) + bound_instrument = metric.bind(label_set) + self.assertEqual( + metric.bound_instruments.get(label_set), bound_instrument + ) class TestCounter(unittest.TestCase): @@ -213,10 +214,10 @@ def test_add(self): metric = metrics.Counter("name", "desc", "unit", int, meter, ("key",)) kvp = {"key": "value"} label_set = meter.get_label_set(kvp) - handle = metric.get_handle(label_set) + bound_counter = metric.bind(label_set) metric.add(3, label_set) metric.add(2, label_set) - self.assertEqual(handle.aggregator.current, 5) + self.assertEqual(bound_counter.aggregator.current, 5) class TestMeasure(unittest.TestCase): @@ -225,12 +226,12 @@ def test_record(self): metric = metrics.Measure("name", "desc", "unit", int, meter, ("key",)) kvp = {"key": "value"} label_set = meter.get_label_set(kvp) - handle = metric.get_handle(label_set) + bound_measure = metric.bind(label_set) values = (37, 42, 7) for val in values: metric.record(val, label_set) self.assertEqual( - handle.aggregator.current, + bound_measure.aggregator.current, (min(values), max(values), sum(values), len(values)), ) @@ -301,63 +302,67 @@ def test_run_exception(self, logger_mock): self.assertTrue(logger_mock.warning.called) -class TestCounterHandle(unittest.TestCase): +class TestBoundCounter(unittest.TestCase): def test_add(self): aggregator = export.aggregate.CounterAggregator() - handle = metrics.CounterHandle(int, True, aggregator) - handle.add(3) - self.assertEqual(handle.aggregator.current, 3) + bound_metric = metrics.BoundCounter(int, True, aggregator) + bound_metric.add(3) + self.assertEqual(bound_metric.aggregator.current, 3) def test_add_disabled(self): aggregator = export.aggregate.CounterAggregator() - handle = metrics.CounterHandle(int, False, aggregator) - handle.add(3) - self.assertEqual(handle.aggregator.current, 0) + bound_counter = metrics.BoundCounter(int, False, aggregator) + bound_counter.add(3) + self.assertEqual(bound_counter.aggregator.current, 0) @mock.patch("opentelemetry.sdk.metrics.logger") def test_add_incorrect_type(self, logger_mock): aggregator = export.aggregate.CounterAggregator() - handle = metrics.CounterHandle(int, True, aggregator) - handle.add(3.0) - self.assertEqual(handle.aggregator.current, 0) + bound_counter = metrics.BoundCounter(int, True, aggregator) + bound_counter.add(3.0) + self.assertEqual(bound_counter.aggregator.current, 0) self.assertTrue(logger_mock.warning.called) @mock.patch("opentelemetry.sdk.metrics.time_ns") def test_update(self, time_mock): aggregator = export.aggregate.CounterAggregator() - handle = metrics.CounterHandle(int, True, aggregator) + bound_counter = metrics.BoundCounter(int, True, aggregator) time_mock.return_value = 123 - handle.update(4.0) - self.assertEqual(handle.last_update_timestamp, 123) - self.assertEqual(handle.aggregator.current, 4.0) + bound_counter.update(4.0) + self.assertEqual(bound_counter.last_update_timestamp, 123) + self.assertEqual(bound_counter.aggregator.current, 4.0) -class TestMeasureHandle(unittest.TestCase): +class TestBoundMeasure(unittest.TestCase): def test_record(self): aggregator = export.aggregate.MinMaxSumCountAggregator() - handle = metrics.MeasureHandle(int, True, aggregator) - handle.record(3) - self.assertEqual(handle.aggregator.current, (3, 3, 3, 1)) + bound_measure = metrics.BoundMeasure(int, True, aggregator) + bound_measure.record(3) + self.assertEqual(bound_measure.aggregator.current, (3, 3, 3, 1)) def test_record_disabled(self): aggregator = export.aggregate.MinMaxSumCountAggregator() - handle = metrics.MeasureHandle(int, False, aggregator) - handle.record(3) - self.assertEqual(handle.aggregator.current, (None, None, None, 0)) + bound_measure = metrics.BoundMeasure(int, False, aggregator) + bound_measure.record(3) + self.assertEqual( + bound_measure.aggregator.current, (None, None, None, 0) + ) @mock.patch("opentelemetry.sdk.metrics.logger") def test_record_incorrect_type(self, logger_mock): aggregator = export.aggregate.MinMaxSumCountAggregator() - handle = metrics.MeasureHandle(int, True, aggregator) - handle.record(3.0) - self.assertEqual(handle.aggregator.current, (None, None, None, 0)) + bound_measure = metrics.BoundMeasure(int, True, aggregator) + bound_measure.record(3.0) + self.assertEqual( + bound_measure.aggregator.current, (None, None, None, 0) + ) self.assertTrue(logger_mock.warning.called) @mock.patch("opentelemetry.sdk.metrics.time_ns") def test_update(self, time_mock): aggregator = export.aggregate.MinMaxSumCountAggregator() - handle = metrics.MeasureHandle(int, True, aggregator) + bound_measure = metrics.BoundMeasure(int, True, aggregator) time_mock.return_value = 123 - handle.update(4.0) - self.assertEqual(handle.last_update_timestamp, 123) - self.assertEqual(handle.aggregator.current, (4.0, 4.0, 4.0, 1)) + bound_measure.update(4.0) + self.assertEqual(bound_measure.last_update_timestamp, 123) + self.assertEqual(bound_measure.aggregator.current, (4.0, 4.0, 4.0, 1))