From 2972ebc0d1ee54b53af1af847d5c029ac44422c9 Mon Sep 17 00:00:00 2001 From: Aaron Abbott Date: Fri, 29 Apr 2022 00:02:36 -0400 Subject: [PATCH] Move Metrics API behind internal package (#2651) * Move __init__.py file * Move Metrics API behind internal package --- .github/workflows/test.yml | 2 +- CHANGELOG.md | 4 +- docs/api/metrics.instrument.rst | 8 - docs/api/metrics.observation.rst | 7 - docs/api/metrics.rst | 5 - docs/conf.py | 22 +- docs/examples/metrics/example.py | 7 +- docs/getting_started/metrics_example.py | 7 +- .../src/opentelemetry/_metrics/__init__.py | 776 ++---------------- .../_metrics/_internal/__init__.py | 760 +++++++++++++++++ .../_metrics/{ => _internal}/instrument.py | 30 +- .../_metrics/{ => _internal}/observation.py | 0 .../tests/metrics/test_instruments.py | 5 +- .../tests/metrics/test_meter_provider.py | 21 +- ...est_measurement.py => test_observation.py} | 2 +- .../opentelemetry/sdk/_metrics/__init__.py | 16 +- .../opentelemetry/sdk/_metrics/aggregation.py | 7 +- .../opentelemetry/sdk/_metrics/instrument.py | 18 +- .../sdk/_metrics/metric_reader_storage.py | 2 +- .../src/opentelemetry/sdk/_metrics/view.py | 2 +- .../metrics/integration_test/test_cpu_time.py | 3 +- .../metrics/test_in_memory_metric_reader.py | 2 +- .../tests/metrics/test_instrument.py | 2 +- .../src/opentelemetry/test/globals_test.py | 5 +- 24 files changed, 888 insertions(+), 825 deletions(-) delete mode 100644 docs/api/metrics.instrument.rst delete mode 100644 docs/api/metrics.observation.rst create mode 100644 opentelemetry-api/src/opentelemetry/_metrics/_internal/__init__.py rename opentelemetry-api/src/opentelemetry/_metrics/{ => _internal}/instrument.py (95%) rename opentelemetry-api/src/opentelemetry/_metrics/{ => _internal}/observation.py (100%) rename opentelemetry-api/tests/metrics/{test_measurement.py => test_observation.py} (96%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2c3fea117d9..2115e71f9f1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,7 @@ env: # Otherwise, set variable to the commit of your branch on # opentelemetry-python-contrib which is compatible with these Core repo # changes. - CONTRIB_REPO_SHA: 4cfca481f8e2c8af5f7cfd032997fac692995f67 + CONTRIB_REPO_SHA: 008cd2370dcd3e87cca8c0ddbb0b820681fd7346 # This is needed because we do not clone the core repo in contrib builds anymore. # When running contrib builds as part of core builds, we use actions/checkout@v2 which # does not set an environment variable (simply just runs tox), which is different when diff --git a/CHANGELOG.md b/CHANGELOG.md index 36e49f5140a..d43389891c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/open-telemetry/opentelemetry-python/compare/v1.11.1-0.30b1...HEAD) -## [1.11.1-0.30b1](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.11.1-0.30b1) - 2022-04-21 +- Move Metrics API behind internal package + ([#2651](https://github.com/open-telemetry/opentelemetry-python/pull/2651)) +## [1.11.1-0.30b1](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.11.1-0.30b1) - 2022-04-21 - Add parameter to MetricReader constructor to select aggregation per instrument kind ([#2638](https://github.com/open-telemetry/opentelemetry-python/pull/2638)) diff --git a/docs/api/metrics.instrument.rst b/docs/api/metrics.instrument.rst deleted file mode 100644 index 7d23f6fa773..00000000000 --- a/docs/api/metrics.instrument.rst +++ /dev/null @@ -1,8 +0,0 @@ -opentelemetry._metrics.instrument -================================= - -.. automodule:: opentelemetry._metrics.instrument - :members: - :private-members: - :undoc-members: - :no-show-inheritance: diff --git a/docs/api/metrics.observation.rst b/docs/api/metrics.observation.rst deleted file mode 100644 index 3df89ae6a5e..00000000000 --- a/docs/api/metrics.observation.rst +++ /dev/null @@ -1,7 +0,0 @@ -opentelemetry._metrics.observation -================================== - -.. automodule:: opentelemetry._metrics.observation - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/api/metrics.rst b/docs/api/metrics.rst index 08c4cbcc708..4fd464ae1d7 100644 --- a/docs/api/metrics.rst +++ b/docs/api/metrics.rst @@ -8,13 +8,8 @@ opentelemetry._metrics package Once metrics become stable, this package will be be renamed to ``opentelemetry.metrics``. -Submodules ----------- - .. toctree:: - metrics.instrument - metrics.observation Module contents --------------- diff --git a/docs/conf.py b/docs/conf.py index d250de2b530..03fad0837f0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -97,9 +97,6 @@ # https://github.com/sphinx-doc/sphinx/pull/3744 nitpick_ignore = [ ("py:class", "ValueT"), - ("py:class", "MetricT"), - ("py:class", "InstrumentT"), - ("py:obj", "opentelemetry._metrics.instrument.InstrumentT"), # Even if wrapt is added to intersphinx_mapping, sphinx keeps failing # with "class reference target not found: ObjectProxy". ("py:class", "ObjectProxy"), @@ -142,24 +139,7 @@ "examples/error_handler/error_handler_1", ] -_exclude_members = [ - "_ProxyObservableUpDownCounter", - "_ProxyHistogram", - "_ProxyObservableGauge", - "_ProxyInstrument", - "_ProxyAsynchronousInstrument", - "_ProxyCounter", - "_ProxyUpDownCounter", - "_ProxyObservableCounter", - "_ProxyObservableGauge", - "_abc_impl", - "_Adding", - "_Grouping", - "_Monotonic", - "_NonMonotonic", - "Synchronous", - "Asynchronous", -] +_exclude_members = ["_abc_impl"] autodoc_default_options = { "members": True, diff --git a/docs/examples/metrics/example.py b/docs/examples/metrics/example.py index 0c4db25cf0e..11c2b8d8e04 100644 --- a/docs/examples/metrics/example.py +++ b/docs/examples/metrics/example.py @@ -1,7 +1,10 @@ from typing import Iterable -from opentelemetry._metrics import get_meter_provider, set_meter_provider -from opentelemetry._metrics.observation import Observation +from opentelemetry._metrics import ( + Observation, + get_meter_provider, + set_meter_provider, +) from opentelemetry.exporter.otlp.proto.grpc._metric_exporter import ( OTLPMetricExporter, ) diff --git a/docs/getting_started/metrics_example.py b/docs/getting_started/metrics_example.py index 2a7199b99c5..a1e1b7ff073 100644 --- a/docs/getting_started/metrics_example.py +++ b/docs/getting_started/metrics_example.py @@ -17,8 +17,11 @@ from typing import Iterable -from opentelemetry._metrics import get_meter_provider, set_meter_provider -from opentelemetry._metrics.observation import Observation +from opentelemetry._metrics import ( + Observation, + get_meter_provider, + set_meter_provider, +) from opentelemetry.sdk._metrics import MeterProvider from opentelemetry.sdk._metrics.export import ( ConsoleMetricExporter, diff --git a/opentelemetry-api/src/opentelemetry/_metrics/__init__.py b/opentelemetry-api/src/opentelemetry/_metrics/__init__.py index 6447135743b..eac37b2cfe3 100644 --- a/opentelemetry-api/src/opentelemetry/_metrics/__init__.py +++ b/opentelemetry-api/src/opentelemetry/_metrics/__init__.py @@ -12,8 +12,6 @@ # 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. @@ -40,17 +38,21 @@ .. versionadded:: 1.10.0 """ - -from abc import ABC, abstractmethod -from logging import getLogger -from os import environ -from threading import Lock -from typing import List, Optional, Sequence, Set, Tuple, Union, cast - -from opentelemetry._metrics.instrument import ( +from opentelemetry._metrics._internal import ( + Meter, + MeterProvider, + NoOpMeter, + NoOpMeterProvider, + get_meter, + get_meter_provider, + set_meter_provider, +) +from opentelemetry._metrics._internal.instrument import ( + Asynchronous, CallbackT, Counter, Histogram, + Instrument, NoOpCounter, NoOpHistogram, NoOpObservableCounter, @@ -60,707 +62,61 @@ ObservableCounter, ObservableGauge, ObservableUpDownCounter, + Synchronous, UpDownCounter, - _ProxyCounter, - _ProxyHistogram, - _ProxyObservableCounter, - _ProxyObservableGauge, - _ProxyObservableUpDownCounter, - _ProxyUpDownCounter, ) -from opentelemetry.environment_variables import ( - _OTEL_PYTHON_METER_PROVIDER as OTEL_PYTHON_METER_PROVIDER, -) -from opentelemetry.util._once import Once -from opentelemetry.util._providers import _load_provider - -_logger = getLogger(__name__) - +from opentelemetry._metrics._internal.observation import Observation -ProxyInstrumentT = Union[ - _ProxyCounter, - _ProxyHistogram, - _ProxyObservableCounter, - _ProxyObservableGauge, - _ProxyObservableUpDownCounter, - _ProxyUpDownCounter, +for obj in [ + Counter, + Synchronous, + Asynchronous, + 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__ = [ + "MeterProvider", + "NoOpMeterProvider", + "Meter", + "Counter", + "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", ] - - -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, - ) -> "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 - ``pkg_resources.get_distribution(instrumenting_library_name).version``. - - schema_url: Optional. Specifies the Schema URL of 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, - ) -> "Meter": - """Returns a NoOpMeter.""" - super().get_meter(name, version=version, schema_url=schema_url) - 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, - ) -> "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): - """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: Set[str] = set() - 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 _is_instrument_registered( - self, name: str, type_: type, unit: str, description: str - ) -> Tuple[bool, str]: - """ - Check if an instrument with the same name, type, unit and description - has been registered already. - - Returns a tuple. The first value is `True` if the instrument has been - registered already, `False` otherwise. The second value is the - instrument id. - """ - - instrument_id = ",".join( - [name.strip().lower(), type_.__name__, unit, description] - ) - - result = False - - with self._instrument_ids_lock: - if instrument_id in self._instrument_ids: - result = True - else: - self._instrument_ids.add(instrument_id) - - return (result, instrument_id) - - @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 returns multiple - :class:`~opentelemetry._metrics.observation.Observation`. - - For example, an observable counter could be used to report system CPU - time periodically. Here is a basic implementation:: - - def cpu_time_callback() -> 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() -> 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.Observation`:: - - def cpu_time_callback(states_to_include: set[str]) -> Iterable[Iterable[Observation]]: - 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 observations - - meter.create_observable_counter( - "system.cpu.time", - callbacks=[cpu_time_callback({"user", "system"})], - unit="s", - description="CPU time" - ) - - Args: - name: The name of the instrument to be created - callbacks: A sequence of callbacks that return an iterable of - :class:`~opentelemetry._metrics.observation.Observation`. - Alternatively, can be a sequence of generators that each yields - iterables of - :class:`~opentelemetry._metrics.observation.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 = "", - ) -> Histogram: - """Creates a `opentelemetry._metrics.instrument.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. - """ - - @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.Observation`. - Alternatively, can be a generator that yields iterables of - :class:`~opentelemetry._metrics.observation.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.Observation`. - Alternatively, can be a generator that yields iterables of - :class:`~opentelemetry._metrics.observation.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 = "", - ) -> 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: 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.""" - super().create_counter(name, unit=unit, description=description) - if self._is_instrument_registered( - name, NoOpCounter, unit, description - )[0]: - _logger.warning( - "An instrument with name %s, type %s, unit %s and " - "description %s has been created already.", - name, - Counter.__name__, - unit, - description, - ) - return NoOpCounter(name, unit=unit, description=description) - - def create_up_down_counter( - self, - name: str, - unit: str = "", - description: str = "", - ) -> UpDownCounter: - """Returns a no-op UpDownCounter.""" - super().create_up_down_counter( - name, unit=unit, description=description - ) - if self._is_instrument_registered( - name, NoOpUpDownCounter, unit, description - )[0]: - _logger.warning( - "An instrument with name %s, type %s, unit %s and " - "description %s has been created already.", - name, - UpDownCounter.__name__, - unit, - description, - ) - 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.""" - super().create_observable_counter( - name, callbacks, unit=unit, description=description - ) - if self._is_instrument_registered( - name, NoOpObservableCounter, unit, description - )[0]: - _logger.warning( - "An instrument with name %s, type %s, unit %s and " - "description %s has been created already.", - name, - ObservableCounter.__name__, - unit, - description, - ) - return NoOpObservableCounter( - name, - callbacks, - unit=unit, - description=description, - ) - - def create_histogram( - self, - name: str, - unit: str = "", - description: str = "", - ) -> Histogram: - """Returns a no-op Histogram.""" - super().create_histogram(name, unit=unit, description=description) - if self._is_instrument_registered( - name, NoOpHistogram, unit, description - )[0]: - _logger.warning( - "An instrument with name %s, type %s, unit %s and " - "description %s has been created already.", - name, - Histogram.__name__, - unit, - description, - ) - return NoOpHistogram(name, unit=unit, description=description) - - def create_observable_gauge( - self, - name: str, - callbacks: Optional[Sequence[CallbackT]] = None, - unit: str = "", - description: str = "", - ) -> ObservableGauge: - """Returns a no-op ObservableGauge.""" - super().create_observable_gauge( - name, callbacks, unit=unit, description=description - ) - if self._is_instrument_registered( - name, NoOpObservableGauge, unit, description - )[0]: - _logger.warning( - "An instrument with name %s, type %s, unit %s and " - "description %s has been created already.", - name, - ObservableGauge.__name__, - unit, - description, - ) - 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.""" - super().create_observable_up_down_counter( - name, callbacks, unit=unit, description=description - ) - if self._is_instrument_registered( - name, NoOpObservableUpDownCounter, unit, description - )[0]: - _logger.warning( - "An instrument with name %s, type %s, unit %s and " - "description %s has been created already.", - name, - ObservableUpDownCounter.__name__, - unit, - description, - ) - 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, -) -> "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( # 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) diff --git a/opentelemetry-api/src/opentelemetry/_metrics/_internal/__init__.py b/opentelemetry-api/src/opentelemetry/_metrics/_internal/__init__.py new file mode 100644 index 00000000000..f7203859f06 --- /dev/null +++ b/opentelemetry-api/src/opentelemetry/_metrics/_internal/__init__.py @@ -0,0 +1,760 @@ +# 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 +""" + + +from abc import ABC, abstractmethod +from logging import getLogger +from os import environ +from threading import Lock +from typing import List, Optional, Sequence, Set, Tuple, Union, cast + +from opentelemetry._metrics._internal.instrument import ( + CallbackT, + Counter, + Histogram, + NoOpCounter, + NoOpHistogram, + NoOpObservableCounter, + NoOpObservableGauge, + NoOpObservableUpDownCounter, + NoOpUpDownCounter, + ObservableCounter, + ObservableGauge, + ObservableUpDownCounter, + UpDownCounter, + _ProxyCounter, + _ProxyHistogram, + _ProxyObservableCounter, + _ProxyObservableGauge, + _ProxyObservableUpDownCounter, + _ProxyUpDownCounter, +) +from opentelemetry.environment_variables import ( + _OTEL_PYTHON_METER_PROVIDER as OTEL_PYTHON_METER_PROVIDER, +) +from opentelemetry.util._once import Once +from opentelemetry.util._providers import _load_provider + +_logger = getLogger(__name__) + + +_ProxyInstrumentT = Union[ + _ProxyCounter, + _ProxyHistogram, + _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, + ) -> "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 + ``pkg_resources.get_distribution(instrumenting_library_name).version``. + + schema_url: Optional. Specifies the Schema URL of 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, + ) -> "Meter": + """Returns a NoOpMeter.""" + super().get_meter(name, version=version, schema_url=schema_url) + 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, + ) -> "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): + """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: Set[str] = set() + 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 _is_instrument_registered( + self, name: str, type_: type, unit: str, description: str + ) -> Tuple[bool, str]: + """ + Check if an instrument with the same name, type, unit and description + has been registered already. + + Returns a tuple. The first value is `True` if the instrument has been + registered already, `False` otherwise. The second value is the + instrument id. + """ + + instrument_id = ",".join( + [name.strip().lower(), type_.__name__, unit, description] + ) + + result = False + + with self._instrument_ids_lock: + if instrument_id in self._instrument_ids: + result = True + else: + self._instrument_ids.add(instrument_id) + + return (result, instrument_id) + + @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 returns 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() -> 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() -> 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]]: + 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 observations + + meter.create_observable_counter( + "system.cpu.time", + callbacks=[cpu_time_callback({"user", "system"})], + unit="s", + description="CPU time" + ) + + 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 = "", + ) -> 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. + """ + + @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 = "", + ) -> 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: 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.""" + super().create_counter(name, unit=unit, description=description) + if self._is_instrument_registered( + name, NoOpCounter, unit, description + )[0]: + _logger.warning( + "An instrument with name %s, type %s, unit %s and " + "description %s has been created already.", + name, + Counter.__name__, + unit, + description, + ) + return NoOpCounter(name, unit=unit, description=description) + + def create_up_down_counter( + self, + name: str, + unit: str = "", + description: str = "", + ) -> UpDownCounter: + """Returns a no-op UpDownCounter.""" + super().create_up_down_counter( + name, unit=unit, description=description + ) + if self._is_instrument_registered( + name, NoOpUpDownCounter, unit, description + )[0]: + _logger.warning( + "An instrument with name %s, type %s, unit %s and " + "description %s has been created already.", + name, + UpDownCounter.__name__, + unit, + description, + ) + 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.""" + super().create_observable_counter( + name, callbacks, unit=unit, description=description + ) + if self._is_instrument_registered( + name, NoOpObservableCounter, unit, description + )[0]: + _logger.warning( + "An instrument with name %s, type %s, unit %s and " + "description %s has been created already.", + name, + ObservableCounter.__name__, + unit, + description, + ) + return NoOpObservableCounter( + name, + callbacks, + unit=unit, + description=description, + ) + + def create_histogram( + self, + name: str, + unit: str = "", + description: str = "", + ) -> Histogram: + """Returns a no-op Histogram.""" + super().create_histogram(name, unit=unit, description=description) + if self._is_instrument_registered( + name, NoOpHistogram, unit, description + )[0]: + _logger.warning( + "An instrument with name %s, type %s, unit %s and " + "description %s has been created already.", + name, + Histogram.__name__, + unit, + description, + ) + return NoOpHistogram(name, unit=unit, description=description) + + def create_observable_gauge( + self, + name: str, + callbacks: Optional[Sequence[CallbackT]] = None, + unit: str = "", + description: str = "", + ) -> ObservableGauge: + """Returns a no-op ObservableGauge.""" + super().create_observable_gauge( + name, callbacks, unit=unit, description=description + ) + if self._is_instrument_registered( + name, NoOpObservableGauge, unit, description + )[0]: + _logger.warning( + "An instrument with name %s, type %s, unit %s and " + "description %s has been created already.", + name, + ObservableGauge.__name__, + unit, + description, + ) + 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.""" + super().create_observable_up_down_counter( + name, callbacks, unit=unit, description=description + ) + if self._is_instrument_registered( + name, NoOpObservableUpDownCounter, unit, description + )[0]: + _logger.warning( + "An instrument with name %s, type %s, unit %s and " + "description %s has been created already.", + name, + ObservableUpDownCounter.__name__, + unit, + description, + ) + 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, +) -> "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) + + +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( # 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) diff --git a/opentelemetry-api/src/opentelemetry/_metrics/instrument.py b/opentelemetry-api/src/opentelemetry/_metrics/_internal/instrument.py similarity index 95% rename from opentelemetry-api/src/opentelemetry/_metrics/instrument.py rename to opentelemetry-api/src/opentelemetry/_metrics/_internal/instrument.py index ed70f2ddc7e..04179524cc3 100644 --- a/opentelemetry-api/src/opentelemetry/_metrics/instrument.py +++ b/opentelemetry-api/src/opentelemetry/_metrics/_internal/instrument.py @@ -30,7 +30,7 @@ # pylint: disable=unused-import; needed for typing and sphinx from opentelemetry import _metrics as metrics -from opentelemetry._metrics.observation import Observation +from opentelemetry._metrics._internal.observation import Observation from opentelemetry.util.types import Attributes InstrumentT = TypeVar("InstrumentT", bound="Instrument") @@ -115,23 +115,7 @@ def __init__( super().__init__(name, unit=unit, description=description) -class _Adding(Instrument): - pass - - -class _Grouping(Instrument): - pass - - -class _Monotonic(_Adding): - pass - - -class _NonMonotonic(_Adding): - pass - - -class Counter(_Monotonic, Synchronous): +class Counter(Synchronous): """A Counter is a synchronous `Instrument` which supports non-negative increments.""" @abstractmethod @@ -176,7 +160,7 @@ def _create_real_instrument(self, meter: "metrics.Meter") -> Counter: return meter.create_counter(self._name, self._unit, self._description) -class UpDownCounter(_NonMonotonic, Synchronous): +class UpDownCounter(Synchronous): """An UpDownCounter is a synchronous `Instrument` which supports increments and decrements.""" @abstractmethod @@ -222,7 +206,7 @@ def _create_real_instrument(self, meter: "metrics.Meter") -> UpDownCounter: ) -class ObservableCounter(_Monotonic, Asynchronous): +class ObservableCounter(Asynchronous): """An ObservableCounter is an asynchronous `Instrument` which reports monotonically increasing value(s) when the instrument is being observed. """ @@ -252,7 +236,7 @@ def _create_real_instrument( ) -class ObservableUpDownCounter(_NonMonotonic, Asynchronous): +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. @@ -284,7 +268,7 @@ def _create_real_instrument( ) -class Histogram(_Grouping, Synchronous): +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. @@ -333,7 +317,7 @@ def _create_real_instrument(self, meter: "metrics.Meter") -> Histogram: ) -class ObservableGauge(_Grouping, Asynchronous): +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. diff --git a/opentelemetry-api/src/opentelemetry/_metrics/observation.py b/opentelemetry-api/src/opentelemetry/_metrics/_internal/observation.py similarity index 100% rename from opentelemetry-api/src/opentelemetry/_metrics/observation.py rename to opentelemetry-api/src/opentelemetry/_metrics/_internal/observation.py diff --git a/opentelemetry-api/tests/metrics/test_instruments.py b/opentelemetry-api/tests/metrics/test_instruments.py index 82079700409..30791fa212d 100644 --- a/opentelemetry-api/tests/metrics/test_instruments.py +++ b/opentelemetry-api/tests/metrics/test_instruments.py @@ -16,13 +16,14 @@ from inspect import Signature, isabstract, signature from unittest import TestCase -from opentelemetry._metrics import Meter, NoOpMeter -from opentelemetry._metrics.instrument import ( +from opentelemetry._metrics import ( Counter, Histogram, Instrument, + Meter, NoOpCounter, NoOpHistogram, + NoOpMeter, NoOpUpDownCounter, ObservableCounter, ObservableGauge, diff --git a/opentelemetry-api/tests/metrics/test_meter_provider.py b/opentelemetry-api/tests/metrics/test_meter_provider.py index 9efcfeb33a9..ef49ddaf87d 100644 --- a/opentelemetry-api/tests/metrics/test_meter_provider.py +++ b/opentelemetry-api/tests/metrics/test_meter_provider.py @@ -18,16 +18,16 @@ from pytest import fixture +import opentelemetry._metrics._internal as metrics_internal from opentelemetry import _metrics as metrics from opentelemetry._metrics import ( NoOpMeter, NoOpMeterProvider, - _ProxyMeter, - _ProxyMeterProvider, get_meter_provider, set_meter_provider, ) -from opentelemetry._metrics.instrument import ( +from opentelemetry._metrics._internal import _ProxyMeter, _ProxyMeterProvider +from opentelemetry._metrics._internal.instrument import ( _ProxyCounter, _ProxyHistogram, _ProxyObservableCounter, @@ -48,8 +48,10 @@ @fixture def reset_meter_provider(): + print(f"calling reset_metrics_globals() {reset_metrics_globals}") reset_metrics_globals() yield + print("teardown - calling reset_metrics_globals()") reset_metrics_globals() @@ -60,18 +62,19 @@ def test_set_meter_provider(reset_meter_provider): mock = Mock() - assert metrics._METER_PROVIDER is None + assert metrics_internal._METER_PROVIDER is None set_meter_provider(mock) - assert metrics._METER_PROVIDER is mock + assert metrics_internal._METER_PROVIDER is mock assert get_meter_provider() is mock def test_set_meter_provider_calls_proxy_provider(reset_meter_provider): with patch( - "opentelemetry._metrics._PROXY_METER_PROVIDER" + "opentelemetry._metrics._internal._PROXY_METER_PROVIDER" ) as mock_proxy_mp: + assert metrics_internal._PROXY_METER_PROVIDER is mock_proxy_mp mock_real_mp = Mock() set_meter_provider(mock_real_mp) mock_proxy_mp.on_set_meter_provider.assert_called_once_with( @@ -84,7 +87,7 @@ def test_get_meter_provider(reset_meter_provider): Test that the API provides a way to get a global default MeterProvider """ - assert metrics._METER_PROVIDER is None + assert metrics_internal._METER_PROVIDER is None assert isinstance(get_meter_provider(), _ProxyMeterProvider) @@ -94,9 +97,9 @@ def test_get_meter_provider(reset_meter_provider): "os.environ", {OTEL_PYTHON_METER_PROVIDER: "test_meter_provider"} ): - with patch("opentelemetry._metrics._load_provider", Mock()): + with patch("opentelemetry._metrics._internal._load_provider", Mock()): with patch( - "opentelemetry._metrics.cast", + "opentelemetry._metrics._internal.cast", Mock(**{"return_value": "test_meter_provider"}), ): assert get_meter_provider() == "test_meter_provider" diff --git a/opentelemetry-api/tests/metrics/test_measurement.py b/opentelemetry-api/tests/metrics/test_observation.py similarity index 96% rename from opentelemetry-api/tests/metrics/test_measurement.py rename to opentelemetry-api/tests/metrics/test_observation.py index 295c5b9a6be..05c644dbd35 100644 --- a/opentelemetry-api/tests/metrics/test_measurement.py +++ b/opentelemetry-api/tests/metrics/test_observation.py @@ -14,7 +14,7 @@ from unittest import TestCase -from opentelemetry._metrics.observation import Observation +from opentelemetry._metrics import Observation class TestObservation(TestCase): diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/__init__.py index 886b07aeade..ff495213edc 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/__init__.py @@ -17,21 +17,17 @@ from threading import Lock from typing import Optional, Sequence +from opentelemetry._metrics import Counter as APICounter +from opentelemetry._metrics import Histogram as APIHistogram from opentelemetry._metrics import Meter as APIMeter from opentelemetry._metrics import MeterProvider as APIMeterProvider from opentelemetry._metrics import NoOpMeter -from opentelemetry._metrics.instrument import Counter as APICounter -from opentelemetry._metrics.instrument import Histogram as APIHistogram -from opentelemetry._metrics.instrument import ( - ObservableCounter as APIObservableCounter, -) -from opentelemetry._metrics.instrument import ( - ObservableGauge as APIObservableGauge, -) -from opentelemetry._metrics.instrument import ( +from opentelemetry._metrics import ObservableCounter as APIObservableCounter +from opentelemetry._metrics import ObservableGauge as APIObservableGauge +from opentelemetry._metrics import ( ObservableUpDownCounter as APIObservableUpDownCounter, ) -from opentelemetry._metrics.instrument import UpDownCounter as APIUpDownCounter +from opentelemetry._metrics import UpDownCounter as APIUpDownCounter from opentelemetry.sdk._metrics.instrument import ( Counter, Histogram, diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/aggregation.py b/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/aggregation.py index 76accd205b4..f977925c83b 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/aggregation.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/aggregation.py @@ -25,7 +25,7 @@ from threading import Lock from typing import Generic, List, Optional, Sequence, TypeVar -from opentelemetry._metrics.instrument import ( +from opentelemetry._metrics import ( Asynchronous, Counter, Histogram, @@ -35,7 +35,6 @@ ObservableUpDownCounter, Synchronous, UpDownCounter, - _Monotonic, ) from opentelemetry.sdk._metrics.measurement import Measurement from opentelemetry.sdk._metrics.point import AggregationTemporality, Gauge @@ -89,7 +88,7 @@ class DefaultAggregation(_AggregationFactory): `UpDownCounter` `SumAggregation` `ObservableCounter` `SumAggregation` `ObservableUpDownCounter` `SumAggregation` - `opentelemetry._metrics.instrument.Histogram` `ExplicitBucketHistogramAggregation` + `opentelemetry._metrics.Histogram` `ExplicitBucketHistogramAggregation` `ObservableGauge` `LastValueAggregation` ============================================= ==================================== """ @@ -473,7 +472,7 @@ def _create_aggregation(self, instrument: Instrument) -> _Aggregation: temporality = AggregationTemporality.CUMULATIVE return _SumAggregation( - isinstance(instrument, _Monotonic), + isinstance(instrument, (Counter, ObservableCounter)), temporality, ) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/instrument.py b/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/instrument.py index 92de8775506..8d383e5402a 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/instrument.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/instrument.py @@ -17,19 +17,15 @@ from logging import getLogger from typing import TYPE_CHECKING, Dict, Generator, Iterable, Optional, Union -from opentelemetry._metrics.instrument import CallbackT -from opentelemetry._metrics.instrument import Counter as APICounter -from opentelemetry._metrics.instrument import Histogram as APIHistogram -from opentelemetry._metrics.instrument import ( - ObservableCounter as APIObservableCounter, -) -from opentelemetry._metrics.instrument import ( - ObservableGauge as APIObservableGauge, -) -from opentelemetry._metrics.instrument import ( +from opentelemetry._metrics import CallbackT +from opentelemetry._metrics import Counter as APICounter +from opentelemetry._metrics import Histogram as APIHistogram +from opentelemetry._metrics import ObservableCounter as APIObservableCounter +from opentelemetry._metrics import ObservableGauge as APIObservableGauge +from opentelemetry._metrics import ( ObservableUpDownCounter as APIObservableUpDownCounter, ) -from opentelemetry._metrics.instrument import UpDownCounter as APIUpDownCounter +from opentelemetry._metrics import UpDownCounter as APIUpDownCounter from opentelemetry.sdk._metrics.measurement import Measurement from opentelemetry.sdk.util.instrumentation import InstrumentationScope diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/metric_reader_storage.py b/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/metric_reader_storage.py index d76457f41b8..d2685391c1f 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/metric_reader_storage.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/metric_reader_storage.py @@ -15,7 +15,7 @@ from threading import RLock from typing import Dict, Iterable, List -from opentelemetry._metrics.instrument import Instrument +from opentelemetry._metrics import Instrument from opentelemetry.sdk._metrics._view_instrument_match import ( _ViewInstrumentMatch, ) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/view.py b/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/view.py index 28adebe643d..6bfde007bdc 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/view.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/view.py @@ -20,7 +20,7 @@ # FIXME import from typing when support for 3.6 is removed from typing_extensions import final -from opentelemetry._metrics.instrument import Instrument +from opentelemetry._metrics import Instrument from opentelemetry.sdk._metrics.aggregation import ( DefaultAggregation, _AggregationFactory, diff --git a/opentelemetry-sdk/tests/metrics/integration_test/test_cpu_time.py b/opentelemetry-sdk/tests/metrics/integration_test/test_cpu_time.py index e8c541c090d..d1688e02d42 100644 --- a/opentelemetry-sdk/tests/metrics/integration_test/test_cpu_time.py +++ b/opentelemetry-sdk/tests/metrics/integration_test/test_cpu_time.py @@ -17,8 +17,7 @@ from typing import Generator, Iterable, List from unittest import TestCase -from opentelemetry._metrics.instrument import Instrument -from opentelemetry._metrics.observation import Observation +from opentelemetry._metrics import Instrument, Observation from opentelemetry.sdk._metrics import MeterProvider from opentelemetry.sdk._metrics.measurement import Measurement diff --git a/opentelemetry-sdk/tests/metrics/test_in_memory_metric_reader.py b/opentelemetry-sdk/tests/metrics/test_in_memory_metric_reader.py index 3da776191a6..58b2aad3e82 100644 --- a/opentelemetry-sdk/tests/metrics/test_in_memory_metric_reader.py +++ b/opentelemetry-sdk/tests/metrics/test_in_memory_metric_reader.py @@ -15,7 +15,7 @@ from unittest import TestCase from unittest.mock import Mock -from opentelemetry._metrics.observation import Observation +from opentelemetry._metrics import Observation from opentelemetry.sdk._metrics import MeterProvider from opentelemetry.sdk._metrics.export import InMemoryMetricReader from opentelemetry.sdk._metrics.point import ( diff --git a/opentelemetry-sdk/tests/metrics/test_instrument.py b/opentelemetry-sdk/tests/metrics/test_instrument.py index ba792842a1e..a86715023eb 100644 --- a/opentelemetry-sdk/tests/metrics/test_instrument.py +++ b/opentelemetry-sdk/tests/metrics/test_instrument.py @@ -15,7 +15,7 @@ from unittest import TestCase from unittest.mock import Mock -from opentelemetry._metrics.observation import Observation +from opentelemetry._metrics import Observation from opentelemetry.sdk._metrics.instrument import ( Counter, Histogram, diff --git a/tests/opentelemetry-test-utils/src/opentelemetry/test/globals_test.py b/tests/opentelemetry-test-utils/src/opentelemetry/test/globals_test.py index f5e4ddaea36..e3d2ba731d2 100644 --- a/tests/opentelemetry-test-utils/src/opentelemetry/test/globals_test.py +++ b/tests/opentelemetry-test-utils/src/opentelemetry/test/globals_test.py @@ -14,8 +14,9 @@ import unittest -from opentelemetry import _metrics as metrics_api from opentelemetry import trace as trace_api +from opentelemetry._metrics import _internal as metrics_api +from opentelemetry._metrics._internal import _ProxyMeterProvider from opentelemetry.util._once import Once @@ -32,7 +33,7 @@ def reset_metrics_globals() -> None: """WARNING: only use this for tests.""" metrics_api._METER_PROVIDER_SET_ONCE = Once() # type: ignore[attr-defined] metrics_api._METER_PROVIDER = None # type: ignore[attr-defined] - metrics_api._PROXY_METER_PROVIDER = metrics_api._ProxyMeterProvider() # type: ignore[attr-defined] + metrics_api._PROXY_METER_PROVIDER = _ProxyMeterProvider() # type: ignore[attr-defined] class TraceGlobalsTest(unittest.TestCase):