Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .changelog/5370.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`opentelemetry-exporter-otlp-proto-http`: fix the OTLP HTTP metric exporter self-observability metrics over-counting data points when `max_export_batch_size` splits a batch; each split now reports its own data-point count instead of the whole-batch count.
Original file line number Diff line number Diff line change
Expand Up @@ -343,19 +343,15 @@ def export(
_logger.warning("Exporter already shutdown, ignoring batch")
return MetricExportResult.FAILURE

num_items = 0
for resource_metrics in metrics_data.resource_metrics:
for scope_metrics in resource_metrics.scope_metrics:
for metric in scope_metrics.metrics:
num_items += len(metric.data.data_points)

export_request = encode_metrics(metrics_data)
deadline_sec = time() + self._timeout

# If no batch size configured, export as single batch with retries as configured
if self._max_export_batch_size is None:
return self._export_with_retries(
export_request, deadline_sec, num_items
export_request,
deadline_sec,
_count_data_points(export_request),
)

# Else, export in batches of configured size
Expand All @@ -367,7 +363,7 @@ def export(
export_result = self._export_with_retries(
split_metrics_data,
deadline_sec,
num_items,
_count_data_points(split_metrics_data),
)
if export_result != MetricExportResult.SUCCESS:
return MetricExportResult.FAILURE
Expand Down Expand Up @@ -404,6 +400,18 @@ def set_meter_provider(self, meter_provider: MeterProvider) -> None:
)


def _count_data_points(export_request: ExportMetricsServiceRequest) -> int:
Comment thread
Krishnachaitanyakc marked this conversation as resolved.
"""Count the number of data points in an encoded metrics export request."""
count = 0
for resource_metrics in export_request.resource_metrics:
for scope_metrics in resource_metrics.scope_metrics:
for metric in scope_metrics.metrics:
field_name = metric.WhichOneof("data")
if field_name:
count += len(getattr(metric, field_name).data_points)
return count


def _split_metrics_data(
metrics_data: ExportMetricsServiceRequest,
max_export_batch_size: int | None = None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,56 @@ def setUp(self):
),
}

@patch.dict(
"os.environ", {OTEL_PYTHON_SDK_INTERNAL_METRICS_ENABLED: "true"}
)
@patch.object(Session, "post")
def test_split_export_records_per_split_data_point_count(self, mock_post):
# When a batch is split, each split must record its own data-point
# count in the self-observability metric, not the whole-batch count.
resp = Response()
resp.status_code = 200
mock_post.return_value = resp
metrics_data = MetricsData(
resource_metrics=[
ResourceMetrics(
resource=Resource(
attributes={"a": 1}, schema_url="resource_schema_url"
),
scope_metrics=[
ScopeMetrics(
scope=SDKInstrumentationScope(
name="name", version="version"
),
metrics=[
_generate_sum("s1", 1),
_generate_sum("s2", 2),
_generate_sum("s3", 3),
],
schema_url="scope_schema_url",
)
],
schema_url="resource_schema_url",
)
]
)
exporter = OTLPMetricExporter(
max_export_batch_size=1, meter_provider=self.meter_provider
)
self.assertEqual(
exporter.export(metrics_data), MetricExportResult.SUCCESS
)
self.assertEqual(mock_post.call_count, 3)
internal = self.metric_reader.get_metrics_data()
scope_metrics = internal.resource_metrics[0].scope_metrics[0]
exported = next(
metric
for metric in scope_metrics.metrics
if metric.name == "otel.sdk.exporter.metric_data_point.exported"
)
total = sum(dp.value for dp in exported.data.data_points)
self.assertEqual(total, 3)

def test_constructor_default(self):
exporter = OTLPMetricExporter()

Expand Down
Loading