Skip to content

Commit 056d3bb

Browse files
committed
Don't collect empty metrics from storage
If a metric has no datapoints, don't send it on. That can foul up exporters.
1 parent b9f31e9 commit 056d3bb

File tree

6 files changed

+47
-43
lines changed

6 files changed

+47
-43
lines changed

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

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
Sum,
4747
ExponentialHistogram as ExponentialHistogramType,
4848
)
49-
from typing import Dict
49+
from typing import Dict, Optional
5050
from opentelemetry.proto.resource.v1.resource_pb2 import (
5151
Resource as PB2Resource,
5252
)
@@ -154,7 +154,7 @@ def _common_configuration(
154154
)
155155

156156

157-
def encode_metrics(data: MetricsData) -> ExportMetricsServiceRequest:
157+
def encode_metrics(data: MetricsData) -> Optional[ExportMetricsServiceRequest]:
158158
resource_metrics_dict = {}
159159

160160
for resource_metrics in data.resource_metrics:
@@ -165,8 +165,6 @@ def encode_metrics(data: MetricsData) -> ExportMetricsServiceRequest:
165165
# associated with an unique resource.
166166
scope_metrics_dict = {}
167167

168-
resource_metrics_dict[resource] = scope_metrics_dict
169-
170168
for scope_metrics in resource_metrics.scope_metrics:
171169

172170
instrumentation_scope = scope_metrics.scope
@@ -181,9 +179,10 @@ def encode_metrics(data: MetricsData) -> ExportMetricsServiceRequest:
181179
)
182180
)
183181

184-
scope_metrics_dict[instrumentation_scope] = pb2_scope_metrics
185-
186182
for metric in scope_metrics.metrics:
183+
if len(metric.data.data_points) == 0:
184+
continue
185+
187186
pb2_metric = pb2.Metric(
188187
name=metric.name,
189188
description=metric.description,
@@ -301,6 +300,16 @@ def encode_metrics(data: MetricsData) -> ExportMetricsServiceRequest:
301300

302301
pb2_scope_metrics.metrics.append(pb2_metric)
303302

303+
if len(pb2_scope_metrics.metrics) > 0:
304+
# It's possible that all the metrics have zero
305+
# datapoints. If so, we'll have no metrics at all to
306+
# send.
307+
scope_metrics_dict[instrumentation_scope] = pb2_scope_metrics
308+
309+
if len(scope_metrics_dict) > 0:
310+
# Analogous to the above, it's possible that all scopes are empty.
311+
resource_metrics_dict[resource] = scope_metrics_dict
312+
304313
resource_data = []
305314
for (
306315
sdk_resource,
@@ -315,4 +324,6 @@ def encode_metrics(data: MetricsData) -> ExportMetricsServiceRequest:
315324
)
316325
)
317326
resource_metrics = resource_data
327+
if len(resource_metrics) == 0:
328+
return None
318329
return ExportMetricsServiceRequest(resource_metrics=resource_metrics)

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def export(
106106
"""Exports a batch of telemetry data.
107107
108108
Args:
109-
metrics: The list of `opentelemetry.sdk.metrics.export.Metric` objects to be exported
109+
metrics: The `opentelemetry.sdk.metrics.export.MetricsData` object to be exported
110110
111111
Returns:
112112
The result of the export
@@ -217,7 +217,7 @@ def __init__(
217217
"opentelemetry.sdk.metrics.export.MetricReader",
218218
AggregationTemporality,
219219
],
220-
Iterable["opentelemetry.sdk.metrics.export.Metric"],
220+
Optional["opentelemetry.sdk.metrics.export.MetricsData"],
221221
] = None
222222

223223
self._instrument_class_temporality = {
@@ -306,7 +306,8 @@ def __init__(
306306
@final
307307
def collect(self, timeout_millis: float = 10_000) -> None:
308308
"""Collects the metrics from the internal SDK state and
309-
invokes the `_receive_metrics` with the collection.
309+
invokes the `_receive_metrics` with the collection if it
310+
contains any data.
310311
311312
Args:
312313
timeout_millis: Amount of time in milliseconds before this function
@@ -335,7 +336,7 @@ def _set_collect_callback(
335336
"opentelemetry.sdk.metrics.export.MetricReader",
336337
AggregationTemporality,
337338
],
338-
Iterable["opentelemetry.sdk.metrics.export.Metric"],
339+
Optional["opentelemetry.sdk.metrics.export.MetricsData"],
339340
],
340341
) -> None:
341342
"""This function is internal to the SDK. It should not be called or overridden by users"""

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

Lines changed: 4 additions & 4 deletions
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
@@ -29,7 +29,7 @@
2929
from opentelemetry.sdk.metrics._internal.metric_reader_storage import (
3030
MetricReaderStorage,
3131
)
32-
from opentelemetry.sdk.metrics._internal.point import Metric
32+
from opentelemetry.sdk.metrics._internal.point import MetricsData
3333

3434

3535
class MeasurementConsumer(ABC):
@@ -51,7 +51,7 @@ def collect(
5151
self,
5252
metric_reader: "opentelemetry.sdk.metrics.MetricReader",
5353
timeout_millis: float = 10_000,
54-
) -> Iterable[Metric]:
54+
) -> Optional[MetricsData]:
5555
pass
5656

5757

@@ -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[MetricsData]:
9898

9999
with self._lock:
100100
metric_reader_storage = self._reader_storages[metric_reader]

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

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from logging import getLogger
1616
from threading import RLock
1717
from time import time_ns
18-
from typing import Dict, List
18+
from typing import Dict, List, Optional
1919

2020
from opentelemetry.metrics import (
2121
Asynchronous,
@@ -119,7 +119,7 @@ def consume_measurement(self, measurement: Measurement) -> None:
119119
):
120120
view_instrument_match.consume_measurement(measurement)
121121

122-
def collect(self) -> MetricsData:
122+
def collect(self) -> Optional[MetricsData]:
123123
# Use a list instead of yielding to prevent a slow reader from holding
124124
# SDK locks
125125

@@ -206,15 +206,19 @@ def collect(self) -> MetricsData:
206206
aggregation_temporality=aggregation_temporality,
207207
)
208208

209-
metrics.append(
210-
Metric(
211-
# pylint: disable=protected-access
212-
name=view_instrument_match._name,
213-
description=view_instrument_match._description,
214-
unit=view_instrument_match._instrument.unit,
215-
data=data,
209+
if len(data.data_points) > 0:
210+
metrics.append(
211+
Metric(
212+
# pylint: disable=protected-access
213+
name=view_instrument_match._name,
214+
description=view_instrument_match._description,
215+
unit=view_instrument_match._instrument.unit,
216+
data=data,
217+
)
216218
)
217-
)
219+
220+
if len(metrics) == 0:
221+
continue
218222

219223
if instrument.instrumentation_scope not in (
220224
instrumentation_scope_scope_metrics
@@ -231,13 +235,15 @@ def collect(self) -> MetricsData:
231235
instrument.instrumentation_scope
232236
].metrics.extend(metrics)
233237

238+
scope_metrics = list(instrumentation_scope_scope_metrics.values())
239+
if len(scope_metrics) == 0:
240+
return None
241+
234242
return MetricsData(
235243
resource_metrics=[
236244
ResourceMetrics(
237245
resource=self._sdk_config.resource,
238-
scope_metrics=list(
239-
instrumentation_scope_scope_metrics.values()
240-
),
246+
scope_metrics=scope_metrics,
241247
schema_url=self._sdk_config.resource.schema_url,
242248
)
243249
]

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,7 @@ def test_disable_default_views(self):
3232
counter.add(10, {"label": "value2"})
3333
counter.add(10, {"label": "value3"})
3434
self.assertEqual(
35-
(
36-
reader.get_metrics_data()
37-
.resource_metrics[0]
38-
.scope_metrics[0]
39-
.metrics
40-
),
41-
[],
35+
None, reader.get_metrics_data()
4236
)
4337

4438
def test_disable_default_views_add_custom(self):

opentelemetry-sdk/tests/metrics/test_metric_reader_storage.py

Lines changed: 1 addition & 9 deletions
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.assertEqual(None, (metric_reader_storage.collect()),)
326318

327319
def test_same_collection_start(self):
328320

0 commit comments

Comments
 (0)