diff --git a/opentelemetry-api/src/opentelemetry/metrics/__init__.py b/opentelemetry-api/src/opentelemetry/metrics/__init__.py deleted file mode 100644 index c353a0e0032..00000000000 --- a/opentelemetry-api/src/opentelemetry/metrics/__init__.py +++ /dev/null @@ -1,472 +0,0 @@ -# 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 -# type: ignore - -# FIXME enhance the documentation of this module -""" -This module provides abstract and concrete (but noop) classes that can be used -to generate metrics. -""" - - -from abc import ABC, abstractmethod -from logging import getLogger -from os import environ -from threading import Lock -from typing import List, Optional, cast - -from opentelemetry.environment_variables import OTEL_PYTHON_METER_PROVIDER -from opentelemetry.metrics.instrument import ( - Counter, - DefaultCounter, - DefaultHistogram, - DefaultObservableCounter, - DefaultObservableGauge, - DefaultObservableUpDownCounter, - DefaultUpDownCounter, - Histogram, - ObservableCounter, - ObservableGauge, - ObservableUpDownCounter, - UpDownCounter, - _ProxyCounter, - _ProxyHistogram, - _ProxyInstrument, - _ProxyObservableCounter, - _ProxyObservableGauge, - _ProxyObservableUpDownCounter, - _ProxyUpDownCounter, -) -from opentelemetry.util._once import Once -from opentelemetry.util._providers import _load_provider - -_logger = getLogger(__name__) - - -class MeterProvider(ABC): - @abstractmethod - def get_meter( - self, - name, - version=None, - schema_url=None, - ) -> "Meter": - if name is None or name == "": - _logger.warning("Invalid name: %s", name) - - -class _DefaultMeterProvider(MeterProvider): - def get_meter( - self, - name, - version=None, - schema_url=None, - ) -> "Meter": - super().get_meter(name, version=version, schema_url=schema_url) - return _DefaultMeter(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, - version=None, - schema_url=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) - - -class Meter(ABC): - def __init__(self, name, version=None, schema_url=None): - super().__init__() - self._name = name - self._version = version - self._schema_url = schema_url - self._instrument_names = set() - - @property - def name(self): - return self._name - - @property - def version(self): - return self._version - - @property - def schema_url(self): - return self._schema_url - - def _secure_instrument_name(self, name): - name = name.lower() - - if name in self._instrument_names: - _logger.error("Instrument name %s has been used already", name) - - else: - self._instrument_names.add(name) - - @abstractmethod - def create_counter(self, name, unit="", description="") -> Counter: - self._secure_instrument_name(name) - - @abstractmethod - def create_up_down_counter( - self, name, unit="", description="" - ) -> UpDownCounter: - self._secure_instrument_name(name) - - @abstractmethod - def create_observable_counter( - self, name, callback, unit="", description="" - ) -> ObservableCounter: - """Creates an observable counter instrument - - An observable counter observes a monotonically increasing count by - calling a provided callback which returns multiple - :class:`~opentelemetry.metrics.measurement.Measurement`. - - For example, an observable counter could be used to report system CPU - time periodically. Here is a basic implementation:: - - def cpu_time_callback() -> Iterable[Measurement]: - measurements = [] - 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() - measurements.append(Measurement(int(states[0]) // 100, {"cpu": cpu, "state": "user"})) - measurements.append(Measurement(int(states[1]) // 100, {"cpu": cpu, "state": "nice"})) - measurements.append(Measurement(int(states[2]) // 100, {"cpu": cpu, "state": "system"})) - # ... other states - return measurements - - meter.create_observable_counter( - "system.cpu.time", - callback=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() -> Iterable[Measurement]: - 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 Measurement(int(states[0]) // 100, {"cpu": cpu, "state": "user"}) - yield Measurement(int(states[1]) // 100, {"cpu": cpu, "state": "nice"}) - # ... other states - - Alternatively, you can pass a generator directly instead of a callback, - which should return iterables of - :class:`~opentelemetry.metrics.measurement.Measurement`:: - - def cpu_time_callback(states_to_include: set[str]) -> Iterable[Iterable[Measurement]]: - while True: - measurements = [] - 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: - measurements.append(Measurement(int(states[0]) // 100, {"cpu": cpu, "state": "user"})) - if "nice" in states_to_include: - measurements.append(Measurement(int(states[1]) // 100, {"cpu": cpu, "state": "nice"})) - # ... other states - yield measurements - - meter.create_observable_counter( - "system.cpu.time", - callback=cpu_time_callback({"user", "system"}), - unit="s", - description="CPU time" - ) - - Args: - name: The name of the instrument to be created - callback: A callback that returns an iterable of - :class:`~opentelemetry.metrics.measurement.Measurement`. - Alternatively, can be a generator that yields iterables of - :class:`~opentelemetry.metrics.measurement.Measurement`. - unit: The unit for measurements this instrument reports. For - example, ``By`` for bytes. UCUM units are recommended. - description: A description for this instrument and what it measures. - """ - - self._secure_instrument_name(name) - - @abstractmethod - def create_histogram(self, name, unit="", description="") -> Histogram: - self._secure_instrument_name(name) - - @abstractmethod - def create_observable_gauge( - self, name, callback, unit="", description="" - ) -> ObservableGauge: - self._secure_instrument_name(name) - - @abstractmethod - def create_observable_up_down_counter( - self, name, callback, unit="", description="" - ) -> ObservableUpDownCounter: - self._secure_instrument_name(name) - - -class _ProxyMeter(Meter): - def __init__( - self, - name, - version=None, - schema_url=None, - ): - super().__init__(name, version=version, schema_url=schema_url) - self._lock = Lock() - self._instruments: List[_ProxyInstrument] = [] - 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, unit="", description="") -> 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, unit="", description="" - ) -> 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, callback, unit="", description="" - ) -> ObservableCounter: - with self._lock: - if self._real_meter: - return self._real_meter.create_observable_counter( - name, callback, unit, description - ) - proxy = _ProxyObservableCounter( - name, callback, unit=unit, description=description - ) - self._instruments.append(proxy) - return proxy - - def create_histogram(self, name, unit="", description="") -> Histogram: - with self._lock: - if self._real_meter: - return self._real_meter.create_histogram( - name, unit, description - ) - proxy = _ProxyHistogram(name, unit, description) - self._instruments.append(proxy) - return proxy - - def create_observable_gauge( - self, name, callback, unit="", description="" - ) -> ObservableGauge: - with self._lock: - if self._real_meter: - return self._real_meter.create_observable_gauge( - name, callback, unit, description - ) - proxy = _ProxyObservableGauge( - name, callback, unit=unit, description=description - ) - self._instruments.append(proxy) - return proxy - - def create_observable_up_down_counter( - self, name, callback, unit="", description="" - ) -> ObservableUpDownCounter: - with self._lock: - if self._real_meter: - return self._real_meter.create_observable_up_down_counter( - name, - callback, - unit, - description, - ) - proxy = _ProxyObservableUpDownCounter( - name, callback, unit=unit, description=description - ) - self._instruments.append(proxy) - return proxy - - -class _DefaultMeter(Meter): - def create_counter(self, name, unit="", description="") -> Counter: - super().create_counter(name, unit=unit, description=description) - return DefaultCounter(name, unit=unit, description=description) - - def create_up_down_counter( - self, name, unit="", description="" - ) -> UpDownCounter: - super().create_up_down_counter( - name, unit=unit, description=description - ) - return DefaultUpDownCounter(name, unit=unit, description=description) - - def create_observable_counter( - self, name, callback, unit="", description="" - ) -> ObservableCounter: - super().create_observable_counter( - name, callback, unit=unit, description=description - ) - return DefaultObservableCounter( - name, - callback, - unit=unit, - description=description, - ) - - def create_histogram(self, name, unit="", description="") -> Histogram: - super().create_histogram(name, unit=unit, description=description) - return DefaultHistogram(name, unit=unit, description=description) - - def create_observable_gauge( - self, name, callback, unit="", description="" - ) -> ObservableGauge: - super().create_observable_gauge( - name, callback, unit=unit, description=description - ) - return DefaultObservableGauge( - name, - callback, - unit=unit, - description=description, - ) - - def create_observable_up_down_counter( - self, name, callback, unit="", description="" - ) -> ObservableUpDownCounter: - super().create_observable_up_down_counter( - name, callback, unit=unit, description=description - ) - return DefaultObservableUpDownCounter( - name, - callback, - 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, -) -> "Meter": - """Returns a `Meter` for use by the given instrumentation library. - - This function is a convenience wrapper for - opentelemetry.trace.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) - - -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 furter 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.keys(): - return _PROXY_METER_PROVIDER - - meter_provider: MeterProvider = _load_provider( - 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) diff --git a/opentelemetry-api/src/opentelemetry/metrics/instrument.py b/opentelemetry-api/src/opentelemetry/metrics/instrument.py deleted file mode 100644 index 7ff4de4b6eb..00000000000 --- a/opentelemetry-api/src/opentelemetry/metrics/instrument.py +++ /dev/null @@ -1,326 +0,0 @@ -# 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 - -# type: ignore - -from abc import ABC, abstractmethod -from collections import abc as collections_abc -from logging import getLogger -from re import compile as compile_ -from typing import Callable, Generator, Iterable, Union - -from opentelemetry.metrics.measurement import Measurement - -_TInstrumentCallback = Callable[[], Iterable[Measurement]] -_TInstrumentCallbackGenerator = Generator[Iterable[Measurement], None, None] -TCallback = Union[_TInstrumentCallback, _TInstrumentCallbackGenerator] -InstrumentT = TypeVar("InstrumentT", bound="Instrument") - - -_logger = getLogger(__name__) - - -class Instrument(ABC): - - _name_regex = compile_(r"[a-zA-Z][-.\w]{0,62}") - - @property - def name(self): - return self._name - - @property - def unit(self): - return self._unit - - @property - def description(self): - return self._description - - @abstractmethod - def __init__(self, name, unit="", description=""): - - if name is None or self._name_regex.fullmatch(name) is None: - _logger.error("Invalid instrument name %s", name) - - else: - self._name = name - - if unit is None: - self._unit = "" - elif len(unit) > 63: - _logger.error("unit must be 63 characters or shorter") - - elif any(ord(character) > 127 for character in unit): - _logger.error("unit must only contain ASCII characters") - else: - self._unit = unit - - if description is None: - description = "" - - self._description = description - - -class _ProxyInstrument(ABC, Generic[InstrumentT]): - def __init__(self, name, unit, description) -> 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, callback, unit, description) -> None: - super().__init__(name, unit, description) - self._callback = callback - - -class Synchronous(Instrument): - pass - - -class Asynchronous(Instrument): - @abstractmethod - def __init__( - self, - name, - callback: TCallback, - *args, - unit="", - description="", - **kwargs - ): - super().__init__( - name, *args, unit=unit, description=description, **kwargs - ) - - if isinstance(callback, collections_abc.Callable): - self._callback = callback - elif isinstance(callback, collections_abc.Generator): - self._callback = self._wrap_generator_callback(callback) - else: - _logger.error("callback must be a callable or generator") - - def _wrap_generator_callback( - self, - generator_callback: _TInstrumentCallbackGenerator, - ) -> _TInstrumentCallback: - """Wraps a generator style callback into a callable one""" - has_items = True - - def inner() -> Iterable[Measurement]: - nonlocal has_items - if not has_items: - return [] - - try: - return next(generator_callback) - except StopIteration: - has_items = False - _logger.error( - "callback generator for instrument %s ran out of measurements", - self._name, - ) - return [] - - return inner - - def callback(self): - measurements = self._callback() - if not isinstance(measurements, collections_abc.Iterable): - _logger.error( - "Callback must return an iterable of Measurement, got %s", - type(measurements), - ) - return - for measurement in measurements: - if not isinstance(measurement, Measurement): - _logger.error( - "Callback must return an iterable of Measurement, " - "iterable contained type %s", - type(measurement), - ) - yield measurement - - -class _Adding(Instrument): - pass - - -class _Grouping(Instrument): - pass - - -class _Monotonic(_Adding): - pass - - -class _NonMonotonic(_Adding): - pass - - -class Counter(_Monotonic, Synchronous): - @abstractmethod - def add(self, amount, attributes=None): - if amount < 0: - _logger.error("Amount must be non-negative") - - -class DefaultCounter(Counter): - def __init__(self, name, unit="", description=""): - super().__init__(name, unit=unit, description=description) - - def add(self, amount, attributes=None): - return super().add(amount, attributes=attributes) - - -class _ProxyCounter(_ProxyInstrument[Counter], Counter): - def add(self, amount, attributes=None): - if self._real_instrument: - self._real_instrument.add(amount, attributes) - - def _create_real_instrument(self, meter: "metrics.Meter") -> Counter: - return meter.create_counter(self._name, self._unit, self._description) - - -class UpDownCounter(_NonMonotonic, Synchronous): - @abstractmethod - def add(self, amount, attributes=None): - pass - - -class DefaultUpDownCounter(UpDownCounter): - def __init__(self, name, unit="", description=""): - super().__init__(name, unit=unit, description=description) - - def add(self, amount, attributes=None): - return super().add(amount, attributes=attributes) - - -class _ProxyUpDownCounter(_ProxyInstrument[UpDownCounter], UpDownCounter): - def add(self, amount, attributes=None): - if self._real_instrument: - self._real_instrument.add(amount, attributes) - - def _create_real_instrument(self, meter: "metrics.Meter") -> UpDownCounter: - return meter.create_up_down_counter( - self._name, self._unit, self._description - ) - - -class ObservableCounter(_Monotonic, Asynchronous): - def callback(self): - measurements = super().callback() - - for measurement in measurements: - if measurement.value < 0: - _logger.error("Amount must be non-negative") - yield measurement - - -class DefaultObservableCounter(ObservableCounter): - def __init__(self, name, callback, unit="", description=""): - super().__init__(name, callback, 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._callback, self._unit, self._description - ) - - -class ObservableUpDownCounter(_NonMonotonic, Asynchronous): - pass - - -class DefaultObservableUpDownCounter(ObservableUpDownCounter): - def __init__(self, name, callback, unit="", description=""): - super().__init__(name, callback, 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._callback, self._unit, self._description - ) - - -class Histogram(_Grouping, Synchronous): - @abstractmethod - def record(self, amount, attributes=None): - pass - - -class DefaultHistogram(Histogram): - def __init__(self, name, unit="", description=""): - super().__init__(name, unit=unit, description=description) - - def record(self, amount, attributes=None): - return super().record(amount, attributes=attributes) - - -class _ProxyHistogram(_ProxyInstrument[Histogram], Histogram): - def record(self, amount, attributes=None): - if self._real_instrument: - self._real_instrument.record(amount, attributes) - - def _create_real_instrument(self, meter: "metrics.Meter") -> Histogram: - return meter.create_histogram( - self._name, self._unit, self._description - ) - - -class ObservableGauge(_Grouping, Asynchronous): - pass - - -class DefaultObservableGauge(ObservableGauge): - def __init__(self, name, callback, unit="", description=""): - super().__init__(name, callback, 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._callback, self._unit, self._description - ) diff --git a/opentelemetry-api/src/opentelemetry/metrics/measurement.py b/opentelemetry-api/src/opentelemetry/metrics/measurement.py deleted file mode 100644 index e8fd9725bb8..00000000000 --- a/opentelemetry-api/src/opentelemetry/metrics/measurement.py +++ /dev/null @@ -1,52 +0,0 @@ -# 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 Union - -from opentelemetry.util.types import Attributes - - -class Measurement: - """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 - """ - - def __init__( - self, value: Union[int, float], attributes: Attributes = None - ) -> None: - self._value = value - self._attributes = attributes - - @property - def value(self) -> Union[float, int]: - return self._value - - @property - def attributes(self) -> Attributes: - return self._attributes - - def __eq__(self, other: object) -> bool: - return ( - isinstance(other, Measurement) - and self.value == other.value - and self.attributes == other.attributes - ) - - def __repr__(self) -> str: - return f"Measurement(value={self.value}, attributes={self.attributes})" diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/aggregation.py b/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/aggregation.py similarity index 100% rename from opentelemetry-sdk/src/opentelemetry/sdk/metrics/aggregation.py rename to opentelemetry-sdk/src/opentelemetry/sdk/_metrics/aggregation.py diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/instrument.py b/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/instrument.py similarity index 100% rename from opentelemetry-sdk/src/opentelemetry/sdk/metrics/instrument.py rename to opentelemetry-sdk/src/opentelemetry/sdk/_metrics/instrument.py diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/__init__.py deleted file mode 100644 index 384e52512a9..00000000000 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/__init__.py +++ /dev/null @@ -1,216 +0,0 @@ -# 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=function-redefined,too-many-ancestors - -from abc import ABC, abstractmethod -from atexit import register, unregister -from logging import getLogger -from typing import Optional - -from opentelemetry.metrics import Meter as APIMeter -from opentelemetry.metrics import MeterProvider as APIMeterProvider -from opentelemetry.metrics import _DefaultMeter -from opentelemetry.metrics.instrument import ( - Counter, - Histogram, - ObservableCounter, - ObservableGauge, - ObservableUpDownCounter, - UpDownCounter, -) -from opentelemetry.sdk.resources import Resource -from opentelemetry.sdk.util.instrumentation import InstrumentationInfo - -_logger = getLogger(__name__) - - -class Meter(APIMeter): - def __init__( - self, - instrumentation_info: InstrumentationInfo, - meter_provider: APIMeterProvider, - ): - super().__init__(instrumentation_info) - self._instrumentation_info = instrumentation_info - self._meter_provider = meter_provider - - def create_counter(self, name, unit=None, description=None) -> Counter: - # FIXME implement this method - pass - - def create_up_down_counter( - self, name, unit=None, description=None - ) -> UpDownCounter: - # FIXME implement this method - pass - - def create_observable_counter( - self, name, callback, unit=None, description=None - ) -> ObservableCounter: - # FIXME implement this method - pass - - def create_histogram(self, name, unit=None, description=None) -> Histogram: - # FIXME implement this method - pass - - def create_observable_gauge( - self, name, callback, unit=None, description=None - ) -> ObservableGauge: - # FIXME implement this method - pass - - def create_observable_up_down_counter( - self, name, callback, unit=None, description=None - ) -> ObservableUpDownCounter: - # FIXME implement this method - pass - - -class MeterProvider(APIMeterProvider): - """See `opentelemetry.metrics.MeterProvider`.""" - - def __init__( - self, - resource: Resource = Resource.create({}), - shutdown_on_exit: bool = True, - ): - self._resource = resource - self._atexit_handler = None - - if shutdown_on_exit: - self._atexit_handler = register(self.shutdown) - - self._metric_readers = [] - self._metric_exporters = [] - self._views = [] - self._shutdown = False - - def get_meter( - self, - name: str, - version: Optional[str] = None, - schema_url: Optional[str] = None, - ) -> Meter: - - if self._shutdown: - _logger.warning( - "A shutdown `MeterProvider` can not provide a `Meter`" - ) - return _DefaultMeter(name, version=version, schema_url=schema_url) - - return Meter(InstrumentationInfo(name, version, schema_url), self) - - def shutdown(self): - # FIXME implement a timeout - - if self._shutdown: - _logger.warning("shutdown can only be called once") - return False - - result = True - - for metric_reader in self._metric_readers: - result = result and metric_reader.shutdown() - - for metric_exporter in self._metric_exporters: - result = result and metric_exporter.shutdown() - - self._shutdown = True - - if self._atexit_handler is not None: - unregister(self._atexit_handler) - self._atexit_handler = None - - return result - - def force_flush(self) -> bool: - - # FIXME implement a timeout - - metric_reader_result = True - metric_exporter_result = True - - for metric_reader in self._metric_readers: - metric_reader_result = ( - metric_reader_result and metric_reader.force_flush() - ) - - if not metric_reader_result: - _logger.warning("Unable to force flush all metric readers") - - for metric_exporter in self._metric_exporters: - metric_exporter_result = ( - metric_exporter_result and metric_exporter.force_flush() - ) - - if not metric_exporter_result: - _logger.warning("Unable to force flush all metric exporters") - - return metric_reader_result and metric_exporter_result - - def register_metric_reader(self, metric_reader: "MetricReader") -> None: - # FIXME protect this method against race conditions - self._metric_readers.append(metric_reader) - - def register_metric_exporter( - self, metric_exporter: "MetricExporter" - ) -> None: - # FIXME protect this method against race conditions - self._metric_exporters.append(metric_exporter) - - def register_view(self, view: "View") -> None: - # FIXME protect this method against race conditions - self._views.append(view) - - -class MetricReader(ABC): - def __init__(self): - self._shutdown = False - - @abstractmethod - def collect(self): - pass - - def shutdown(self): - # FIXME this will need a Once wrapper - self._shutdown = True - - -class MetricExporter(ABC): - def __init__(self): - self._shutdown = False - - @abstractmethod - def export(self): - pass - - def shutdown(self): - # FIXME this will need a Once wrapper - self._shutdown = True - - -class View: - pass - - -class ConsoleMetricExporter(MetricExporter): - def export(self): - pass - - -class SDKMetricReader(MetricReader): - def collect(self): - pass