Skip to content

Commit 4032bd9

Browse files
authored
Merge branch 'main' into pytyped
2 parents 90f4eca + f15821f commit 4032bd9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2722
-268
lines changed

.github/workflows/contrib_0.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2060,6 +2060,34 @@ jobs:
20602060
- name: Run tests
20612061
run: tox -e py38-test-instrumentation-aio-pika-3 -- -ra
20622062

2063+
py38-test-instrumentation-aiokafka:
2064+
name: instrumentation-aiokafka
2065+
runs-on: ubuntu-latest
2066+
steps:
2067+
- name: Checkout contrib repo @ SHA - ${{ env.CONTRIB_REPO_SHA }}
2068+
uses: actions/checkout@v4
2069+
with:
2070+
repository: open-telemetry/opentelemetry-python-contrib
2071+
ref: ${{ env.CONTRIB_REPO_SHA }}
2072+
2073+
- name: Checkout core repo @ SHA - ${{ github.sha }}
2074+
uses: actions/checkout@v4
2075+
with:
2076+
repository: open-telemetry/opentelemetry-python
2077+
path: opentelemetry-python-core
2078+
2079+
- name: Set up Python 3.8
2080+
uses: actions/setup-python@v5
2081+
with:
2082+
python-version: "3.8"
2083+
architecture: "x64"
2084+
2085+
- name: Install tox
2086+
run: pip install tox
2087+
2088+
- name: Run tests
2089+
run: tox -e py38-test-instrumentation-aiokafka -- -ra
2090+
20632091
py38-test-instrumentation-kafka-python:
20642092
name: instrumentation-kafka-python
20652093
runs-on: ubuntu-latest

.github/workflows/misc_0.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ jobs:
165165
runs-on: ubuntu-latest
166166
if: |
167167
!contains(github.event.pull_request.labels.*.name, 'Approve Public API check')
168-
&& github.actor != 'opentelemetrybot'
168+
&& github.actor != 'opentelemetrybot' && github.event_name == 'pull_request'
169169
steps:
170170
- name: Checkout repo @ SHA - ${{ github.sha }}
171171
uses: actions/checkout@v4

CHANGELOG.md

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

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ Every public symbol is something that has to be kept in order to maintain backwa
8282

8383
To check if your PR is adding public symbols, run `tox -e public-symbols-check`. This will always fail if public symbols are being added/removed. The idea
8484
behind this is that every PR that adds/removes public symbols fails in CI, forcing reviewers to check the symbols to make sure they are strictly necessary.
85-
If after checking them, it is considered that they are indeed necessary, the PR will be labeled with `Skip Public API check` so that this check is not
85+
If after checking them, it is considered that they are indeed necessary, the PR will be labeled with `Approve Public API check` so that this check is not
8686
run.
8787

8888
Also, we try to keep our console output as clean as possible. Most of the time this means catching expected log messages in the test cases:

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)