Skip to content

Commit 5e795f5

Browse files
committed
WIP
1 parent 1ea9c69 commit 5e795f5

File tree

10 files changed

+113
-69
lines changed

10 files changed

+113
-69
lines changed

opentelemetry-sdk/pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ dependencies = [
2727
"opentelemetry-api == 1.20.0.dev",
2828
"opentelemetry-semantic-conventions == 0.41b0.dev",
2929
"typing-extensions >= 3.7.4",
30+
"ipdb"
3031
]
3132

3233
[project.optional-dependencies]

opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/_view_instrument_match.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from logging import getLogger
1717
from threading import Lock
1818
from time import time_ns
19-
from typing import Dict, List, Sequence
19+
from typing import Dict, List, Sequence, Optional
2020

2121
from opentelemetry.metrics import Instrument
2222
from opentelemetry.sdk.metrics._internal.aggregation import (
@@ -126,7 +126,7 @@ def collect(
126126
self,
127127
aggregation_temporality: AggregationTemporality,
128128
collection_start_nanos: int,
129-
) -> Sequence[DataPointT]:
129+
) -> Optional[Sequence[DataPointT]]:
130130

131131
data_points: List[DataPointT] = []
132132
with self._lock:
@@ -136,4 +136,6 @@ def collect(
136136
)
137137
if data_point is not None:
138138
data_points.append(data_point)
139-
return data_points
139+
if data_points:
140+
return data_points
141+
return None

opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/export/__init__.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -322,10 +322,14 @@ def collect(self, timeout_millis: float = 10_000) -> None:
322322
)
323323
return
324324

325-
self._receive_metrics(
326-
self._collect(self, timeout_millis=timeout_millis),
327-
timeout_millis=timeout_millis,
328-
)
325+
metrics = self._collect(self, timeout_millis=timeout_millis)
326+
327+
if metrics is not None:
328+
329+
self._receive_metrics(
330+
metrics,
331+
timeout_millis=timeout_millis,
332+
)
329333

330334
@final
331335
def _set_collect_callback(
@@ -515,8 +519,7 @@ def _receive_metrics(
515519
timeout_millis: float = 10_000,
516520
**kwargs,
517521
) -> None:
518-
if metrics_data is None:
519-
return
522+
520523
token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True))
521524
try:
522525
with self._export_lock:

opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/measurement_consumer.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from abc import ABC, abstractmethod
1818
from threading import Lock
1919
from time import time_ns
20-
from typing import Iterable, List, Mapping
20+
from typing import Iterable, List, Mapping, Optional
2121

2222
# This kind of import is needed to avoid Sphinx errors.
2323
import opentelemetry.sdk.metrics
@@ -94,7 +94,7 @@ def collect(
9494
self,
9595
metric_reader: "opentelemetry.sdk.metrics.MetricReader",
9696
timeout_millis: float = 10_000,
97-
) -> Iterable[Metric]:
97+
) -> Optional[Iterable[Metric]]:
9898

9999
with self._lock:
100100
metric_reader_storage = self._reader_storages[metric_reader]
@@ -123,4 +123,6 @@ def collect(
123123
for measurement in measurements:
124124
metric_reader_storage.consume_measurement(measurement)
125125

126-
return self._reader_storages[metric_reader].collect()
126+
result = self._reader_storages[metric_reader].collect()
127+
128+
return result

opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/metric_reader_storage.py

+42-39
Original file line numberDiff line numberDiff line change
@@ -152,16 +152,21 @@ def collect(self) -> MetricsData:
152152

153153
for view_instrument_match in view_instrument_matches:
154154

155+
data_points = view_instrument_match.collect(
156+
aggregation_temporality, collection_start_nanos
157+
)
158+
159+
if data_points is None:
160+
continue
161+
155162
if isinstance(
156163
# pylint: disable=protected-access
157164
view_instrument_match._aggregation,
158165
_SumAggregation,
159166
):
160167
data = Sum(
161168
aggregation_temporality=aggregation_temporality,
162-
data_points=view_instrument_match.collect(
163-
aggregation_temporality, collection_start_nanos
164-
),
169+
data_points=data_points,
165170
is_monotonic=isinstance(
166171
instrument, (Counter, ObservableCounter)
167172
),
@@ -171,20 +176,14 @@ def collect(self) -> MetricsData:
171176
view_instrument_match._aggregation,
172177
_LastValueAggregation,
173178
):
174-
data = Gauge(
175-
data_points=view_instrument_match.collect(
176-
aggregation_temporality, collection_start_nanos
177-
)
178-
)
179+
data = Gauge(data_points=data_points)
179180
elif isinstance(
180181
# pylint: disable=protected-access
181182
view_instrument_match._aggregation,
182183
_ExplicitBucketHistogramAggregation,
183184
):
184185
data = Histogram(
185-
data_points=view_instrument_match.collect(
186-
aggregation_temporality, collection_start_nanos
187-
),
186+
data_points=data_points,
188187
aggregation_temporality=aggregation_temporality,
189188
)
190189
elif isinstance(
@@ -200,9 +199,7 @@ def collect(self) -> MetricsData:
200199
_ExponentialBucketHistogramAggregation,
201200
):
202201
data = ExponentialHistogram(
203-
data_points=view_instrument_match.collect(
204-
aggregation_temporality, collection_start_nanos
205-
),
202+
data_points=data_points,
206203
aggregation_temporality=aggregation_temporality,
207204
)
208205

@@ -216,32 +213,38 @@ def collect(self) -> MetricsData:
216213
)
217214
)
218215

219-
if instrument.instrumentation_scope not in (
220-
instrumentation_scope_scope_metrics
221-
):
222-
instrumentation_scope_scope_metrics[
223-
instrument.instrumentation_scope
224-
] = ScopeMetrics(
225-
scope=instrument.instrumentation_scope,
226-
metrics=metrics,
227-
schema_url=instrument.instrumentation_scope.schema_url,
216+
if metrics:
217+
218+
if instrument.instrumentation_scope not in (
219+
instrumentation_scope_scope_metrics
220+
):
221+
instrumentation_scope_scope_metrics[
222+
instrument.instrumentation_scope
223+
] = ScopeMetrics(
224+
scope=instrument.instrumentation_scope,
225+
metrics=metrics,
226+
schema_url=instrument.instrumentation_scope.schema_url,
227+
)
228+
else:
229+
instrumentation_scope_scope_metrics[
230+
instrument.instrumentation_scope
231+
].metrics.extend(metrics)
232+
233+
if instrumentation_scope_scope_metrics:
234+
235+
return MetricsData(
236+
resource_metrics=[
237+
ResourceMetrics(
238+
resource=self._sdk_config.resource,
239+
scope_metrics=list(
240+
instrumentation_scope_scope_metrics.values()
241+
),
242+
schema_url=self._sdk_config.resource.schema_url,
228243
)
229-
else:
230-
instrumentation_scope_scope_metrics[
231-
instrument.instrumentation_scope
232-
].metrics.extend(metrics)
233-
234-
return MetricsData(
235-
resource_metrics=[
236-
ResourceMetrics(
237-
resource=self._sdk_config.resource,
238-
scope_metrics=list(
239-
instrumentation_scope_scope_metrics.values()
240-
),
241-
schema_url=self._sdk_config.resource.schema_url,
242-
)
243-
]
244-
)
244+
]
245+
)
246+
247+
return None
245248

246249
def _handle_view_instrument_match(
247250
self,

opentelemetry-sdk/tests/metrics/integration_test/test_console_exporter.py

+16
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,19 @@ def test_console_exporter(self):
7272

7373
self.assertEqual(metrics["attributes"], {"a": "b"})
7474
self.assertEqual(metrics["value"], 1)
75+
76+
def test_console_exporter_no_export(self):
77+
78+
output = StringIO()
79+
exporter = ConsoleMetricExporter(out=output)
80+
reader = PeriodicExportingMetricReader(
81+
exporter, export_interval_millis=100
82+
)
83+
provider = MeterProvider(metric_readers=[reader])
84+
provider.shutdown()
85+
86+
output.seek(0)
87+
actual = "".join(output.readlines())
88+
expected = ""
89+
90+
self.assertEqual(actual, expected)

opentelemetry-sdk/tests/metrics/integration_test/test_disable_default_views.py

+1-9
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,7 @@ def test_disable_default_views(self):
3131
counter.add(10, {"label": "value1"})
3232
counter.add(10, {"label": "value2"})
3333
counter.add(10, {"label": "value3"})
34-
self.assertEqual(
35-
(
36-
reader.get_metrics_data()
37-
.resource_metrics[0]
38-
.scope_metrics[0]
39-
.metrics
40-
),
41-
[],
42-
)
34+
self.assertIsNone(reader.get_metrics_data())
4335

4436
def test_disable_default_views_add_custom(self):
4537
reader = InMemoryMetricReader()

opentelemetry-sdk/tests/metrics/integration_test/test_exporter_concurrency.py

+14
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,15 @@ class TestExporterConcurrency(ConcurrencyTestBase):
7474
> be called again only after the current call returns.
7575
7676
https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#exportbatch
77+
78+
This test also tests that a thread that calls the a
79+
``MetricReader.collect`` method using an asynchronous instrument is able
80+
to perform two actions in the same thread lock space (without it being
81+
interrupted by another thread):
82+
83+
1. Consume the measurement produced by the callback associated to the
84+
asynchronous instrument.
85+
2. Export the measurement mentioned in the step above.
7786
"""
7887

7988
def test_exporter_not_called_concurrently(self):
@@ -84,7 +93,11 @@ def test_exporter_not_called_concurrently(self):
8493
)
8594
meter_provider = MeterProvider(metric_readers=[reader])
8695

96+
counter_cb_counter = 0
97+
8798
def counter_cb(options: CallbackOptions):
99+
nonlocal counter_cb_counter
100+
counter_cb_counter += 1
88101
yield Observation(2)
89102

90103
meter_provider.get_meter(__name__).create_observable_counter(
@@ -97,6 +110,7 @@ def test_many_threads():
97110

98111
self.run_with_many_threads(test_many_threads, num_threads=100)
99112

113+
self.assertEqual(counter_cb_counter, 100)
100114
# no thread should be in export() now
101115
self.assertEqual(exporter.count_in_export, 0)
102116
# should be one call for each thread

opentelemetry-sdk/tests/metrics/test_metric_reader_storage.py

+1-9
Original file line numberDiff line numberDiff line change
@@ -314,15 +314,7 @@ def test_drop_aggregation(self):
314314
)
315315
metric_reader_storage.consume_measurement(Measurement(1, counter))
316316

317-
self.assertEqual(
318-
[],
319-
(
320-
metric_reader_storage.collect()
321-
.resource_metrics[0]
322-
.scope_metrics[0]
323-
.metrics
324-
),
325-
)
317+
self.assertIsNone(metric_reader_storage.collect())
326318

327319
def test_same_collection_start(self):
328320

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from opentelemetry.sdk.metrics import MeterProvider
2+
from opentelemetry.sdk.metrics.export import (
3+
ConsoleMetricExporter,
4+
PeriodicExportingMetricReader,
5+
)
6+
from unittest import TestCase
7+
from time import sleep
8+
9+
10+
class TestNoData(TestCase):
11+
12+
def test_no_data(self):
13+
reader = PeriodicExportingMetricReader(
14+
ConsoleMetricExporter(),
15+
export_interval_millis=10
16+
)
17+
provider = MeterProvider(metric_readers=[reader])
18+
provider
19+
sleep(1)

0 commit comments

Comments
 (0)