Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add exemplars feature #4094

Merged
merged 51 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
9935e63
Add based classes
fcollonval Jul 30, 2024
5aa8353
Add exemplar to datapoint
fcollonval Jul 31, 2024
ceba9f1
Merge branch 'main' into ft/exemplars
lzchen Jul 31, 2024
d699f8d
Add time to Measurement
fcollonval Aug 11, 2024
3b1e40d
Add context to measurements
fcollonval Aug 11, 2024
05ebc34
First propagation of filter and reservoir factory
fcollonval Aug 12, 2024
6f74b3c
Reduce autoformat noise
fcollonval Aug 13, 2024
5fc775b
Fixing existing test - part 1
fcollonval Aug 13, 2024
80f040d
Lint the code
fcollonval Aug 13, 2024
19b2db4
Fix code and unit tests
fcollonval Aug 14, 2024
2b7793a
Add optional context args in Instrument.record/add/set
fcollonval Aug 14, 2024
6a25608
Add first test focusing on exemplar
fcollonval Aug 14, 2024
22cebeb
Add trivial test for exemplar filters
fcollonval Aug 14, 2024
f0ecace
Lint the code
fcollonval Aug 14, 2024
fadcefc
add unit tests for exemplarfilter, exemplarreservoir, and reservoirfa…
czhang771 Aug 16, 2024
70f8bef
add unit and integration tests
czhang771 Aug 23, 2024
351730c
update otlp exporter to export exemplars
czhang771 Aug 27, 2024
afd4e2c
address basic PR comments
czhang771 Aug 29, 2024
eece48d
add samples for exemplar filter and custom reservoir factory
czhang771 Aug 29, 2024
bfaec2d
clean up documentation on exemplar reservoir
czhang771 Aug 30, 2024
68e8824
refactor aggregate method and fix bucket index
czhang771 Aug 30, 2024
ed02f8b
refactored FixedSizeExemplarReservoirABC
czhang771 Aug 30, 2024
4f5efa7
Apply suggestions from review
fcollonval Sep 2, 2024
682a176
Lint the code
fcollonval Sep 2, 2024
0a13b62
Fix unit tests
fcollonval Sep 2, 2024
612404e
Improve the example
fcollonval Sep 2, 2024
e8aa164
Merge branch 'main' into ft/exemplars
fcollonval Sep 2, 2024
c29e0dd
Fix pylint errors
fcollonval Sep 3, 2024
1309b61
Add changelog entry
fcollonval Sep 3, 2024
2780df7
Fix opentelemetry-api tests
fcollonval Sep 3, 2024
0ea80dc
Fix TypeAlias non-supported with py38 and py39
fcollonval Sep 3, 2024
e7e4227
add exemplar filter as environment variable
czhang771 Sep 3, 2024
028e414
Fix format
fcollonval Sep 4, 2024
74016f0
Lint the latest version
fcollonval Sep 4, 2024
dcb44f0
More typing fixes for py38 and py39
fcollonval Sep 4, 2024
c0787ab
Fix log record tests
fcollonval Sep 4, 2024
e2b7778
Fix doc
fcollonval Sep 4, 2024
04a21e0
More linting
fcollonval Sep 4, 2024
975700a
Fix PyPy json loads
fcollonval Sep 4, 2024
5b32ffc
Fix sphinx doc generation
fcollonval Sep 4, 2024
f6f6233
Merge branch 'main' into ft/exemplars
fcollonval Sep 4, 2024
ecd03bc
fix view instrument match test case
czhang771 Sep 4, 2024
77d109e
Merge branch 'main' into ft/exemplars
fcollonval Sep 5, 2024
e3fe1cb
Make `encode_exemplars` protected
fcollonval Sep 5, 2024
627da87
Add some prose to the doc
fcollonval Sep 5, 2024
8559504
Add test for filtered attributes through View
fcollonval Sep 5, 2024
b5734f5
Lint the code
fcollonval Sep 5, 2024
592270a
Merge branch 'main' into ft/exemplars
lzchen Sep 5, 2024
e6c1d01
Fix pylint
fcollonval Sep 6, 2024
b1355f3
Merge branch 'main' into ft/exemplars
xrmx Sep 11, 2024
22a2875
Merge branch 'main' into ft/exemplars
lzchen Sep 12, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

# 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

Expand Down Expand Up @@ -173,6 +174,7 @@ def add(
self,
amount: Union[int, float],
attributes: Optional[Attributes] = None,
context: Optional[Context] = None,
) -> None:
pass

Expand All @@ -192,18 +194,20 @@ def add(
self,
amount: Union[int, float],
attributes: Optional[Attributes] = None,
context: Optional[Context] = None,
) -> None:
return super().add(amount, attributes=attributes)
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)
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)
Expand All @@ -217,6 +221,7 @@ def add(
self,
amount: Union[int, float],
attributes: Optional[Attributes] = None,
context: Optional[Context] = None,
) -> None:
pass

Expand All @@ -236,18 +241,20 @@ def add(
self,
amount: Union[int, float],
attributes: Optional[Attributes] = None,
context: Optional[Context] = None,
) -> None:
return super().add(amount, attributes=attributes)
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)
self._real_instrument.add(amount, attributes, context)

def _create_real_instrument(self, meter: "metrics.Meter") -> UpDownCounter:
return meter.create_up_down_counter(
Expand Down Expand Up @@ -328,6 +335,7 @@ def record(
self,
amount: Union[int, float],
attributes: Optional[Attributes] = None,
context: Optional[Context] = None,
) -> None:
pass

Expand All @@ -347,18 +355,20 @@ def record(
self,
amount: Union[int, float],
attributes: Optional[Attributes] = None,
context: Optional[Context] = None,
) -> None:
return super().record(amount, attributes=attributes)
return super().record(amount, attributes=attributes, context=context)


class _ProxyHistogram(_ProxyInstrument[Histogram], Histogram):
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)
self._real_instrument.record(amount, attributes, context)

def _create_real_instrument(self, meter: "metrics.Meter") -> Histogram:
return meter.create_histogram(
Expand Down Expand Up @@ -406,6 +416,7 @@ def set(
self,
amount: Union[int, float],
attributes: Optional[Attributes] = None,
context: Optional[Context] = None,
) -> None:
pass

Expand All @@ -425,8 +436,9 @@ def set(
self,
amount: Union[int, float],
attributes: Optional[Attributes] = None,
context: Optional[Context] = None,
) -> None:
return super().set(amount, attributes=attributes)
return super().set(amount, attributes=attributes, context=context)


class _ProxyGauge(
Expand All @@ -437,9 +449,10 @@ 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)
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)
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Union
from typing import Optional, Union

from opentelemetry.context import Context
from opentelemetry.util.types import Attributes


Expand All @@ -25,13 +26,18 @@ class Observation:
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
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]:
Expand All @@ -41,12 +47,17 @@ def value(self) -> Union[float, int]:
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})"
return f"Observation(value={self.value}, attributes={self.attributes}, context={self.context})"
16 changes: 16 additions & 0 deletions opentelemetry-sdk/src/opentelemetry/sdk/metrics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@

from opentelemetry.sdk.metrics._internal import Meter, MeterProvider
from opentelemetry.sdk.metrics._internal.exceptions import MetricsTimeoutError
from opentelemetry.sdk.metrics._internal.exemplar import (
AlignedHistogramBucketExemplarReservoir,
AlwaysOffExemplarFilter,
AlwaysOnExemplarFilter,
ExemplarFilter,
ExemplarReservoir,
SimpleFixedSizeExemplarReservoir,
TraceBasedExemplarFilter,
)
from opentelemetry.sdk.metrics._internal.instrument import Counter
from opentelemetry.sdk.metrics._internal.instrument import Gauge as _Gauge
from opentelemetry.sdk.metrics._internal.instrument import (
Expand All @@ -26,6 +35,11 @@
)

__all__ = [
"AlignedHistogramBucketExemplarReservoir",
"AlwaysOnExemplarFilter",
"AlwaysOffExemplarFilter",
"ExemplarFilter",
"ExemplarReservoir",
"Meter",
"MeterProvider",
"MetricsTimeoutError",
Expand All @@ -35,5 +49,7 @@
"ObservableCounter",
"ObservableGauge",
"ObservableUpDownCounter",
"SimpleFixedSizeExemplarReservoir",
"UpDownCounter",
"TraceBasedExemplarFilter",
]
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
from opentelemetry.metrics import _Gauge as APIGauge
from opentelemetry.sdk.environment_variables import OTEL_SDK_DISABLED
from opentelemetry.sdk.metrics._internal.exceptions import MetricsTimeoutError
from opentelemetry.sdk.metrics._internal.exemplar import (
ExemplarFilter,
TraceBasedExemplarFilter,
)
from opentelemetry.sdk.metrics._internal.instrument import (
_Counter,
_Gauge,
Expand Down Expand Up @@ -380,7 +384,8 @@ def __init__(
metric_readers: Sequence[
"opentelemetry.sdk.metrics.export.MetricReader"
] = (),
resource: Resource = None,
resource: Optional[Resource] = None,
exemplar_filter: Optional[ExemplarFilter] = None,
shutdown_on_exit: bool = True,
views: Sequence["opentelemetry.sdk.metrics.view.View"] = (),
):
Expand All @@ -390,6 +395,11 @@ def __init__(
if resource is None:
resource = Resource.create({})
self._sdk_config = SdkConfiguration(
exemplar_filter=(
TraceBasedExemplarFilter()
if exemplar_filter is None
else exemplar_filter
),
resource=resource,
metric_readers=metric_readers,
views=views,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,20 @@ def __init__(
)
if not isinstance(self._view._aggregation, DefaultAggregation):
self._aggregation = self._view._aggregation._create_aggregation(
self._instrument, None, 0
self._instrument,
None,
self._view._exemplar_reservoir_factory,
0,
)
else:
self._aggregation = self._instrument_class_aggregation[
self._instrument.__class__
]._create_aggregation(self._instrument, None, 0)
]._create_aggregation(
self._instrument,
None,
self._view._exemplar_reservoir_factory,
0,
)

def conflicts(self, other: "_ViewInstrumentMatch") -> bool:
# pylint: disable=protected-access
Expand All @@ -81,7 +89,9 @@ def conflicts(self, other: "_ViewInstrumentMatch") -> bool:
return result

# pylint: disable=protected-access
def consume_measurement(self, measurement: Measurement) -> None:
def consume_measurement(
self, measurement: Measurement, should_sample_exemplar: bool = True
) -> None:

if self._view._attribute_keys is not None:

Expand All @@ -107,6 +117,7 @@ def consume_measurement(self, measurement: Measurement) -> None:
self._view._aggregation._create_aggregation(
self._instrument,
attributes,
self._view._exemplar_reservoir_factory,
self._start_time_unix_nano,
)
)
Expand All @@ -116,11 +127,14 @@ def consume_measurement(self, measurement: Measurement) -> None:
]._create_aggregation(
self._instrument,
attributes,
self._view._exemplar_reservoir_factory,
self._start_time_unix_nano,
)
self._attributes_aggregation[aggr_key] = aggregation

self._attributes_aggregation[aggr_key].aggregate(measurement)
self._attributes_aggregation[aggr_key].aggregate(
measurement, should_sample_exemplar
)

def collect(
self,
Expand Down
Loading