Skip to content

Commit f3d75ec

Browse files
committed
Make Measurement a concrete class
1 parent 613ff32 commit f3d75ec

File tree

7 files changed

+139
-91
lines changed

7 files changed

+139
-91
lines changed

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
nitpick_ignore = [
9999
("py:class", "ValueT"),
100100
("py:class", "MetricT"),
101+
("py:obj", "opentelemetry.metrics.measurement.ValueT"),
101102
# Even if wrapt is added to intersphinx_mapping, sphinx keeps failing
102103
# with "class reference target not found: ObjectProxy".
103104
("py:class", "ObjectProxy"),

opentelemetry-api/src/opentelemetry/metrics/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def create_observable_counter(
136136
For example, an observable counter could be used to report system CPU
137137
time periodically. Here is a basic implementation::
138138
139-
def cpu_time_callback() -> Iterable[Measurement]:
139+
def cpu_time_callback() -> Iterable[Measurement[int]]:
140140
measurements = []
141141
with open("/proc/stat") as procstat:
142142
procstat.readline() # skip the first line
@@ -159,7 +159,7 @@ def cpu_time_callback() -> Iterable[Measurement]:
159159
To reduce memory usage, you can use generator callbacks instead of
160160
building the full list::
161161
162-
def cpu_time_callback() -> Iterable[Measurement]:
162+
def cpu_time_callback() -> Iterable[Measurement[int]]:
163163
with open("/proc/stat") as procstat:
164164
procstat.readline() # skip the first line
165165
for line in procstat:
@@ -173,7 +173,7 @@ def cpu_time_callback() -> Iterable[Measurement]:
173173
which should return iterables of
174174
:class:`~opentelemetry.metrics.measurement.Measurement`::
175175
176-
def cpu_time_callback(states_to_include: set[str]) -> Iterable[Iterable[Measurement]]:
176+
def cpu_time_callback(states_to_include: set[str]) -> Iterable[Iterable[Measurement[int]]]:
177177
while True:
178178
measurements = []
179179
with open("/proc/stat") as procstat:

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

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,17 @@
2020
from collections import abc as collections_abc
2121
from logging import getLogger
2222
from re import compile as compile_
23-
from typing import Callable, Generator, Iterable, Union
23+
from typing import Callable, Generator, Generic, Iterable, Union
2424

25-
from opentelemetry.metrics.measurement import Measurement
25+
from opentelemetry.metrics.measurement import Measurement, ValueT
2626

27-
_TInstrumentCallback = Callable[[], Iterable[Measurement]]
28-
_TInstrumentCallbackGenerator = Generator[Iterable[Measurement], None, None]
29-
TCallback = Union[_TInstrumentCallback, _TInstrumentCallbackGenerator]
27+
_InstrumentCallbackT = Callable[[], Iterable[Measurement[ValueT]]]
28+
_InstrumentCallbackGeneratorT = Generator[
29+
Iterable[Measurement[ValueT]], None, None
30+
]
31+
CallbackT = Union[
32+
_InstrumentCallbackT[ValueT], _InstrumentCallbackGeneratorT[ValueT]
33+
]
3034

3135

3236
_logger = getLogger(__name__)
@@ -77,12 +81,12 @@ class Synchronous(Instrument):
7781
pass
7882

7983

80-
class Asynchronous(Instrument):
84+
class Asynchronous(Generic[ValueT], Instrument):
8185
@abstractmethod
8286
def __init__(
8387
self,
8488
name,
85-
callback: TCallback,
89+
callback: CallbackT[ValueT],
8690
*args,
8791
unit="",
8892
description="",
@@ -101,12 +105,12 @@ def __init__(
101105

102106
def _wrap_generator_callback(
103107
self,
104-
generator_callback: _TInstrumentCallbackGenerator,
105-
) -> _TInstrumentCallback:
108+
generator_callback: _InstrumentCallbackGeneratorT[ValueT],
109+
) -> _InstrumentCallbackT[ValueT]:
106110
"""Wraps a generator style callback into a callable one"""
107111
has_items = True
108112

109-
def inner() -> Iterable[Measurement]:
113+
def inner() -> Iterable[Measurement[ValueT]]:
110114
nonlocal has_items
111115
if not has_items:
112116
return []
@@ -186,7 +190,7 @@ def add(self, amount, attributes=None):
186190
return super().add(amount, attributes=attributes)
187191

188192

189-
class ObservableCounter(_Monotonic, Asynchronous):
193+
class ObservableCounter(Asynchronous[ValueT], _Monotonic):
190194
def callback(self):
191195
measurements = super().callback()
192196

@@ -196,17 +200,20 @@ def callback(self):
196200
yield measurement
197201

198202

199-
class DefaultObservableCounter(ObservableCounter):
203+
class DefaultObservableCounter(ObservableCounter[ValueT]):
200204
def __init__(self, name, callback, unit="", description=""):
201205
super().__init__(name, callback, unit=unit, description=description)
202206

203207

204-
class ObservableUpDownCounter(_NonMonotonic, Asynchronous):
208+
class ObservableUpDownCounter(
209+
Asynchronous[ValueT],
210+
_NonMonotonic,
211+
):
205212

206213
pass
207214

208215

209-
class DefaultObservableUpDownCounter(ObservableUpDownCounter):
216+
class DefaultObservableUpDownCounter(ObservableUpDownCounter[ValueT]):
210217
def __init__(self, name, callback, unit="", description=""):
211218
super().__init__(name, callback, unit=unit, description=description)
212219

@@ -225,10 +232,10 @@ def record(self, amount, attributes=None):
225232
return super().record(amount, attributes=attributes)
226233

227234

228-
class ObservableGauge(_Grouping, Asynchronous):
235+
class ObservableGauge(Asynchronous[ValueT], _Grouping):
229236
pass
230237

231238

232-
class DefaultObservableGauge(ObservableGauge):
239+
class DefaultObservableGauge(ObservableGauge[ValueT]):
233240
def __init__(self, name, callback, unit="", description=""):
234241
super().__init__(name, callback, unit=unit, description=description)

opentelemetry-api/src/opentelemetry/metrics/measurement.py

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

15-
# pylint: disable=too-many-ancestors
16-
# type:ignore
1715

16+
from typing import Generic, TypeVar
1817

19-
from abc import ABC, abstractmethod
18+
from opentelemetry.util.types import Attributes
2019

20+
ValueT = TypeVar("ValueT", int, float)
21+
22+
23+
class Measurement(Generic[ValueT]):
24+
"""A measurement observed in an asynchronous instrument
25+
26+
Return/yield instances of this class from asynchronous instrument callbacks.
27+
28+
Args:
29+
value: The float or int measured value
30+
attributes: The measurement's attributes
31+
"""
32+
33+
def __init__(self, value: ValueT, attributes: Attributes = None) -> None:
34+
self._value = value
35+
self._attributes = attributes
2136

22-
class Measurement(ABC):
2337
@property
24-
def value(self):
38+
def value(self) -> ValueT:
2539
return self._value
2640

2741
@property
28-
def attributes(self):
42+
def attributes(self) -> Attributes:
2943
return self._attributes
3044

31-
@abstractmethod
32-
def __init__(self, value, attributes=None):
33-
self._value = value
34-
self._attributes = attributes
35-
45+
def __eq__(self, other: "Measurement[ValueT]") -> bool:
46+
return (
47+
self.value == other.value and self.attributes == other.attributes
48+
)
3649

37-
class DefaultMeasurement(Measurement):
38-
def __init__(self, value, attributes=None):
39-
super().__init__(value, attributes=attributes)
50+
def __repr__(self) -> str:
51+
return f"Measurement(value={self.value}, attributes={self.attributes})"

opentelemetry-api/tests/metrics/integration_test/test_cpu_time.py

Lines changed: 38 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,6 @@
2323
# FIXME Test that the instrument methods can be called concurrently safely.
2424

2525

26-
class ChildMeasurement(Measurement):
27-
def __init__(self, value, attributes=None):
28-
super().__init__(value, attributes=attributes)
29-
30-
def __eq__(self, o: Measurement) -> bool:
31-
return self.value == o.value and self.attributes == o.attributes
32-
33-
3426
class TestCpuTimeIntegration(TestCase):
3527
"""Integration test of scraping CPU time from proc stat with an observable
3628
counter"""
@@ -48,61 +40,61 @@ class TestCpuTimeIntegration(TestCase):
4840
softirq 1644603067 0 166540056 208 309152755 8936439 0 1354908 935642970 13 222975718\n"""
4941

5042
measurements_expected = [
51-
ChildMeasurement(6150, {"cpu": "cpu0", "state": "user"}),
52-
ChildMeasurement(3177, {"cpu": "cpu0", "state": "nice"}),
53-
ChildMeasurement(5946, {"cpu": "cpu0", "state": "system"}),
54-
ChildMeasurement(891264, {"cpu": "cpu0", "state": "idle"}),
55-
ChildMeasurement(1296, {"cpu": "cpu0", "state": "iowait"}),
56-
ChildMeasurement(0, {"cpu": "cpu0", "state": "irq"}),
57-
ChildMeasurement(8343, {"cpu": "cpu0", "state": "softirq"}),
58-
ChildMeasurement(421, {"cpu": "cpu0", "state": "guest"}),
59-
ChildMeasurement(0, {"cpu": "cpu0", "state": "guest_nice"}),
60-
ChildMeasurement(5882, {"cpu": "cpu1", "state": "user"}),
61-
ChildMeasurement(3491, {"cpu": "cpu1", "state": "nice"}),
62-
ChildMeasurement(6404, {"cpu": "cpu1", "state": "system"}),
63-
ChildMeasurement(891564, {"cpu": "cpu1", "state": "idle"}),
64-
ChildMeasurement(1244, {"cpu": "cpu1", "state": "iowait"}),
65-
ChildMeasurement(0, {"cpu": "cpu1", "state": "irq"}),
66-
ChildMeasurement(2410, {"cpu": "cpu1", "state": "softirq"}),
67-
ChildMeasurement(418, {"cpu": "cpu1", "state": "guest"}),
68-
ChildMeasurement(0, {"cpu": "cpu1", "state": "guest_nice"}),
43+
Measurement(6150, {"cpu": "cpu0", "state": "user"}),
44+
Measurement(3177, {"cpu": "cpu0", "state": "nice"}),
45+
Measurement(5946, {"cpu": "cpu0", "state": "system"}),
46+
Measurement(891264, {"cpu": "cpu0", "state": "idle"}),
47+
Measurement(1296, {"cpu": "cpu0", "state": "iowait"}),
48+
Measurement(0, {"cpu": "cpu0", "state": "irq"}),
49+
Measurement(8343, {"cpu": "cpu0", "state": "softirq"}),
50+
Measurement(421, {"cpu": "cpu0", "state": "guest"}),
51+
Measurement(0, {"cpu": "cpu0", "state": "guest_nice"}),
52+
Measurement(5882, {"cpu": "cpu1", "state": "user"}),
53+
Measurement(3491, {"cpu": "cpu1", "state": "nice"}),
54+
Measurement(6404, {"cpu": "cpu1", "state": "system"}),
55+
Measurement(891564, {"cpu": "cpu1", "state": "idle"}),
56+
Measurement(1244, {"cpu": "cpu1", "state": "iowait"}),
57+
Measurement(0, {"cpu": "cpu1", "state": "irq"}),
58+
Measurement(2410, {"cpu": "cpu1", "state": "softirq"}),
59+
Measurement(418, {"cpu": "cpu1", "state": "guest"}),
60+
Measurement(0, {"cpu": "cpu1", "state": "guest_nice"}),
6961
]
7062

7163
def test_cpu_time_callback(self):
7264
meter = _DefaultMeter("foo")
7365

74-
def cpu_time_callback() -> Iterable[Measurement]:
66+
def cpu_time_callback() -> Iterable[Measurement[int]]:
7567
procstat = io.StringIO(self.procstat_str)
7668
procstat.readline() # skip the first line
7769
for line in procstat:
7870
if not line.startswith("cpu"):
7971
break
8072
cpu, *states = line.split()
81-
yield ChildMeasurement(
73+
yield Measurement(
8274
int(states[0]) // 100, {"cpu": cpu, "state": "user"}
8375
)
84-
yield ChildMeasurement(
76+
yield Measurement(
8577
int(states[1]) // 100, {"cpu": cpu, "state": "nice"}
8678
)
87-
yield ChildMeasurement(
79+
yield Measurement(
8880
int(states[2]) // 100, {"cpu": cpu, "state": "system"}
8981
)
90-
yield ChildMeasurement(
82+
yield Measurement(
9183
int(states[3]) // 100, {"cpu": cpu, "state": "idle"}
9284
)
93-
yield ChildMeasurement(
85+
yield Measurement(
9486
int(states[4]) // 100, {"cpu": cpu, "state": "iowait"}
9587
)
96-
yield ChildMeasurement(
88+
yield Measurement(
9789
int(states[5]) // 100, {"cpu": cpu, "state": "irq"}
9890
)
99-
yield ChildMeasurement(
91+
yield Measurement(
10092
int(states[6]) // 100, {"cpu": cpu, "state": "softirq"}
10193
)
102-
yield ChildMeasurement(
94+
yield Measurement(
10395
int(states[7]) // 100, {"cpu": cpu, "state": "guest"}
10496
)
105-
yield ChildMeasurement(
97+
yield Measurement(
10698
int(states[8]) // 100, {"cpu": cpu, "state": "guest_nice"}
10799
)
108100

@@ -119,7 +111,7 @@ def test_cpu_time_generator(self):
119111
meter = _DefaultMeter("foo")
120112

121113
def cpu_time_generator() -> Generator[
122-
Iterable[Measurement], None, None
114+
Iterable[Measurement[int]], None, None
123115
]:
124116
while True:
125117
measurements = []
@@ -130,54 +122,54 @@ def cpu_time_generator() -> Generator[
130122
break
131123
cpu, *states = line.split()
132124
measurements.append(
133-
ChildMeasurement(
125+
Measurement(
134126
int(states[0]) // 100,
135127
{"cpu": cpu, "state": "user"},
136128
)
137129
)
138130
measurements.append(
139-
ChildMeasurement(
131+
Measurement(
140132
int(states[1]) // 100,
141133
{"cpu": cpu, "state": "nice"},
142134
)
143135
)
144136
measurements.append(
145-
ChildMeasurement(
137+
Measurement(
146138
int(states[2]) // 100,
147139
{"cpu": cpu, "state": "system"},
148140
)
149141
)
150142
measurements.append(
151-
ChildMeasurement(
143+
Measurement(
152144
int(states[3]) // 100,
153145
{"cpu": cpu, "state": "idle"},
154146
)
155147
)
156148
measurements.append(
157-
ChildMeasurement(
149+
Measurement(
158150
int(states[4]) // 100,
159151
{"cpu": cpu, "state": "iowait"},
160152
)
161153
)
162154
measurements.append(
163-
ChildMeasurement(
155+
Measurement(
164156
int(states[5]) // 100, {"cpu": cpu, "state": "irq"}
165157
)
166158
)
167159
measurements.append(
168-
ChildMeasurement(
160+
Measurement(
169161
int(states[6]) // 100,
170162
{"cpu": cpu, "state": "softirq"},
171163
)
172164
)
173165
measurements.append(
174-
ChildMeasurement(
166+
Measurement(
175167
int(states[7]) // 100,
176168
{"cpu": cpu, "state": "guest"},
177169
)
178170
)
179171
measurements.append(
180-
ChildMeasurement(
172+
Measurement(
181173
int(states[8]) // 100,
182174
{"cpu": cpu, "state": "guest_nice"},
183175
)

0 commit comments

Comments
 (0)