Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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