Skip to content

Commit 636262d

Browse files
authored
Refactor metrics instrument (#2297)
* Refactor instrument Fixes #2294 * Add Callback type back to API * Create instrument with meter * Make default_aggregation public * Use right import * Remove default aggregations
1 parent 8d4a804 commit 636262d

File tree

5 files changed

+149
-144
lines changed

5 files changed

+149
-144
lines changed

opentelemetry-api/src/opentelemetry/_metrics/instrument.py

Lines changed: 6 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
# type: ignore
1818

1919
from abc import ABC, abstractmethod
20-
from collections import abc as collections_abc
2120
from logging import getLogger
2221
from typing import (
2322
Callable,
@@ -33,10 +32,11 @@
3332
from opentelemetry import _metrics as metrics
3433
from opentelemetry._metrics.measurement import Measurement
3534

36-
_TInstrumentCallback = Callable[[], Iterable[Measurement]]
37-
_TInstrumentCallbackGenerator = Generator[Iterable[Measurement], None, None]
38-
TCallback = Union[_TInstrumentCallback, _TInstrumentCallbackGenerator]
3935
InstrumentT = TypeVar("InstrumentT", bound="Instrument")
36+
CallbackT = Union[
37+
Callable[[], Iterable[Measurement]],
38+
Generator[Iterable[Measurement], None, None],
39+
]
4040

4141

4242
_logger = getLogger(__name__)
@@ -87,45 +87,11 @@ class Asynchronous(Instrument):
8787
def __init__(
8888
self,
8989
name,
90-
callback: TCallback,
91-
*args,
90+
callback,
9291
unit="",
9392
description="",
94-
**kwargs
9593
):
96-
super().__init__(
97-
name, *args, unit=unit, description=description, **kwargs
98-
)
99-
100-
if isinstance(callback, collections_abc.Callable):
101-
self._callback = callback
102-
elif isinstance(callback, collections_abc.Generator):
103-
self._callback = self._wrap_generator_callback(callback)
104-
# FIXME check that callback is a callable or generator
105-
106-
@staticmethod
107-
def _wrap_generator_callback(
108-
generator_callback: _TInstrumentCallbackGenerator,
109-
) -> _TInstrumentCallback:
110-
"""Wraps a generator style callback into a callable one"""
111-
has_items = True
112-
113-
def inner() -> Iterable[Measurement]:
114-
nonlocal has_items
115-
if not has_items:
116-
return []
117-
118-
try:
119-
return next(generator_callback)
120-
except StopIteration:
121-
has_items = False
122-
# FIXME handle the situation where the callback generator has
123-
# run out of measurements
124-
return []
125-
126-
return inner
127-
128-
# FIXME check that callbacks return an iterable of Measurements
94+
super().__init__(name, unit=unit, description=description)
12995

13096

13197
class _Adding(Instrument):

opentelemetry-sdk/src/opentelemetry/sdk/_metrics/instrument.py

Lines changed: 42 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,11 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
# pylint: disable=function-redefined
16-
# pylint: disable=dangerous-default-value
17-
# Classes in this module use dictionaries as default arguments. This is
18-
# considered dangerous by pylint because the default dictionary is shared by
19-
# all instances. Implementations of these classes must not make any change to
20-
# this default dictionary in __init__.
15+
# pylint: disable=too-many-ancestors
2116

17+
from typing import Dict, Generator, Iterable, Union
18+
19+
from opentelemetry._metrics.instrument import CallbackT
2220
from opentelemetry._metrics.instrument import Counter as APICounter
2321
from opentelemetry._metrics.instrument import Histogram as APIHistogram
2422
from opentelemetry._metrics.instrument import (
@@ -30,131 +28,77 @@
3028
from opentelemetry._metrics.instrument import (
3129
ObservableUpDownCounter as APIObservableUpDownCounter,
3230
)
33-
from opentelemetry._metrics.instrument import TCallback
3431
from opentelemetry._metrics.instrument import UpDownCounter as APIUpDownCounter
32+
from opentelemetry.sdk._metrics.measurement import Measurement
3533
from opentelemetry.sdk.util.instrumentation import InstrumentationInfo
3634

3735

38-
class _Instrument:
36+
class _Synchronous:
3937
def __init__(
4038
self,
4139
instrumentation_info: InstrumentationInfo,
4240
name: str,
43-
*args,
4441
unit: str = "",
4542
description: str = "",
4643
):
4744
self._instrumentation_info = instrumentation_info
48-
super().__init__(name, *args, unit=unit, description=description)
45+
super().__init__(name, unit=unit, description=description)
4946

5047

51-
class Counter(_Instrument, APICounter):
48+
class _Asynchronous:
5249
def __init__(
5350
self,
5451
instrumentation_info: InstrumentationInfo,
5552
name: str,
53+
callback: CallbackT,
5654
unit: str = "",
5755
description: str = "",
5856
):
59-
super().__init__(
60-
instrumentation_info,
61-
name,
62-
unit=unit,
63-
description=description,
64-
)
65-
66-
def add(self, amount, attributes=None):
67-
# FIXME check that the amount is non negative
68-
pass
6957

58+
self._instrumentation_info = instrumentation_info
59+
super().__init__(name, callback, unit=unit, description=description)
7060

71-
class UpDownCounter(_Instrument, APIUpDownCounter):
72-
def __init__(
73-
self,
74-
instrumentation_info: InstrumentationInfo,
75-
name: str,
76-
unit: str = "",
77-
description: str = "",
78-
):
79-
super().__init__(
80-
instrumentation_info,
81-
name,
82-
unit=unit,
83-
description=description,
84-
)
85-
86-
def add(self, amount, attributes=None):
87-
pass
61+
self._callback = callback
8862

63+
if isinstance(callback, Generator):
8964

90-
class ObservableCounter(_Instrument, APIObservableCounter):
91-
def __init__(
92-
self,
93-
instrumentation_info: InstrumentationInfo,
94-
name: str,
95-
callback: TCallback,
96-
unit: str = "",
97-
description: str = "",
98-
):
99-
super().__init__(
100-
instrumentation_info,
101-
name,
102-
callback,
103-
unit=unit,
104-
description=description,
105-
)
65+
def inner() -> Iterable[Measurement]:
66+
return next(callback)
10667

68+
self._callback = inner
10769

108-
class ObservableUpDownCounter(_Instrument, APIObservableUpDownCounter):
109-
def __init__(
110-
self,
111-
instrumentation_info: InstrumentationInfo,
112-
name: str,
113-
callback: TCallback,
114-
unit: str = "",
115-
description: str = "",
70+
@property
71+
def callback(self) -> CallbackT:
72+
return self._callback
73+
74+
75+
class Counter(_Synchronous, APICounter):
76+
def add(
77+
self, amount: Union[int, float], attributes: Dict[str, str] = None
11678
):
117-
super().__init__(
118-
instrumentation_info,
119-
name,
120-
callback,
121-
unit=unit,
122-
description=description,
123-
)
79+
if amount < 0:
80+
raise Exception("amount must be non negative")
12481

12582

126-
class Histogram(_Instrument, APIHistogram):
127-
def __init__(
128-
self,
129-
instrumentation_info: InstrumentationInfo,
130-
name: str,
131-
unit: str = "",
132-
description: str = "",
83+
class UpDownCounter(_Synchronous, APIUpDownCounter):
84+
def add(
85+
self, amount: Union[int, float], attributes: Dict[str, str] = None
13386
):
134-
super().__init__(
135-
instrumentation_info,
136-
name,
137-
unit=unit,
138-
description=description,
139-
)
87+
pass
88+
14089

90+
class ObservableCounter(_Asynchronous, APIObservableCounter):
91+
pass
92+
93+
94+
class ObservableUpDownCounter(_Asynchronous, APIObservableUpDownCounter):
95+
pass
96+
97+
98+
class Histogram(_Synchronous, APIHistogram):
14199
def record(self, amount, attributes=None):
142100
pass
143101

144102

145-
class ObservableGauge(_Instrument, APIObservableGauge):
146-
def __init__(
147-
self,
148-
instrumentation_info: InstrumentationInfo,
149-
name: str,
150-
callback: TCallback,
151-
unit: str = "",
152-
description: str = "",
153-
):
154-
super().__init__(
155-
instrumentation_info,
156-
name,
157-
callback,
158-
unit=unit,
159-
description=description,
160-
)
103+
class ObservableGauge(_Asynchronous, APIObservableGauge):
104+
pass
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
class Measurement:
17+
pass

opentelemetry-api/tests/metrics/integration_test/test_cpu_time.py renamed to opentelemetry-sdk/tests/metrics/integration_test/test_cpu_time.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
from typing import Generator, Iterable
1818
from unittest import TestCase
1919

20-
from opentelemetry._metrics import _DefaultMeter
2120
from opentelemetry._metrics.measurement import Measurement
21+
from opentelemetry.sdk._metrics import MeterProvider
2222

2323
# FIXME Test that the instrument methods can be called concurrently safely.
2424

@@ -61,8 +61,6 @@ class TestCpuTimeIntegration(TestCase):
6161
]
6262

6363
def test_cpu_time_callback(self):
64-
meter = _DefaultMeter("foo")
65-
6664
def cpu_time_callback() -> Iterable[Measurement]:
6765
procstat = io.StringIO(self.procstat_str)
6866
procstat.readline() # skip the first line
@@ -98,18 +96,17 @@ def cpu_time_callback() -> Iterable[Measurement]:
9896
int(states[8]) // 100, {"cpu": cpu, "state": "guest_nice"}
9997
)
10098

99+
meter = MeterProvider().get_meter("name")
101100
observable_counter = meter.create_observable_counter(
102101
"system.cpu.time",
103-
callback=cpu_time_callback,
102+
cpu_time_callback,
104103
unit="s",
105104
description="CPU time",
106105
)
107106
measurements = list(observable_counter._callback())
108107
self.assertEqual(measurements, self.measurements_expected)
109108

110109
def test_cpu_time_generator(self):
111-
meter = _DefaultMeter("foo")
112-
113110
def cpu_time_generator() -> Generator[
114111
Iterable[Measurement], None, None
115112
]:
@@ -176,6 +173,7 @@ def cpu_time_generator() -> Generator[
176173
)
177174
yield measurements
178175

176+
meter = MeterProvider().get_meter("name")
179177
observable_counter = meter.create_observable_counter(
180178
"system.cpu.time",
181179
callback=cpu_time_generator(),

0 commit comments

Comments
 (0)