Skip to content

Commit ae7b32a

Browse files
committed
Merge branch 'main' into add_custom_any_type
2 parents ff2a0bf + d5fb2c4 commit ae7b32a

38 files changed

+2666
-260
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
([#4154](https://github.com/open-telemetry/opentelemetry-python/pull/4154))
1212
- sdk: Add support for log formatting
1313
([#4137](https://github.com/open-telemetry/opentelemetry-python/pull/4166))
14+
- sdk: Implementation of exemplars
15+
([#4094](https://github.com/open-telemetry/opentelemetry-python/pull/4094))
1416
- Implement events sdk
1517
([#4176](https://github.com/open-telemetry/opentelemetry-python/pull/4176))
1618

docs/conf.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,18 @@
138138
"py:class",
139139
"opentelemetry.proto.collector.logs.v1.logs_service_pb2.ExportLogsServiceRequest",
140140
),
141+
(
142+
"py:class",
143+
"opentelemetry.sdk.metrics._internal.exemplar.exemplar_reservoir.FixedSizeExemplarReservoirABC",
144+
),
145+
(
146+
"py:class",
147+
"opentelemetry.sdk.metrics._internal.exemplar.exemplar.Exemplar",
148+
),
149+
(
150+
"py:class",
151+
"opentelemetry.sdk.metrics._internal.aggregation._Aggregation",
152+
),
141153
]
142154

143155
# Add any paths that contain templates here, relative to this directory.

docs/examples/metrics/reader/README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ These examples show how to customize the metrics that are output by the SDK usin
55

66
* preferred_aggregation.py: Shows how to configure the preferred aggregation for metric instrument types.
77
* preferred_temporality.py: Shows how to configure the preferred temporality for metric instrument types.
8+
* preferred_exemplarfilter.py: Shows how to configure the exemplar filter.
89

910
The source files of these examples are available :scm_web:`here <docs/examples/metrics/reader/>`.
1011

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
import time
15+
16+
from opentelemetry import trace
17+
from opentelemetry.metrics import get_meter_provider, set_meter_provider
18+
from opentelemetry.sdk.metrics import MeterProvider
19+
from opentelemetry.sdk.metrics._internal.exemplar import AlwaysOnExemplarFilter
20+
from opentelemetry.sdk.metrics.export import (
21+
ConsoleMetricExporter,
22+
PeriodicExportingMetricReader,
23+
)
24+
from opentelemetry.sdk.trace import TracerProvider
25+
26+
# Create an ExemplarFilter instance
27+
# Available values are AlwaysOffExemplarFilter, AlwaysOnExemplarFilter
28+
# and TraceBasedExemplarFilter.
29+
# The default value is `TraceBasedExemplarFilter`.
30+
#
31+
# You can also use the environment variable `OTEL_METRICS_EXEMPLAR_FILTER`
32+
# to change the default value.
33+
#
34+
# You can also define your own filter by implementing the abstract class
35+
# `ExemplarFilter`
36+
exemplar_filter = AlwaysOnExemplarFilter()
37+
38+
exporter = ConsoleMetricExporter()
39+
40+
reader = PeriodicExportingMetricReader(
41+
exporter,
42+
export_interval_millis=5_000,
43+
)
44+
45+
# Set up the MeterProvider with the ExemplarFilter
46+
provider = MeterProvider(
47+
metric_readers=[reader],
48+
exemplar_filter=exemplar_filter, # Pass the ExemplarFilter to the MeterProvider
49+
)
50+
set_meter_provider(provider)
51+
52+
meter = get_meter_provider().get_meter("exemplar-filter-example", "0.1.2")
53+
counter = meter.create_counter("my-counter")
54+
55+
# Create a trace and span as the default exemplar filter `TraceBasedExemplarFilter`
56+
# will only store exemplar if a context exists
57+
trace.set_tracer_provider(TracerProvider())
58+
tracer = trace.get_tracer(__name__)
59+
with tracer.start_as_current_span("foo"):
60+
for value in range(10):
61+
counter.add(value)
62+
time.sleep(2.0)

docs/examples/metrics/views/README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ These examples show how to customize the metrics that are output by the SDK usin
77
* change_name.py: Shows how to change the name of a metric.
88
* limit_num_of_attrs.py: Shows how to limit the number of attributes that are output for a metric.
99
* drop_metrics_from_instrument.py: Shows how to drop measurements from an instrument.
10+
* change_reservoir_factory.py: Shows how to use your own ``ExemplarReservoir``
1011

1112
The source files of these examples are available :scm_web:`here <docs/examples/metrics/views/>`.
1213

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import random
16+
import time
17+
from typing import Type
18+
19+
from opentelemetry import trace
20+
from opentelemetry.metrics import get_meter_provider, set_meter_provider
21+
from opentelemetry.sdk.metrics import MeterProvider
22+
from opentelemetry.sdk.metrics._internal.aggregation import (
23+
DefaultAggregation,
24+
_Aggregation,
25+
_ExplicitBucketHistogramAggregation,
26+
)
27+
from opentelemetry.sdk.metrics._internal.exemplar import (
28+
AlignedHistogramBucketExemplarReservoir,
29+
ExemplarReservoirBuilder,
30+
SimpleFixedSizeExemplarReservoir,
31+
)
32+
from opentelemetry.sdk.metrics.export import (
33+
ConsoleMetricExporter,
34+
PeriodicExportingMetricReader,
35+
)
36+
from opentelemetry.sdk.metrics.view import View
37+
from opentelemetry.sdk.trace import TracerProvider
38+
39+
40+
# Create a custom reservoir factory with specified parameters
41+
def custom_reservoir_factory(
42+
aggregationType: Type[_Aggregation],
43+
) -> ExemplarReservoirBuilder:
44+
if issubclass(aggregationType, _ExplicitBucketHistogramAggregation):
45+
return AlignedHistogramBucketExemplarReservoir
46+
else:
47+
# Custom reservoir must accept `**kwargs` that may set the `size` for
48+
# _ExponentialBucketHistogramAggregation or the `boundaries` for
49+
# _ExplicitBucketHistogramAggregation
50+
return lambda **kwargs: SimpleFixedSizeExemplarReservoir(
51+
size=10,
52+
**{k: v for k, v in kwargs.items() if k != "size"},
53+
)
54+
55+
56+
# Create a view with the custom reservoir factory
57+
change_reservoir_factory_view = View(
58+
instrument_name="my.counter",
59+
name="name",
60+
aggregation=DefaultAggregation(),
61+
exemplar_reservoir_factory=custom_reservoir_factory,
62+
)
63+
64+
# Use console exporter for the example
65+
exporter = ConsoleMetricExporter()
66+
67+
# Create a metric reader with stdout exporter
68+
reader = PeriodicExportingMetricReader(exporter, export_interval_millis=1_000)
69+
provider = MeterProvider(
70+
metric_readers=[
71+
reader,
72+
],
73+
views=[
74+
change_reservoir_factory_view,
75+
],
76+
)
77+
set_meter_provider(provider)
78+
79+
meter = get_meter_provider().get_meter("reservoir-factory-change", "0.1.2")
80+
81+
my_counter = meter.create_counter("my.counter")
82+
83+
# Create a trace and span as the default exemplar filter `TraceBasedExemplarFilter`
84+
# will only store exemplar if a context exists
85+
trace.set_tracer_provider(TracerProvider())
86+
tracer = trace.get_tracer(__name__)
87+
with tracer.start_as_current_span("foo"):
88+
while 1:
89+
my_counter.add(random.randint(1, 10))
90+
time.sleep(random.random())

exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/metrics_encoder/__init__.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
)
2929
from opentelemetry.exporter.otlp.proto.common._internal import (
3030
_encode_attributes,
31+
_encode_span_id,
32+
_encode_trace_id,
3133
)
3234
from opentelemetry.sdk.environment_variables import (
3335
OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE,
@@ -254,6 +256,7 @@ def _encode_metric(metric, pb2_metric):
254256
pt = pb2.NumberDataPoint(
255257
attributes=_encode_attributes(data_point.attributes),
256258
time_unix_nano=data_point.time_unix_nano,
259+
exemplars=_encode_exemplars(data_point.exemplars),
257260
)
258261
if isinstance(data_point.value, int):
259262
pt.as_int = data_point.value
@@ -267,6 +270,7 @@ def _encode_metric(metric, pb2_metric):
267270
attributes=_encode_attributes(data_point.attributes),
268271
time_unix_nano=data_point.time_unix_nano,
269272
start_time_unix_nano=data_point.start_time_unix_nano,
273+
exemplars=_encode_exemplars(data_point.exemplars),
270274
count=data_point.count,
271275
sum=data_point.sum,
272276
bucket_counts=data_point.bucket_counts,
@@ -285,6 +289,7 @@ def _encode_metric(metric, pb2_metric):
285289
attributes=_encode_attributes(data_point.attributes),
286290
start_time_unix_nano=data_point.start_time_unix_nano,
287291
time_unix_nano=data_point.time_unix_nano,
292+
exemplars=_encode_exemplars(data_point.exemplars),
288293
)
289294
if isinstance(data_point.value, int):
290295
pt.as_int = data_point.value
@@ -322,6 +327,7 @@ def _encode_metric(metric, pb2_metric):
322327
attributes=_encode_attributes(data_point.attributes),
323328
time_unix_nano=data_point.time_unix_nano,
324329
start_time_unix_nano=data_point.start_time_unix_nano,
330+
exemplars=_encode_exemplars(data_point.exemplars),
325331
count=data_point.count,
326332
sum=data_point.sum,
327333
scale=data_point.scale,
@@ -342,3 +348,35 @@ def _encode_metric(metric, pb2_metric):
342348
"unsupported data type %s",
343349
metric.data.__class__.__name__,
344350
)
351+
352+
353+
def _encode_exemplars(sdk_exemplars: list) -> list:
354+
"""
355+
Converts a list of SDK Exemplars into a list of protobuf Exemplars.
356+
357+
Args:
358+
sdk_exemplars (list): The list of exemplars from the OpenTelemetry SDK.
359+
360+
Returns:
361+
list: A list of protobuf exemplars.
362+
"""
363+
pb_exemplars = []
364+
for sdk_exemplar in sdk_exemplars:
365+
pb_exemplar = pb2.Exemplar(
366+
time_unix_nano=sdk_exemplar.time_unix_nano,
367+
span_id=_encode_span_id(sdk_exemplar.span_id),
368+
trace_id=_encode_trace_id(sdk_exemplar.trace_id),
369+
filtered_attributes=_encode_attributes(
370+
sdk_exemplar.filtered_attributes
371+
),
372+
)
373+
# Assign the value based on its type in the SDK exemplar
374+
if isinstance(sdk_exemplar.value, float):
375+
pb_exemplar.as_double = sdk_exemplar.value
376+
elif isinstance(sdk_exemplar.value, int):
377+
pb_exemplar.as_int = sdk_exemplar.value
378+
else:
379+
raise ValueError("Exemplar value must be an int or float")
380+
pb_exemplars.append(pb_exemplar)
381+
382+
return pb_exemplars

0 commit comments

Comments
 (0)