structure saas with tools
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""
|
||||
The OpenTelemetry metrics API describes the classes used to generate
|
||||
metrics.
|
||||
|
||||
The :class:`.MeterProvider` provides users access to the :class:`.Meter` which in
|
||||
turn is used to create :class:`.Instrument` objects. The :class:`.Instrument` objects are
|
||||
used to record measurements.
|
||||
|
||||
This module provides abstract (i.e. unimplemented) classes required for
|
||||
metrics, and a concrete no-op implementation :class:`.NoOpMeter` that allows applications
|
||||
to use the API package alone without a supporting implementation.
|
||||
|
||||
To get a meter, you need to provide the package name from which you are
|
||||
calling the meter APIs to OpenTelemetry by calling `MeterProvider.get_meter`
|
||||
with the calling instrumentation name and the version of your package.
|
||||
|
||||
The following code shows how to obtain a meter using the global :class:`.MeterProvider`::
|
||||
|
||||
from opentelemetry.metrics import get_meter
|
||||
|
||||
meter = get_meter("example-meter")
|
||||
counter = meter.create_counter("example-counter")
|
||||
|
||||
.. versionadded:: 1.10.0
|
||||
.. versionchanged:: 1.12.0rc
|
||||
"""
|
||||
|
||||
from opentelemetry.metrics._internal import (
|
||||
Meter,
|
||||
MeterProvider,
|
||||
NoOpMeter,
|
||||
NoOpMeterProvider,
|
||||
get_meter,
|
||||
get_meter_provider,
|
||||
set_meter_provider,
|
||||
)
|
||||
from opentelemetry.metrics._internal.instrument import (
|
||||
Asynchronous,
|
||||
CallbackOptions,
|
||||
CallbackT,
|
||||
Counter,
|
||||
Histogram,
|
||||
Instrument,
|
||||
NoOpCounter,
|
||||
NoOpHistogram,
|
||||
NoOpObservableCounter,
|
||||
NoOpObservableGauge,
|
||||
NoOpObservableUpDownCounter,
|
||||
NoOpUpDownCounter,
|
||||
ObservableCounter,
|
||||
ObservableGauge,
|
||||
ObservableUpDownCounter,
|
||||
Synchronous,
|
||||
UpDownCounter,
|
||||
)
|
||||
from opentelemetry.metrics._internal.instrument import Gauge as _Gauge
|
||||
from opentelemetry.metrics._internal.instrument import NoOpGauge as _NoOpGauge
|
||||
from opentelemetry.metrics._internal.observation import Observation
|
||||
|
||||
for obj in [
|
||||
Counter,
|
||||
Synchronous,
|
||||
Asynchronous,
|
||||
CallbackOptions,
|
||||
_Gauge,
|
||||
_NoOpGauge,
|
||||
get_meter_provider,
|
||||
get_meter,
|
||||
Histogram,
|
||||
Meter,
|
||||
MeterProvider,
|
||||
Instrument,
|
||||
NoOpCounter,
|
||||
NoOpHistogram,
|
||||
NoOpMeter,
|
||||
NoOpMeterProvider,
|
||||
NoOpObservableCounter,
|
||||
NoOpObservableGauge,
|
||||
NoOpObservableUpDownCounter,
|
||||
NoOpUpDownCounter,
|
||||
ObservableCounter,
|
||||
ObservableGauge,
|
||||
ObservableUpDownCounter,
|
||||
Observation,
|
||||
set_meter_provider,
|
||||
UpDownCounter,
|
||||
]:
|
||||
obj.__module__ = __name__
|
||||
|
||||
__all__ = [
|
||||
"CallbackOptions",
|
||||
"MeterProvider",
|
||||
"NoOpMeterProvider",
|
||||
"Meter",
|
||||
"Counter",
|
||||
"_Gauge",
|
||||
"_NoOpGauge",
|
||||
"NoOpCounter",
|
||||
"UpDownCounter",
|
||||
"NoOpUpDownCounter",
|
||||
"Histogram",
|
||||
"NoOpHistogram",
|
||||
"ObservableCounter",
|
||||
"NoOpObservableCounter",
|
||||
"ObservableUpDownCounter",
|
||||
"Instrument",
|
||||
"Synchronous",
|
||||
"Asynchronous",
|
||||
"NoOpObservableGauge",
|
||||
"ObservableGauge",
|
||||
"NoOpObservableUpDownCounter",
|
||||
"get_meter",
|
||||
"get_meter_provider",
|
||||
"set_meter_provider",
|
||||
"Observation",
|
||||
"CallbackT",
|
||||
"NoOpMeter",
|
||||
]
|
||||
Binary file not shown.
@@ -0,0 +1,889 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# pylint: disable=too-many-ancestors
|
||||
|
||||
"""
|
||||
The OpenTelemetry metrics API describes the classes used to generate
|
||||
metrics.
|
||||
|
||||
The :class:`.MeterProvider` provides users access to the :class:`.Meter` which in
|
||||
turn is used to create :class:`.Instrument` objects. The :class:`.Instrument` objects are
|
||||
used to record measurements.
|
||||
|
||||
This module provides abstract (i.e. unimplemented) classes required for
|
||||
metrics, and a concrete no-op implementation :class:`.NoOpMeter` that allows applications
|
||||
to use the API package alone without a supporting implementation.
|
||||
|
||||
To get a meter, you need to provide the package name from which you are
|
||||
calling the meter APIs to OpenTelemetry by calling `MeterProvider.get_meter`
|
||||
with the calling instrumentation name and the version of your package.
|
||||
|
||||
The following code shows how to obtain a meter using the global :class:`.MeterProvider`::
|
||||
|
||||
from opentelemetry.metrics import get_meter
|
||||
|
||||
meter = get_meter("example-meter")
|
||||
counter = meter.create_counter("example-counter")
|
||||
|
||||
.. versionadded:: 1.10.0
|
||||
"""
|
||||
|
||||
import warnings
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from logging import getLogger
|
||||
from os import environ
|
||||
from threading import Lock
|
||||
from typing import Dict, List, Optional, Sequence, Union, cast
|
||||
|
||||
from opentelemetry.environment_variables import OTEL_PYTHON_METER_PROVIDER
|
||||
from opentelemetry.metrics._internal.instrument import (
|
||||
CallbackT,
|
||||
Counter,
|
||||
Gauge,
|
||||
Histogram,
|
||||
NoOpCounter,
|
||||
NoOpGauge,
|
||||
NoOpHistogram,
|
||||
NoOpObservableCounter,
|
||||
NoOpObservableGauge,
|
||||
NoOpObservableUpDownCounter,
|
||||
NoOpUpDownCounter,
|
||||
ObservableCounter,
|
||||
ObservableGauge,
|
||||
ObservableUpDownCounter,
|
||||
UpDownCounter,
|
||||
_MetricsHistogramAdvisory,
|
||||
_ProxyCounter,
|
||||
_ProxyGauge,
|
||||
_ProxyHistogram,
|
||||
_ProxyObservableCounter,
|
||||
_ProxyObservableGauge,
|
||||
_ProxyObservableUpDownCounter,
|
||||
_ProxyUpDownCounter,
|
||||
)
|
||||
from opentelemetry.util._once import Once
|
||||
from opentelemetry.util._providers import _load_provider
|
||||
from opentelemetry.util.types import (
|
||||
Attributes,
|
||||
)
|
||||
|
||||
_logger = getLogger(__name__)
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
_ProxyInstrumentT = Union[
|
||||
_ProxyCounter,
|
||||
_ProxyHistogram,
|
||||
_ProxyGauge,
|
||||
_ProxyObservableCounter,
|
||||
_ProxyObservableGauge,
|
||||
_ProxyObservableUpDownCounter,
|
||||
_ProxyUpDownCounter,
|
||||
]
|
||||
|
||||
|
||||
class MeterProvider(ABC):
|
||||
"""
|
||||
MeterProvider is the entry point of the API. It provides access to `Meter` instances.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_meter(
|
||||
self,
|
||||
name: str,
|
||||
version: Optional[str] = None,
|
||||
schema_url: Optional[str] = None,
|
||||
attributes: Optional[Attributes] = None,
|
||||
) -> "Meter":
|
||||
"""Returns a `Meter` for use by the given instrumentation library.
|
||||
|
||||
For any two calls it is undefined whether the same or different
|
||||
`Meter` instances are returned, even for different library names.
|
||||
|
||||
This function may return different `Meter` types (e.g. a no-op meter
|
||||
vs. a functional meter).
|
||||
|
||||
Args:
|
||||
name: The name of the instrumenting module.
|
||||
``__name__`` may not be used as this can result in
|
||||
different meter names if the meters are in different files.
|
||||
It is better to use a fixed string that can be imported where
|
||||
needed and used consistently as the name of the meter.
|
||||
|
||||
This should *not* be the name of the module that is
|
||||
instrumented but the name of the module doing the instrumentation.
|
||||
E.g., instead of ``"requests"``, use
|
||||
``"opentelemetry.instrumentation.requests"``.
|
||||
|
||||
version: Optional. The version string of the
|
||||
instrumenting library. Usually this should be the same as
|
||||
``importlib.metadata.version(instrumenting_library_name)``.
|
||||
|
||||
schema_url: Optional. Specifies the Schema URL of the emitted telemetry.
|
||||
attributes: Optional. Attributes that are associated with the emitted telemetry.
|
||||
"""
|
||||
|
||||
|
||||
class NoOpMeterProvider(MeterProvider):
|
||||
"""The default MeterProvider used when no MeterProvider implementation is available."""
|
||||
|
||||
def get_meter(
|
||||
self,
|
||||
name: str,
|
||||
version: Optional[str] = None,
|
||||
schema_url: Optional[str] = None,
|
||||
attributes: Optional[Attributes] = None,
|
||||
) -> "Meter":
|
||||
"""Returns a NoOpMeter."""
|
||||
return NoOpMeter(name, version=version, schema_url=schema_url)
|
||||
|
||||
|
||||
class _ProxyMeterProvider(MeterProvider):
|
||||
def __init__(self) -> None:
|
||||
self._lock = Lock()
|
||||
self._meters: List[_ProxyMeter] = []
|
||||
self._real_meter_provider: Optional[MeterProvider] = None
|
||||
|
||||
def get_meter(
|
||||
self,
|
||||
name: str,
|
||||
version: Optional[str] = None,
|
||||
schema_url: Optional[str] = None,
|
||||
attributes: Optional[Attributes] = None,
|
||||
) -> "Meter":
|
||||
with self._lock:
|
||||
if self._real_meter_provider is not None:
|
||||
return self._real_meter_provider.get_meter(
|
||||
name, version, schema_url
|
||||
)
|
||||
|
||||
meter = _ProxyMeter(name, version=version, schema_url=schema_url)
|
||||
self._meters.append(meter)
|
||||
return meter
|
||||
|
||||
def on_set_meter_provider(self, meter_provider: MeterProvider) -> None:
|
||||
with self._lock:
|
||||
self._real_meter_provider = meter_provider
|
||||
for meter in self._meters:
|
||||
meter.on_set_meter_provider(meter_provider)
|
||||
|
||||
|
||||
@dataclass
|
||||
class _InstrumentRegistrationStatus:
|
||||
instrument_id: str
|
||||
already_registered: bool
|
||||
conflict: bool
|
||||
current_advisory: Optional[_MetricsHistogramAdvisory]
|
||||
|
||||
|
||||
class Meter(ABC):
|
||||
"""Handles instrument creation.
|
||||
|
||||
This class provides methods for creating instruments which are then
|
||||
used to produce measurements.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
version: Optional[str] = None,
|
||||
schema_url: Optional[str] = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self._name = name
|
||||
self._version = version
|
||||
self._schema_url = schema_url
|
||||
self._instrument_ids: Dict[
|
||||
str, Optional[_MetricsHistogramAdvisory]
|
||||
] = {}
|
||||
self._instrument_ids_lock = Lock()
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""
|
||||
The name of the instrumenting module.
|
||||
"""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def version(self) -> Optional[str]:
|
||||
"""
|
||||
The version string of the instrumenting library.
|
||||
"""
|
||||
return self._version
|
||||
|
||||
@property
|
||||
def schema_url(self) -> Optional[str]:
|
||||
"""
|
||||
Specifies the Schema URL of the emitted telemetry
|
||||
"""
|
||||
return self._schema_url
|
||||
|
||||
def _register_instrument(
|
||||
self,
|
||||
name: str,
|
||||
type_: type,
|
||||
unit: str,
|
||||
description: str,
|
||||
advisory: Optional[_MetricsHistogramAdvisory] = None,
|
||||
) -> _InstrumentRegistrationStatus:
|
||||
"""
|
||||
Register an instrument with the name, type, unit and description as
|
||||
identifying keys and the advisory as value.
|
||||
|
||||
Returns a tuple. The first value is the instrument id.
|
||||
The second value is an `_InstrumentRegistrationStatus` where
|
||||
`already_registered` is `True` if the instrument has been registered
|
||||
already.
|
||||
If `conflict` is set to True the `current_advisory` attribute contains
|
||||
the registered instrument advisory.
|
||||
"""
|
||||
|
||||
instrument_id = ",".join(
|
||||
[name.strip().lower(), type_.__name__, unit, description]
|
||||
)
|
||||
|
||||
already_registered = False
|
||||
conflict = False
|
||||
current_advisory = None
|
||||
|
||||
with self._instrument_ids_lock:
|
||||
# we are not using get because None is a valid value
|
||||
already_registered = instrument_id in self._instrument_ids
|
||||
if already_registered:
|
||||
current_advisory = self._instrument_ids[instrument_id]
|
||||
conflict = current_advisory != advisory
|
||||
else:
|
||||
self._instrument_ids[instrument_id] = advisory
|
||||
|
||||
return _InstrumentRegistrationStatus(
|
||||
instrument_id=instrument_id,
|
||||
already_registered=already_registered,
|
||||
conflict=conflict,
|
||||
current_advisory=current_advisory,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _log_instrument_registration_conflict(
|
||||
name: str,
|
||||
instrumentation_type: str,
|
||||
unit: str,
|
||||
description: str,
|
||||
status: _InstrumentRegistrationStatus,
|
||||
) -> None:
|
||||
_logger.warning(
|
||||
"An instrument with name %s, type %s, unit %s and "
|
||||
"description %s has been created already with a "
|
||||
"different advisory value %s and will be used instead.",
|
||||
name,
|
||||
instrumentation_type,
|
||||
unit,
|
||||
description,
|
||||
status.current_advisory,
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def create_counter(
|
||||
self,
|
||||
name: str,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> Counter:
|
||||
"""Creates a `Counter` instrument
|
||||
|
||||
Args:
|
||||
name: The name of the instrument to be created
|
||||
unit: The unit for observations this instrument reports. For
|
||||
example, ``By`` for bytes. UCUM units are recommended.
|
||||
description: A description for this instrument and what it measures.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def create_up_down_counter(
|
||||
self,
|
||||
name: str,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> UpDownCounter:
|
||||
"""Creates an `UpDownCounter` instrument
|
||||
|
||||
Args:
|
||||
name: The name of the instrument to be created
|
||||
unit: The unit for observations this instrument reports. For
|
||||
example, ``By`` for bytes. UCUM units are recommended.
|
||||
description: A description for this instrument and what it measures.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def create_observable_counter(
|
||||
self,
|
||||
name: str,
|
||||
callbacks: Optional[Sequence[CallbackT]] = None,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> ObservableCounter:
|
||||
"""Creates an `ObservableCounter` instrument
|
||||
|
||||
An observable counter observes a monotonically increasing count by calling provided
|
||||
callbacks which accept a :class:`~opentelemetry.metrics.CallbackOptions` and return
|
||||
multiple :class:`~opentelemetry.metrics.Observation`.
|
||||
|
||||
For example, an observable counter could be used to report system CPU
|
||||
time periodically. Here is a basic implementation::
|
||||
|
||||
def cpu_time_callback(options: CallbackOptions) -> Iterable[Observation]:
|
||||
observations = []
|
||||
with open("/proc/stat") as procstat:
|
||||
procstat.readline() # skip the first line
|
||||
for line in procstat:
|
||||
if not line.startswith("cpu"): break
|
||||
cpu, *states = line.split()
|
||||
observations.append(Observation(int(states[0]) // 100, {"cpu": cpu, "state": "user"}))
|
||||
observations.append(Observation(int(states[1]) // 100, {"cpu": cpu, "state": "nice"}))
|
||||
observations.append(Observation(int(states[2]) // 100, {"cpu": cpu, "state": "system"}))
|
||||
# ... other states
|
||||
return observations
|
||||
|
||||
meter.create_observable_counter(
|
||||
"system.cpu.time",
|
||||
callbacks=[cpu_time_callback],
|
||||
unit="s",
|
||||
description="CPU time"
|
||||
)
|
||||
|
||||
To reduce memory usage, you can use generator callbacks instead of
|
||||
building the full list::
|
||||
|
||||
def cpu_time_callback(options: CallbackOptions) -> Iterable[Observation]:
|
||||
with open("/proc/stat") as procstat:
|
||||
procstat.readline() # skip the first line
|
||||
for line in procstat:
|
||||
if not line.startswith("cpu"): break
|
||||
cpu, *states = line.split()
|
||||
yield Observation(int(states[0]) // 100, {"cpu": cpu, "state": "user"})
|
||||
yield Observation(int(states[1]) // 100, {"cpu": cpu, "state": "nice"})
|
||||
# ... other states
|
||||
|
||||
Alternatively, you can pass a sequence of generators directly instead of a sequence of
|
||||
callbacks, which each should return iterables of :class:`~opentelemetry.metrics.Observation`::
|
||||
|
||||
def cpu_time_callback(states_to_include: set[str]) -> Iterable[Iterable[Observation]]:
|
||||
# accept options sent in from OpenTelemetry
|
||||
options = yield
|
||||
while True:
|
||||
observations = []
|
||||
with open("/proc/stat") as procstat:
|
||||
procstat.readline() # skip the first line
|
||||
for line in procstat:
|
||||
if not line.startswith("cpu"): break
|
||||
cpu, *states = line.split()
|
||||
if "user" in states_to_include:
|
||||
observations.append(Observation(int(states[0]) // 100, {"cpu": cpu, "state": "user"}))
|
||||
if "nice" in states_to_include:
|
||||
observations.append(Observation(int(states[1]) // 100, {"cpu": cpu, "state": "nice"}))
|
||||
# ... other states
|
||||
# yield the observations and receive the options for next iteration
|
||||
options = yield observations
|
||||
|
||||
meter.create_observable_counter(
|
||||
"system.cpu.time",
|
||||
callbacks=[cpu_time_callback({"user", "system"})],
|
||||
unit="s",
|
||||
description="CPU time"
|
||||
)
|
||||
|
||||
The :class:`~opentelemetry.metrics.CallbackOptions` contain a timeout which the
|
||||
callback should respect. For example if the callback does asynchronous work, like
|
||||
making HTTP requests, it should respect the timeout::
|
||||
|
||||
def scrape_http_callback(options: CallbackOptions) -> Iterable[Observation]:
|
||||
r = requests.get('http://scrapethis.com', timeout=options.timeout_millis / 10**3)
|
||||
for value in r.json():
|
||||
yield Observation(value)
|
||||
|
||||
Args:
|
||||
name: The name of the instrument to be created
|
||||
callbacks: A sequence of callbacks that return an iterable of
|
||||
:class:`~opentelemetry.metrics.Observation`. Alternatively, can be a sequence of generators that each
|
||||
yields iterables of :class:`~opentelemetry.metrics.Observation`.
|
||||
unit: The unit for observations this instrument reports. For
|
||||
example, ``By`` for bytes. UCUM units are recommended.
|
||||
description: A description for this instrument and what it measures.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def create_histogram(
|
||||
self,
|
||||
name: str,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
*,
|
||||
explicit_bucket_boundaries_advisory: Optional[Sequence[float]] = None,
|
||||
) -> Histogram:
|
||||
"""Creates a :class:`~opentelemetry.metrics.Histogram` instrument
|
||||
|
||||
Args:
|
||||
name: The name of the instrument to be created
|
||||
unit: The unit for observations this instrument reports. For
|
||||
example, ``By`` for bytes. UCUM units are recommended.
|
||||
description: A description for this instrument and what it measures.
|
||||
"""
|
||||
|
||||
def create_gauge( # type: ignore # pylint: disable=no-self-use
|
||||
self,
|
||||
name: str,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> Gauge: # pyright: ignore[reportReturnType]
|
||||
"""Creates a ``Gauge`` instrument
|
||||
|
||||
Args:
|
||||
name: The name of the instrument to be created
|
||||
unit: The unit for observations this instrument reports. For
|
||||
example, ``By`` for bytes. UCUM units are recommended.
|
||||
description: A description for this instrument and what it measures.
|
||||
"""
|
||||
warnings.warn("create_gauge() is not implemented and will be a no-op")
|
||||
|
||||
@abstractmethod
|
||||
def create_observable_gauge(
|
||||
self,
|
||||
name: str,
|
||||
callbacks: Optional[Sequence[CallbackT]] = None,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> ObservableGauge:
|
||||
"""Creates an `ObservableGauge` instrument
|
||||
|
||||
Args:
|
||||
name: The name of the instrument to be created
|
||||
callbacks: A sequence of callbacks that return an iterable of
|
||||
:class:`~opentelemetry.metrics.Observation`. Alternatively, can be a generator that yields iterables
|
||||
of :class:`~opentelemetry.metrics.Observation`.
|
||||
unit: The unit for observations this instrument reports. For
|
||||
example, ``By`` for bytes. UCUM units are recommended.
|
||||
description: A description for this instrument and what it measures.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def create_observable_up_down_counter(
|
||||
self,
|
||||
name: str,
|
||||
callbacks: Optional[Sequence[CallbackT]] = None,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> ObservableUpDownCounter:
|
||||
"""Creates an `ObservableUpDownCounter` instrument
|
||||
|
||||
Args:
|
||||
name: The name of the instrument to be created
|
||||
callbacks: A sequence of callbacks that return an iterable of
|
||||
:class:`~opentelemetry.metrics.Observation`. Alternatively, can be a generator that yields iterables
|
||||
of :class:`~opentelemetry.metrics.Observation`.
|
||||
unit: The unit for observations this instrument reports. For
|
||||
example, ``By`` for bytes. UCUM units are recommended.
|
||||
description: A description for this instrument and what it measures.
|
||||
"""
|
||||
|
||||
|
||||
class _ProxyMeter(Meter):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
version: Optional[str] = None,
|
||||
schema_url: Optional[str] = None,
|
||||
) -> None:
|
||||
super().__init__(name, version=version, schema_url=schema_url)
|
||||
self._lock = Lock()
|
||||
self._instruments: List[_ProxyInstrumentT] = []
|
||||
self._real_meter: Optional[Meter] = None
|
||||
|
||||
def on_set_meter_provider(self, meter_provider: MeterProvider) -> None:
|
||||
"""Called when a real meter provider is set on the creating _ProxyMeterProvider
|
||||
|
||||
Creates a real backing meter for this instance and notifies all created
|
||||
instruments so they can create real backing instruments.
|
||||
"""
|
||||
real_meter = meter_provider.get_meter(
|
||||
self._name, self._version, self._schema_url
|
||||
)
|
||||
|
||||
with self._lock:
|
||||
self._real_meter = real_meter
|
||||
# notify all proxy instruments of the new meter so they can create
|
||||
# real instruments to back themselves
|
||||
for instrument in self._instruments:
|
||||
instrument.on_meter_set(real_meter)
|
||||
|
||||
def create_counter(
|
||||
self,
|
||||
name: str,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> Counter:
|
||||
with self._lock:
|
||||
if self._real_meter:
|
||||
return self._real_meter.create_counter(name, unit, description)
|
||||
proxy = _ProxyCounter(name, unit, description)
|
||||
self._instruments.append(proxy)
|
||||
return proxy
|
||||
|
||||
def create_up_down_counter(
|
||||
self,
|
||||
name: str,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> UpDownCounter:
|
||||
with self._lock:
|
||||
if self._real_meter:
|
||||
return self._real_meter.create_up_down_counter(
|
||||
name, unit, description
|
||||
)
|
||||
proxy = _ProxyUpDownCounter(name, unit, description)
|
||||
self._instruments.append(proxy)
|
||||
return proxy
|
||||
|
||||
def create_observable_counter(
|
||||
self,
|
||||
name: str,
|
||||
callbacks: Optional[Sequence[CallbackT]] = None,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> ObservableCounter:
|
||||
with self._lock:
|
||||
if self._real_meter:
|
||||
return self._real_meter.create_observable_counter(
|
||||
name, callbacks, unit, description
|
||||
)
|
||||
proxy = _ProxyObservableCounter(
|
||||
name, callbacks, unit=unit, description=description
|
||||
)
|
||||
self._instruments.append(proxy)
|
||||
return proxy
|
||||
|
||||
def create_histogram(
|
||||
self,
|
||||
name: str,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
*,
|
||||
explicit_bucket_boundaries_advisory: Optional[Sequence[float]] = None,
|
||||
) -> Histogram:
|
||||
with self._lock:
|
||||
if self._real_meter:
|
||||
return self._real_meter.create_histogram(
|
||||
name,
|
||||
unit,
|
||||
description,
|
||||
explicit_bucket_boundaries_advisory=explicit_bucket_boundaries_advisory,
|
||||
)
|
||||
proxy = _ProxyHistogram(
|
||||
name, unit, description, explicit_bucket_boundaries_advisory
|
||||
)
|
||||
self._instruments.append(proxy)
|
||||
return proxy
|
||||
|
||||
def create_gauge(
|
||||
self,
|
||||
name: str,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> Gauge:
|
||||
with self._lock:
|
||||
if self._real_meter:
|
||||
return self._real_meter.create_gauge(name, unit, description)
|
||||
proxy = _ProxyGauge(name, unit, description)
|
||||
self._instruments.append(proxy)
|
||||
return proxy
|
||||
|
||||
def create_observable_gauge(
|
||||
self,
|
||||
name: str,
|
||||
callbacks: Optional[Sequence[CallbackT]] = None,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> ObservableGauge:
|
||||
with self._lock:
|
||||
if self._real_meter:
|
||||
return self._real_meter.create_observable_gauge(
|
||||
name, callbacks, unit, description
|
||||
)
|
||||
proxy = _ProxyObservableGauge(
|
||||
name, callbacks, unit=unit, description=description
|
||||
)
|
||||
self._instruments.append(proxy)
|
||||
return proxy
|
||||
|
||||
def create_observable_up_down_counter(
|
||||
self,
|
||||
name: str,
|
||||
callbacks: Optional[Sequence[CallbackT]] = None,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> ObservableUpDownCounter:
|
||||
with self._lock:
|
||||
if self._real_meter:
|
||||
return self._real_meter.create_observable_up_down_counter(
|
||||
name,
|
||||
callbacks,
|
||||
unit,
|
||||
description,
|
||||
)
|
||||
proxy = _ProxyObservableUpDownCounter(
|
||||
name, callbacks, unit=unit, description=description
|
||||
)
|
||||
self._instruments.append(proxy)
|
||||
return proxy
|
||||
|
||||
|
||||
class NoOpMeter(Meter):
|
||||
"""The default Meter used when no Meter implementation is available.
|
||||
|
||||
All operations are no-op.
|
||||
"""
|
||||
|
||||
def create_counter(
|
||||
self,
|
||||
name: str,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> Counter:
|
||||
"""Returns a no-op Counter."""
|
||||
status = self._register_instrument(
|
||||
name, NoOpCounter, unit, description
|
||||
)
|
||||
if status.conflict:
|
||||
self._log_instrument_registration_conflict(
|
||||
name,
|
||||
Counter.__name__,
|
||||
unit,
|
||||
description,
|
||||
status,
|
||||
)
|
||||
|
||||
return NoOpCounter(name, unit=unit, description=description)
|
||||
|
||||
def create_gauge(
|
||||
self,
|
||||
name: str,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> Gauge:
|
||||
"""Returns a no-op Gauge."""
|
||||
status = self._register_instrument(name, NoOpGauge, unit, description)
|
||||
if status.conflict:
|
||||
self._log_instrument_registration_conflict(
|
||||
name,
|
||||
Gauge.__name__,
|
||||
unit,
|
||||
description,
|
||||
status,
|
||||
)
|
||||
return NoOpGauge(name, unit=unit, description=description)
|
||||
|
||||
def create_up_down_counter(
|
||||
self,
|
||||
name: str,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> UpDownCounter:
|
||||
"""Returns a no-op UpDownCounter."""
|
||||
status = self._register_instrument(
|
||||
name, NoOpUpDownCounter, unit, description
|
||||
)
|
||||
if status.conflict:
|
||||
self._log_instrument_registration_conflict(
|
||||
name,
|
||||
UpDownCounter.__name__,
|
||||
unit,
|
||||
description,
|
||||
status,
|
||||
)
|
||||
return NoOpUpDownCounter(name, unit=unit, description=description)
|
||||
|
||||
def create_observable_counter(
|
||||
self,
|
||||
name: str,
|
||||
callbacks: Optional[Sequence[CallbackT]] = None,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> ObservableCounter:
|
||||
"""Returns a no-op ObservableCounter."""
|
||||
status = self._register_instrument(
|
||||
name, NoOpObservableCounter, unit, description
|
||||
)
|
||||
if status.conflict:
|
||||
self._log_instrument_registration_conflict(
|
||||
name,
|
||||
ObservableCounter.__name__,
|
||||
unit,
|
||||
description,
|
||||
status,
|
||||
)
|
||||
return NoOpObservableCounter(
|
||||
name,
|
||||
callbacks,
|
||||
unit=unit,
|
||||
description=description,
|
||||
)
|
||||
|
||||
def create_histogram(
|
||||
self,
|
||||
name: str,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
*,
|
||||
explicit_bucket_boundaries_advisory: Optional[Sequence[float]] = None,
|
||||
) -> Histogram:
|
||||
"""Returns a no-op Histogram."""
|
||||
status = self._register_instrument(
|
||||
name,
|
||||
NoOpHistogram,
|
||||
unit,
|
||||
description,
|
||||
_MetricsHistogramAdvisory(
|
||||
explicit_bucket_boundaries=explicit_bucket_boundaries_advisory
|
||||
),
|
||||
)
|
||||
if status.conflict:
|
||||
self._log_instrument_registration_conflict(
|
||||
name,
|
||||
Histogram.__name__,
|
||||
unit,
|
||||
description,
|
||||
status,
|
||||
)
|
||||
return NoOpHistogram(
|
||||
name,
|
||||
unit=unit,
|
||||
description=description,
|
||||
explicit_bucket_boundaries_advisory=explicit_bucket_boundaries_advisory,
|
||||
)
|
||||
|
||||
def create_observable_gauge(
|
||||
self,
|
||||
name: str,
|
||||
callbacks: Optional[Sequence[CallbackT]] = None,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> ObservableGauge:
|
||||
"""Returns a no-op ObservableGauge."""
|
||||
status = self._register_instrument(
|
||||
name, NoOpObservableGauge, unit, description
|
||||
)
|
||||
if status.conflict:
|
||||
self._log_instrument_registration_conflict(
|
||||
name,
|
||||
ObservableGauge.__name__,
|
||||
unit,
|
||||
description,
|
||||
status,
|
||||
)
|
||||
return NoOpObservableGauge(
|
||||
name,
|
||||
callbacks,
|
||||
unit=unit,
|
||||
description=description,
|
||||
)
|
||||
|
||||
def create_observable_up_down_counter(
|
||||
self,
|
||||
name: str,
|
||||
callbacks: Optional[Sequence[CallbackT]] = None,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> ObservableUpDownCounter:
|
||||
"""Returns a no-op ObservableUpDownCounter."""
|
||||
status = self._register_instrument(
|
||||
name, NoOpObservableUpDownCounter, unit, description
|
||||
)
|
||||
if status.conflict:
|
||||
self._log_instrument_registration_conflict(
|
||||
name,
|
||||
ObservableUpDownCounter.__name__,
|
||||
unit,
|
||||
description,
|
||||
status,
|
||||
)
|
||||
return NoOpObservableUpDownCounter(
|
||||
name,
|
||||
callbacks,
|
||||
unit=unit,
|
||||
description=description,
|
||||
)
|
||||
|
||||
|
||||
_METER_PROVIDER_SET_ONCE = Once()
|
||||
_METER_PROVIDER: Optional[MeterProvider] = None
|
||||
_PROXY_METER_PROVIDER = _ProxyMeterProvider()
|
||||
|
||||
|
||||
def get_meter(
|
||||
name: str,
|
||||
version: str = "",
|
||||
meter_provider: Optional[MeterProvider] = None,
|
||||
schema_url: Optional[str] = None,
|
||||
attributes: Optional[Attributes] = None,
|
||||
) -> "Meter":
|
||||
"""Returns a `Meter` for use by the given instrumentation library.
|
||||
|
||||
This function is a convenience wrapper for
|
||||
`opentelemetry.metrics.MeterProvider.get_meter`.
|
||||
|
||||
If meter_provider is omitted the current configured one is used.
|
||||
"""
|
||||
if meter_provider is None:
|
||||
meter_provider = get_meter_provider()
|
||||
return meter_provider.get_meter(name, version, schema_url, attributes)
|
||||
|
||||
|
||||
def _set_meter_provider(meter_provider: MeterProvider, log: bool) -> None:
|
||||
def set_mp() -> None:
|
||||
global _METER_PROVIDER # pylint: disable=global-statement
|
||||
_METER_PROVIDER = meter_provider
|
||||
|
||||
# gives all proxies real instruments off the newly set meter provider
|
||||
_PROXY_METER_PROVIDER.on_set_meter_provider(meter_provider)
|
||||
|
||||
did_set = _METER_PROVIDER_SET_ONCE.do_once(set_mp)
|
||||
|
||||
if log and not did_set:
|
||||
_logger.warning("Overriding of current MeterProvider is not allowed")
|
||||
|
||||
|
||||
def set_meter_provider(meter_provider: MeterProvider) -> None:
|
||||
"""Sets the current global :class:`~.MeterProvider` object.
|
||||
|
||||
This can only be done once, a warning will be logged if any further attempt
|
||||
is made.
|
||||
"""
|
||||
_set_meter_provider(meter_provider, log=True)
|
||||
|
||||
|
||||
def get_meter_provider() -> MeterProvider:
|
||||
"""Gets the current global :class:`~.MeterProvider` object."""
|
||||
|
||||
if _METER_PROVIDER is None:
|
||||
if OTEL_PYTHON_METER_PROVIDER not in environ:
|
||||
return _PROXY_METER_PROVIDER
|
||||
|
||||
meter_provider: MeterProvider = _load_provider( # type: ignore
|
||||
OTEL_PYTHON_METER_PROVIDER, "meter_provider"
|
||||
)
|
||||
_set_meter_provider(meter_provider, log=False)
|
||||
|
||||
# _METER_PROVIDER will have been set by one thread
|
||||
return cast("MeterProvider", _METER_PROVIDER)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,530 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# pylint: disable=too-many-ancestors
|
||||
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from logging import getLogger
|
||||
from re import compile as re_compile
|
||||
from typing import (
|
||||
Callable,
|
||||
Dict,
|
||||
Generator,
|
||||
Generic,
|
||||
Iterable,
|
||||
Optional,
|
||||
Sequence,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
# pylint: disable=unused-import; needed for typing and sphinx
|
||||
from opentelemetry import metrics
|
||||
from opentelemetry.context import Context
|
||||
from opentelemetry.metrics._internal.observation import Observation
|
||||
from opentelemetry.util.types import (
|
||||
Attributes,
|
||||
)
|
||||
|
||||
_logger = getLogger(__name__)
|
||||
|
||||
_name_regex = re_compile(r"[a-zA-Z][-_./a-zA-Z0-9]{0,254}")
|
||||
_unit_regex = re_compile(r"[\x00-\x7F]{0,63}")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _MetricsHistogramAdvisory:
|
||||
explicit_bucket_boundaries: Optional[Sequence[float]] = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CallbackOptions:
|
||||
"""Options for the callback
|
||||
|
||||
Args:
|
||||
timeout_millis: Timeout for the callback's execution. If the callback does asynchronous
|
||||
work (e.g. HTTP requests), it should respect this timeout.
|
||||
"""
|
||||
|
||||
timeout_millis: float = 10_000
|
||||
|
||||
|
||||
InstrumentT = TypeVar("InstrumentT", bound="Instrument")
|
||||
# pylint: disable=invalid-name
|
||||
CallbackT = Union[
|
||||
Callable[[CallbackOptions], Iterable[Observation]],
|
||||
Generator[Iterable[Observation], CallbackOptions, None],
|
||||
]
|
||||
|
||||
|
||||
class Instrument(ABC):
|
||||
"""Abstract class that serves as base for all instruments."""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _check_name_unit_description(
|
||||
name: str, unit: str, description: str
|
||||
) -> Dict[str, Optional[str]]:
|
||||
"""
|
||||
Checks the following instrument name, unit and description for
|
||||
compliance with the spec.
|
||||
|
||||
Returns a dict with keys "name", "unit" and "description", the
|
||||
corresponding values will be the checked strings or `None` if the value
|
||||
is invalid. If valid, the checked strings should be used instead of the
|
||||
original values.
|
||||
"""
|
||||
|
||||
result: Dict[str, Optional[str]] = {}
|
||||
|
||||
if _name_regex.fullmatch(name) is not None:
|
||||
result["name"] = name
|
||||
else:
|
||||
result["name"] = None
|
||||
|
||||
if unit is None:
|
||||
unit = ""
|
||||
if _unit_regex.fullmatch(unit) is not None:
|
||||
result["unit"] = unit
|
||||
else:
|
||||
result["unit"] = None
|
||||
|
||||
if description is None:
|
||||
result["description"] = ""
|
||||
else:
|
||||
result["description"] = description
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class _ProxyInstrument(ABC, Generic[InstrumentT]):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> None:
|
||||
self._name = name
|
||||
self._unit = unit
|
||||
self._description = description
|
||||
self._real_instrument: Optional[InstrumentT] = None
|
||||
|
||||
def on_meter_set(self, meter: "metrics.Meter") -> None:
|
||||
"""Called when a real meter is set on the creating _ProxyMeter"""
|
||||
|
||||
# We don't need any locking on proxy instruments because it's OK if some
|
||||
# measurements get dropped while a real backing instrument is being
|
||||
# created.
|
||||
self._real_instrument = self._create_real_instrument(meter)
|
||||
|
||||
@abstractmethod
|
||||
def _create_real_instrument(self, meter: "metrics.Meter") -> InstrumentT:
|
||||
"""Create an instance of the real instrument. Implement this."""
|
||||
|
||||
|
||||
class _ProxyAsynchronousInstrument(_ProxyInstrument[InstrumentT]):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
callbacks: Optional[Sequence[CallbackT]] = None,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> None:
|
||||
super().__init__(name, unit, description)
|
||||
self._callbacks = callbacks
|
||||
|
||||
|
||||
class Synchronous(Instrument):
|
||||
"""Base class for all synchronous instruments"""
|
||||
|
||||
|
||||
class Asynchronous(Instrument):
|
||||
"""Base class for all asynchronous instruments"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
callbacks: Optional[Sequence[CallbackT]] = None,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> None:
|
||||
super().__init__(name, unit=unit, description=description)
|
||||
|
||||
|
||||
class Counter(Synchronous):
|
||||
"""A Counter is a synchronous `Instrument` which supports non-negative increments."""
|
||||
|
||||
@abstractmethod
|
||||
def add(
|
||||
self,
|
||||
amount: Union[int, float],
|
||||
attributes: Optional[Attributes] = None,
|
||||
context: Optional[Context] = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class NoOpCounter(Counter):
|
||||
"""No-op implementation of `Counter`."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> None:
|
||||
super().__init__(name, unit=unit, description=description)
|
||||
|
||||
def add(
|
||||
self,
|
||||
amount: Union[int, float],
|
||||
attributes: Optional[Attributes] = None,
|
||||
context: Optional[Context] = None,
|
||||
) -> None:
|
||||
return super().add(amount, attributes=attributes, context=context)
|
||||
|
||||
|
||||
class _ProxyCounter(_ProxyInstrument[Counter], Counter):
|
||||
def add(
|
||||
self,
|
||||
amount: Union[int, float],
|
||||
attributes: Optional[Attributes] = None,
|
||||
context: Optional[Context] = None,
|
||||
) -> None:
|
||||
if self._real_instrument:
|
||||
self._real_instrument.add(amount, attributes, context)
|
||||
|
||||
def _create_real_instrument(self, meter: "metrics.Meter") -> Counter:
|
||||
return meter.create_counter(
|
||||
self._name,
|
||||
self._unit,
|
||||
self._description,
|
||||
)
|
||||
|
||||
|
||||
class UpDownCounter(Synchronous):
|
||||
"""An UpDownCounter is a synchronous `Instrument` which supports increments and decrements."""
|
||||
|
||||
@abstractmethod
|
||||
def add(
|
||||
self,
|
||||
amount: Union[int, float],
|
||||
attributes: Optional[Attributes] = None,
|
||||
context: Optional[Context] = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class NoOpUpDownCounter(UpDownCounter):
|
||||
"""No-op implementation of `UpDownCounter`."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> None:
|
||||
super().__init__(name, unit=unit, description=description)
|
||||
|
||||
def add(
|
||||
self,
|
||||
amount: Union[int, float],
|
||||
attributes: Optional[Attributes] = None,
|
||||
context: Optional[Context] = None,
|
||||
) -> None:
|
||||
return super().add(amount, attributes=attributes, context=context)
|
||||
|
||||
|
||||
class _ProxyUpDownCounter(_ProxyInstrument[UpDownCounter], UpDownCounter):
|
||||
def add(
|
||||
self,
|
||||
amount: Union[int, float],
|
||||
attributes: Optional[Attributes] = None,
|
||||
context: Optional[Context] = None,
|
||||
) -> None:
|
||||
if self._real_instrument:
|
||||
self._real_instrument.add(amount, attributes, context)
|
||||
|
||||
def _create_real_instrument(self, meter: "metrics.Meter") -> UpDownCounter:
|
||||
return meter.create_up_down_counter(
|
||||
self._name,
|
||||
self._unit,
|
||||
self._description,
|
||||
)
|
||||
|
||||
|
||||
class ObservableCounter(Asynchronous):
|
||||
"""An ObservableCounter is an asynchronous `Instrument` which reports monotonically
|
||||
increasing value(s) when the instrument is being observed.
|
||||
"""
|
||||
|
||||
|
||||
class NoOpObservableCounter(ObservableCounter):
|
||||
"""No-op implementation of `ObservableCounter`."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
callbacks: Optional[Sequence[CallbackT]] = None,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> None:
|
||||
super().__init__(
|
||||
name,
|
||||
callbacks,
|
||||
unit=unit,
|
||||
description=description,
|
||||
)
|
||||
|
||||
|
||||
class _ProxyObservableCounter(
|
||||
_ProxyAsynchronousInstrument[ObservableCounter], ObservableCounter
|
||||
):
|
||||
def _create_real_instrument(
|
||||
self, meter: "metrics.Meter"
|
||||
) -> ObservableCounter:
|
||||
return meter.create_observable_counter(
|
||||
self._name,
|
||||
self._callbacks,
|
||||
self._unit,
|
||||
self._description,
|
||||
)
|
||||
|
||||
|
||||
class ObservableUpDownCounter(Asynchronous):
|
||||
"""An ObservableUpDownCounter is an asynchronous `Instrument` which reports additive value(s) (e.g.
|
||||
the process heap size - it makes sense to report the heap size from multiple processes and sum them
|
||||
up, so we get the total heap usage) when the instrument is being observed.
|
||||
"""
|
||||
|
||||
|
||||
class NoOpObservableUpDownCounter(ObservableUpDownCounter):
|
||||
"""No-op implementation of `ObservableUpDownCounter`."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
callbacks: Optional[Sequence[CallbackT]] = None,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> None:
|
||||
super().__init__(
|
||||
name,
|
||||
callbacks,
|
||||
unit=unit,
|
||||
description=description,
|
||||
)
|
||||
|
||||
|
||||
class _ProxyObservableUpDownCounter(
|
||||
_ProxyAsynchronousInstrument[ObservableUpDownCounter],
|
||||
ObservableUpDownCounter,
|
||||
):
|
||||
def _create_real_instrument(
|
||||
self, meter: "metrics.Meter"
|
||||
) -> ObservableUpDownCounter:
|
||||
return meter.create_observable_up_down_counter(
|
||||
self._name,
|
||||
self._callbacks,
|
||||
self._unit,
|
||||
self._description,
|
||||
)
|
||||
|
||||
|
||||
class Histogram(Synchronous):
|
||||
"""Histogram is a synchronous `Instrument` which can be used to report arbitrary values
|
||||
that are likely to be statistically meaningful. It is intended for statistics such as
|
||||
histograms, summaries, and percentile.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
explicit_bucket_boundaries_advisory: Optional[Sequence[float]] = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def record(
|
||||
self,
|
||||
amount: Union[int, float],
|
||||
attributes: Optional[Attributes] = None,
|
||||
context: Optional[Context] = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class NoOpHistogram(Histogram):
|
||||
"""No-op implementation of `Histogram`."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
explicit_bucket_boundaries_advisory: Optional[Sequence[float]] = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
name,
|
||||
unit=unit,
|
||||
description=description,
|
||||
explicit_bucket_boundaries_advisory=explicit_bucket_boundaries_advisory,
|
||||
)
|
||||
|
||||
def record(
|
||||
self,
|
||||
amount: Union[int, float],
|
||||
attributes: Optional[Attributes] = None,
|
||||
context: Optional[Context] = None,
|
||||
) -> None:
|
||||
return super().record(amount, attributes=attributes, context=context)
|
||||
|
||||
|
||||
class _ProxyHistogram(_ProxyInstrument[Histogram], Histogram):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
explicit_bucket_boundaries_advisory: Optional[Sequence[float]] = None,
|
||||
) -> None:
|
||||
super().__init__(name, unit=unit, description=description)
|
||||
self._explicit_bucket_boundaries_advisory = (
|
||||
explicit_bucket_boundaries_advisory
|
||||
)
|
||||
|
||||
def record(
|
||||
self,
|
||||
amount: Union[int, float],
|
||||
attributes: Optional[Attributes] = None,
|
||||
context: Optional[Context] = None,
|
||||
) -> None:
|
||||
if self._real_instrument:
|
||||
self._real_instrument.record(amount, attributes, context)
|
||||
|
||||
def _create_real_instrument(self, meter: "metrics.Meter") -> Histogram:
|
||||
return meter.create_histogram(
|
||||
self._name,
|
||||
self._unit,
|
||||
self._description,
|
||||
explicit_bucket_boundaries_advisory=self._explicit_bucket_boundaries_advisory,
|
||||
)
|
||||
|
||||
|
||||
class ObservableGauge(Asynchronous):
|
||||
"""Asynchronous Gauge is an asynchronous `Instrument` which reports non-additive value(s) (e.g.
|
||||
the room temperature - it makes no sense to report the temperature value from multiple rooms
|
||||
and sum them up) when the instrument is being observed.
|
||||
"""
|
||||
|
||||
|
||||
class NoOpObservableGauge(ObservableGauge):
|
||||
"""No-op implementation of `ObservableGauge`."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
callbacks: Optional[Sequence[CallbackT]] = None,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> None:
|
||||
super().__init__(
|
||||
name,
|
||||
callbacks,
|
||||
unit=unit,
|
||||
description=description,
|
||||
)
|
||||
|
||||
|
||||
class _ProxyObservableGauge(
|
||||
_ProxyAsynchronousInstrument[ObservableGauge],
|
||||
ObservableGauge,
|
||||
):
|
||||
def _create_real_instrument(
|
||||
self, meter: "metrics.Meter"
|
||||
) -> ObservableGauge:
|
||||
return meter.create_observable_gauge(
|
||||
self._name,
|
||||
self._callbacks,
|
||||
self._unit,
|
||||
self._description,
|
||||
)
|
||||
|
||||
|
||||
class Gauge(Synchronous):
|
||||
"""A Gauge is a synchronous `Instrument` which can be used to record non-additive values as they occur."""
|
||||
|
||||
@abstractmethod
|
||||
def set(
|
||||
self,
|
||||
amount: Union[int, float],
|
||||
attributes: Optional[Attributes] = None,
|
||||
context: Optional[Context] = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class NoOpGauge(Gauge):
|
||||
"""No-op implementation of ``Gauge``."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
unit: str = "",
|
||||
description: str = "",
|
||||
) -> None:
|
||||
super().__init__(name, unit=unit, description=description)
|
||||
|
||||
def set(
|
||||
self,
|
||||
amount: Union[int, float],
|
||||
attributes: Optional[Attributes] = None,
|
||||
context: Optional[Context] = None,
|
||||
) -> None:
|
||||
return super().set(amount, attributes=attributes, context=context)
|
||||
|
||||
|
||||
class _ProxyGauge(
|
||||
_ProxyInstrument[Gauge],
|
||||
Gauge,
|
||||
):
|
||||
def set(
|
||||
self,
|
||||
amount: Union[int, float],
|
||||
attributes: Optional[Attributes] = None,
|
||||
context: Optional[Context] = None,
|
||||
) -> None:
|
||||
if self._real_instrument:
|
||||
self._real_instrument.set(amount, attributes, context)
|
||||
|
||||
def _create_real_instrument(self, meter: "metrics.Meter") -> Gauge:
|
||||
return meter.create_gauge(
|
||||
self._name,
|
||||
self._unit,
|
||||
self._description,
|
||||
)
|
||||
@@ -0,0 +1,63 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from typing import Optional, Union
|
||||
|
||||
from opentelemetry.context import Context
|
||||
from opentelemetry.util.types import Attributes
|
||||
|
||||
|
||||
class Observation:
|
||||
"""A measurement observed in an asynchronous instrument
|
||||
|
||||
Return/yield instances of this class from asynchronous instrument callbacks.
|
||||
|
||||
Args:
|
||||
value: The float or int measured value
|
||||
attributes: The measurement's attributes
|
||||
context: The measurement's context
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
value: Union[int, float],
|
||||
attributes: Attributes = None,
|
||||
context: Optional[Context] = None,
|
||||
) -> None:
|
||||
self._value = value
|
||||
self._attributes = attributes
|
||||
self._context = context
|
||||
|
||||
@property
|
||||
def value(self) -> Union[float, int]:
|
||||
return self._value
|
||||
|
||||
@property
|
||||
def attributes(self) -> Attributes:
|
||||
return self._attributes
|
||||
|
||||
@property
|
||||
def context(self) -> Optional[Context]:
|
||||
return self._context
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return (
|
||||
isinstance(other, Observation)
|
||||
and self.value == other.value
|
||||
and self.attributes == other.attributes
|
||||
and self.context == other.context
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Observation(value={self.value}, attributes={self.attributes}, context={self.context})"
|
||||
Reference in New Issue
Block a user