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