Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/opentelemetry/metrics/_internal/instrument.py: 62%
120 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
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.
15# pylint: disable=too-many-ancestors
18from abc import ABC, abstractmethod
19from dataclasses import dataclass
20from logging import getLogger
21from re import compile as re_compile
22from typing import (
23 Callable,
24 Dict,
25 Generator,
26 Generic,
27 Iterable,
28 Optional,
29 Sequence,
30 TypeVar,
31 Union,
32)
34# pylint: disable=unused-import; needed for typing and sphinx
35from opentelemetry import metrics
36from opentelemetry.metrics._internal.observation import Observation
37from opentelemetry.util.types import Attributes
39_logger = getLogger(__name__)
41_name_regex = re_compile(r"[a-zA-Z][-_.a-zA-Z0-9]{0,62}")
42_unit_regex = re_compile(r"[\x00-\x7F]{0,63}")
45@dataclass(frozen=True)
46class CallbackOptions:
47 """Options for the callback
49 Args:
50 timeout_millis: Timeout for the callback's execution. If the callback does asynchronous
51 work (e.g. HTTP requests), it should respect this timeout.
52 """
54 timeout_millis: float = 10_000
57InstrumentT = TypeVar("InstrumentT", bound="Instrument")
58CallbackT = Union[
59 Callable[[CallbackOptions], Iterable[Observation]],
60 Generator[Iterable[Observation], CallbackOptions, None],
61]
64class Instrument(ABC):
65 """Abstract class that serves as base for all instruments."""
67 @abstractmethod
68 def __init__(
69 self,
70 name: str,
71 unit: str = "",
72 description: str = "",
73 ) -> None:
74 pass
76 @staticmethod
77 def _check_name_unit_description(
78 name: str, unit: str, description: str
79 ) -> Dict[str, Optional[str]]:
80 """
81 Checks the following instrument name, unit and description for
82 compliance with the spec.
84 Returns a dict with keys "name", "unit" and "description", the
85 corresponding values will be the checked strings or `None` if the value
86 is invalid. If valid, the checked strings should be used instead of the
87 original values.
88 """
90 result: Dict[str, Optional[str]] = {}
92 if _name_regex.fullmatch(name) is not None:
93 result["name"] = name
94 else:
95 result["name"] = None
97 if unit is None:
98 unit = ""
99 if _unit_regex.fullmatch(unit) is not None:
100 result["unit"] = unit
101 else:
102 result["unit"] = None
104 if description is None:
105 result["description"] = ""
106 else:
107 result["description"] = description
109 return result
112class _ProxyInstrument(ABC, Generic[InstrumentT]):
113 def __init__(
114 self,
115 name: str,
116 unit: str = "",
117 description: str = "",
118 ) -> None:
119 self._name = name
120 self._unit = unit
121 self._description = description
122 self._real_instrument: Optional[InstrumentT] = None
124 def on_meter_set(self, meter: "metrics.Meter") -> None:
125 """Called when a real meter is set on the creating _ProxyMeter"""
127 # We don't need any locking on proxy instruments because it's OK if some
128 # measurements get dropped while a real backing instrument is being
129 # created.
130 self._real_instrument = self._create_real_instrument(meter)
132 @abstractmethod
133 def _create_real_instrument(self, meter: "metrics.Meter") -> InstrumentT:
134 """Create an instance of the real instrument. Implement this."""
137class _ProxyAsynchronousInstrument(_ProxyInstrument[InstrumentT]):
138 def __init__(
139 self,
140 name: str,
141 callbacks: Optional[Sequence[CallbackT]] = None,
142 unit: str = "",
143 description: str = "",
144 ) -> None:
145 super().__init__(name, unit, description)
146 self._callbacks = callbacks
149class Synchronous(Instrument):
150 """Base class for all synchronous instruments"""
153class Asynchronous(Instrument):
154 """Base class for all asynchronous instruments"""
156 @abstractmethod
157 def __init__(
158 self,
159 name: str,
160 callbacks: Optional[Sequence[CallbackT]] = None,
161 unit: str = "",
162 description: str = "",
163 ) -> None:
164 super().__init__(name, unit=unit, description=description)
167class Counter(Synchronous):
168 """A Counter is a synchronous `Instrument` which supports non-negative increments."""
170 @abstractmethod
171 def add(
172 self,
173 amount: Union[int, float],
174 attributes: Optional[Attributes] = None,
175 ) -> None:
176 pass
179class NoOpCounter(Counter):
180 """No-op implementation of `Counter`."""
182 def __init__(
183 self,
184 name: str,
185 unit: str = "",
186 description: str = "",
187 ) -> None:
188 super().__init__(name, unit=unit, description=description)
190 def add(
191 self,
192 amount: Union[int, float],
193 attributes: Optional[Attributes] = None,
194 ) -> None:
195 return super().add(amount, attributes=attributes)
198class _ProxyCounter(_ProxyInstrument[Counter], Counter):
199 def add(
200 self,
201 amount: Union[int, float],
202 attributes: Optional[Attributes] = None,
203 ) -> None:
204 if self._real_instrument:
205 self._real_instrument.add(amount, attributes)
207 def _create_real_instrument(self, meter: "metrics.Meter") -> Counter:
208 return meter.create_counter(self._name, self._unit, self._description)
211class UpDownCounter(Synchronous):
212 """An UpDownCounter is a synchronous `Instrument` which supports increments and decrements."""
214 @abstractmethod
215 def add(
216 self,
217 amount: Union[int, float],
218 attributes: Optional[Attributes] = None,
219 ) -> None:
220 pass
223class NoOpUpDownCounter(UpDownCounter):
224 """No-op implementation of `UpDownCounter`."""
226 def __init__(
227 self,
228 name: str,
229 unit: str = "",
230 description: str = "",
231 ) -> None:
232 super().__init__(name, unit=unit, description=description)
234 def add(
235 self,
236 amount: Union[int, float],
237 attributes: Optional[Attributes] = None,
238 ) -> None:
239 return super().add(amount, attributes=attributes)
242class _ProxyUpDownCounter(_ProxyInstrument[UpDownCounter], UpDownCounter):
243 def add(
244 self,
245 amount: Union[int, float],
246 attributes: Optional[Attributes] = None,
247 ) -> None:
248 if self._real_instrument:
249 self._real_instrument.add(amount, attributes)
251 def _create_real_instrument(self, meter: "metrics.Meter") -> UpDownCounter:
252 return meter.create_up_down_counter(
253 self._name, self._unit, self._description
254 )
257class ObservableCounter(Asynchronous):
258 """An ObservableCounter is an asynchronous `Instrument` which reports monotonically
259 increasing value(s) when the instrument is being observed.
260 """
263class NoOpObservableCounter(ObservableCounter):
264 """No-op implementation of `ObservableCounter`."""
266 def __init__(
267 self,
268 name: str,
269 callbacks: Optional[Sequence[CallbackT]] = None,
270 unit: str = "",
271 description: str = "",
272 ) -> None:
273 super().__init__(name, callbacks, unit=unit, description=description)
276class _ProxyObservableCounter(
277 _ProxyAsynchronousInstrument[ObservableCounter], ObservableCounter
278):
279 def _create_real_instrument(
280 self, meter: "metrics.Meter"
281 ) -> ObservableCounter:
282 return meter.create_observable_counter(
283 self._name, self._callbacks, self._unit, self._description
284 )
287class ObservableUpDownCounter(Asynchronous):
288 """An ObservableUpDownCounter is an asynchronous `Instrument` which reports additive value(s) (e.g.
289 the process heap size - it makes sense to report the heap size from multiple processes and sum them
290 up, so we get the total heap usage) when the instrument is being observed.
291 """
294class NoOpObservableUpDownCounter(ObservableUpDownCounter):
295 """No-op implementation of `ObservableUpDownCounter`."""
297 def __init__(
298 self,
299 name: str,
300 callbacks: Optional[Sequence[CallbackT]] = None,
301 unit: str = "",
302 description: str = "",
303 ) -> None:
304 super().__init__(name, callbacks, unit=unit, description=description)
307class _ProxyObservableUpDownCounter(
308 _ProxyAsynchronousInstrument[ObservableUpDownCounter],
309 ObservableUpDownCounter,
310):
311 def _create_real_instrument(
312 self, meter: "metrics.Meter"
313 ) -> ObservableUpDownCounter:
314 return meter.create_observable_up_down_counter(
315 self._name, self._callbacks, self._unit, self._description
316 )
319class Histogram(Synchronous):
320 """Histogram is a synchronous `Instrument` which can be used to report arbitrary values
321 that are likely to be statistically meaningful. It is intended for statistics such as
322 histograms, summaries, and percentile.
323 """
325 @abstractmethod
326 def record(
327 self,
328 amount: Union[int, float],
329 attributes: Optional[Attributes] = None,
330 ) -> None:
331 pass
334class NoOpHistogram(Histogram):
335 """No-op implementation of `Histogram`."""
337 def __init__(
338 self,
339 name: str,
340 unit: str = "",
341 description: str = "",
342 ) -> None:
343 super().__init__(name, unit=unit, description=description)
345 def record(
346 self,
347 amount: Union[int, float],
348 attributes: Optional[Attributes] = None,
349 ) -> None:
350 return super().record(amount, attributes=attributes)
353class _ProxyHistogram(_ProxyInstrument[Histogram], Histogram):
354 def record(
355 self,
356 amount: Union[int, float],
357 attributes: Optional[Attributes] = None,
358 ) -> None:
359 if self._real_instrument:
360 self._real_instrument.record(amount, attributes)
362 def _create_real_instrument(self, meter: "metrics.Meter") -> Histogram:
363 return meter.create_histogram(
364 self._name, self._unit, self._description
365 )
368class ObservableGauge(Asynchronous):
369 """Asynchronous Gauge is an asynchronous `Instrument` which reports non-additive value(s) (e.g.
370 the room temperature - it makes no sense to report the temperature value from multiple rooms
371 and sum them up) when the instrument is being observed.
372 """
375class NoOpObservableGauge(ObservableGauge):
376 """No-op implementation of `ObservableGauge`."""
378 def __init__(
379 self,
380 name: str,
381 callbacks: Optional[Sequence[CallbackT]] = None,
382 unit: str = "",
383 description: str = "",
384 ) -> None:
385 super().__init__(name, callbacks, unit=unit, description=description)
388class _ProxyObservableGauge(
389 _ProxyAsynchronousInstrument[ObservableGauge],
390 ObservableGauge,
391):
392 def _create_real_instrument(
393 self, meter: "metrics.Meter"
394 ) -> ObservableGauge:
395 return meter.create_observable_gauge(
396 self._name, self._callbacks, self._unit, self._description
397 )