Skip to content

Commit

Permalink
Synchronous gauge prototype (#3462)
Browse files Browse the repository at this point in the history
* merge with cherry pick

* docs/getting_started/metrics_example.py

* Fix

* edit changelog

* Format

* Update docs/getting_started/metrics_example.py

Co-authored-by: Diego Hurtado <ocelotl@users.noreply.github.com>

* Make synchronous gauge private for opentelemetry API

* Fix name of SDK Gauge

* Fix docs

---------

Co-authored-by: Diego Hurtado <ocelotl@users.noreply.github.com>
  • Loading branch information
sarafonseca-123 and ocelotl authored Feb 5, 2024
1 parent b696c3b commit 66e7d61
Show file tree
Hide file tree
Showing 20 changed files with 361 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

- Add Synchronous Gauge instrument
([#3462](https://github.com/open-telemetry/opentelemetry-python/pull/3462))
- Drop support for 3.7
([#3668](https://github.com/open-telemetry/opentelemetry-python/pull/3668))
- Include key in attribute sequence warning
Expand Down
11 changes: 11 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,14 @@
"scm_raw_web": (scm_raw_web + "/%s", "scm_raw_web"),
"scm_web": (scm_web + "/%s", "scm_web"),
}


def on_missing_reference(app, env, node, contnode):
# FIXME Remove when opentelemetry.metrics._Gauge is renamed to
# opentelemetry.metrics.Gauge
if node["reftarget"] == "opentelemetry.metrics.Gauge":
return contnode


def setup(app):
app.connect("missing-reference", on_missing_reference)
8 changes: 7 additions & 1 deletion docs/getting_started/metrics_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,10 @@ def observable_gauge_func(options: CallbackOptions) -> Iterable[Observation]:
histogram.record(99.9)

# Async Gauge
gauge = meter.create_observable_gauge("gauge", [observable_gauge_func])
observable_gauge = meter.create_observable_gauge(
"observable_gauge", [observable_gauge_func]
)

# Sync Gauge
gauge = meter.create_gauge("gauge")
gauge.set(1)
10 changes: 10 additions & 0 deletions opentelemetry-api/src/opentelemetry/metrics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,15 @@
CallbackOptions,
CallbackT,
Counter,
)
from opentelemetry.metrics._internal.instrument import Gauge as _Gauge
from opentelemetry.metrics._internal.instrument import (
Histogram,
Instrument,
NoOpCounter,
)
from opentelemetry.metrics._internal.instrument import NoOpGauge as _NoOpGauge
from opentelemetry.metrics._internal.instrument import (
NoOpHistogram,
NoOpObservableCounter,
NoOpObservableGauge,
Expand All @@ -74,6 +80,8 @@
Synchronous,
Asynchronous,
CallbackOptions,
_Gauge,
_NoOpGauge,
get_meter_provider,
get_meter,
Histogram,
Expand Down Expand Up @@ -103,6 +111,8 @@
"NoOpMeterProvider",
"Meter",
"Counter",
"_Gauge",
"_NoOpGauge",
"NoOpCounter",
"UpDownCounter",
"NoOpUpDownCounter",
Expand Down
54 changes: 54 additions & 0 deletions opentelemetry-api/src/opentelemetry/metrics/_internal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@
from opentelemetry.metrics._internal.instrument import (
CallbackT,
Counter,
Gauge,
Histogram,
NoOpCounter,
NoOpGauge,
NoOpHistogram,
NoOpObservableCounter,
NoOpObservableGauge,
Expand All @@ -63,6 +65,7 @@
ObservableUpDownCounter,
UpDownCounter,
_ProxyCounter,
_ProxyGauge,
_ProxyHistogram,
_ProxyObservableCounter,
_ProxyObservableGauge,
Expand All @@ -79,6 +82,7 @@
_ProxyInstrumentT = Union[
_ProxyCounter,
_ProxyHistogram,
_ProxyGauge,
_ProxyObservableCounter,
_ProxyObservableGauge,
_ProxyObservableUpDownCounter,
Expand Down Expand Up @@ -381,6 +385,22 @@ def create_histogram(
description: A description for this instrument and what it measures.
"""

@abstractmethod
def create_gauge(
self,
name: str,
unit: str = "",
description: str = "",
) -> Gauge:
"""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.
"""

@abstractmethod
def create_observable_gauge(
self,
Expand Down Expand Up @@ -512,6 +532,19 @@ def create_histogram(
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,
Expand Down Expand Up @@ -579,6 +612,27 @@ def create_counter(
)
return NoOpCounter(name, unit=unit, description=description)

def create_gauge(
self,
name: str,
unit: str = "",
description: str = "",
) -> Gauge:
"""Returns a no-op Gauge."""
super().create_gauge(name, unit=unit, description=description)
if self._is_instrument_registered(name, NoOpGauge, unit, description)[
0
]:
_logger.warning(
"An instrument with name %s, type %s, unit %s and "
"description %s has been created already.",
name,
Gauge.__name__,
unit,
description,
)
return NoOpGauge(name, unit=unit, description=description)

def create_up_down_counter(
self,
name: str,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -396,3 +396,50 @@ def _create_real_instrument(
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,
) -> 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,
) -> None:
return super().set(amount, attributes=attributes)


class _ProxyGauge(
_ProxyInstrument[Gauge],
Gauge,
):
def set(
self,
amount: Union[int, float],
attributes: Optional[Attributes] = None,
) -> None:
if self._real_instrument:
self._real_instrument.set(amount, attributes)

def _create_real_instrument(self, meter: "metrics.Meter") -> Gauge:
return meter.create_gauge(self._name, self._unit, self._description)
45 changes: 45 additions & 0 deletions opentelemetry-api/tests/metrics/test_instruments.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
ObservableGauge,
ObservableUpDownCounter,
UpDownCounter,
_Gauge,
)

# FIXME Test that the instrument methods can be called concurrently safely.
Expand Down Expand Up @@ -277,6 +278,50 @@ def test_histogram_record_method(self):
self.assertIsNone(NoOpHistogram("name").record(1))


class TestGauge(TestCase):
def test_create_gauge(self):
"""
Test that the Gauge can be created with create_gauge.
"""

self.assertTrue(
isinstance(NoOpMeter("name").create_gauge("name"), _Gauge)
)

def test_api_gauge_abstract(self):
"""
Test that the API Gauge is an abstract class.
"""

self.assertTrue(isabstract(_Gauge))

def test_create_gauge_api(self):
"""
Test that the API for creating a gauge accepts the name of the instrument.
Test that the API for creating a gauge accepts a sequence of callbacks.
Test that the API for creating a gauge accepts the unit of the instrument.
Test that the API for creating a gauge accepts the description of the instrument
"""

create_gauge_signature = signature(Meter.create_gauge)
self.assertIn("name", create_gauge_signature.parameters.keys())
self.assertIs(
create_gauge_signature.parameters["name"].default,
Signature.empty,
)
create_gauge_signature = signature(Meter.create_gauge)
create_gauge_signature = signature(Meter.create_gauge)
self.assertIn("unit", create_gauge_signature.parameters.keys())
self.assertIs(create_gauge_signature.parameters["unit"].default, "")

create_gauge_signature = signature(Meter.create_gauge)
self.assertIn("description", create_gauge_signature.parameters.keys())
self.assertIs(
create_gauge_signature.parameters["description"].default,
"",
)


class TestObservableGauge(TestCase):
def test_create_observable_gauge(self):
"""
Expand Down
13 changes: 13 additions & 0 deletions opentelemetry-api/tests/metrics/test_meter.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ def create_observable_counter(
def create_histogram(self, name, unit="", description=""):
super().create_histogram(name, unit=unit, description=description)

def create_gauge(self, name, unit="", description=""):
super().create_gauge(name, unit=unit, description=description)

def create_observable_gauge(self, name, callback, unit="", description=""):
super().create_observable_gauge(
name, callback, unit=unit, description=description
Expand All @@ -64,6 +67,7 @@ def test_repeated_instrument_names(self):
test_meter.create_up_down_counter("up_down_counter")
test_meter.create_observable_counter("observable_counter", Mock())
test_meter.create_histogram("histogram")
test_meter.create_gauge("gauge")
test_meter.create_observable_gauge("observable_gauge", Mock())
test_meter.create_observable_up_down_counter(
"observable_up_down_counter", Mock()
Expand All @@ -75,6 +79,7 @@ def test_repeated_instrument_names(self):
"counter",
"up_down_counter",
"histogram",
"gauge",
]:
with self.assertLogs(level=WARNING):
getattr(test_meter, f"create_{instrument_name}")(
Expand Down Expand Up @@ -123,6 +128,14 @@ def test_create_histogram(self):
self.assertTrue(hasattr(Meter, "create_histogram"))
self.assertTrue(Meter.create_histogram.__isabstractmethod__)

def test_create_gauge(self):
"""
Test that the meter provides a function to create a new Gauge
"""

self.assertTrue(hasattr(Meter, "create_gauge"))
self.assertTrue(Meter.create_gauge.__isabstractmethod__)

def test_create_observable_gauge(self):
"""
Test that the meter provides a function to create a new ObservableGauge
Expand Down
Loading

0 comments on commit 66e7d61

Please sign in to comment.