Skip to content

Commit 55c9414

Browse files
tammy-baylis-swiherin049lzchenxrmx
authored
Add docker-tests coverage of metrics export (#5030)
* Add docker-tests metrics export * Update dc and otcollector * Add test_metrics_export_batch_size_two tests http,grpc * Changelog * Update tests/opentelemetry-docker-tests/tests/otlpexporter/__init__.py Co-authored-by: Lukas Hering <40302054+herin049@users.noreply.github.com> * Update tests/opentelemetry-docker-tests/tests/otlpexporter/__init__.py Co-authored-by: Lukas Hering <40302054+herin049@users.noreply.github.com> * Fix import * Update CHANGELOG.md * Create 5030.added --------- Co-authored-by: Lukas Hering <40302054+herin049@users.noreply.github.com> Co-authored-by: Leighton Chen <lechen@microsoft.com> Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>
1 parent f67bb84 commit 55c9414

5 files changed

Lines changed: 238 additions & 11 deletions

File tree

.changelog/5030.added

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
`opentelemetry-docker-tests`: add docker-tests coverage of `opentelemetry-exporter-otlp-proto-grpc` and `opentelemetry-exporter-otlp-proto-http` metrics export
Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
version: '3'
2-
31
services:
42
otopencensus:
53
image: rafaeljesus/opencensus-collector:latest
@@ -8,7 +6,7 @@ services:
86
- "8888:8888"
97
- "55678:55678"
108
otcollector:
11-
image: otel/opentelemetry-collector:0.31.0
9+
image: otel/opentelemetry-collector:0.149.0
1210
ports:
1311
- "4317:4317"
14-
- "4318:55681"
12+
- "4318:4318"

tests/opentelemetry-docker-tests/tests/otlpexporter/__init__.py

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,32 @@
11
# Copyright The OpenTelemetry Authors
22
# SPDX-License-Identifier: Apache-2.0
33

4+
import time
45
from abc import ABC, abstractmethod
56

6-
from opentelemetry.context import attach, detach, set_value
7+
from opentelemetry.context import (
8+
_SUPPRESS_INSTRUMENTATION_KEY,
9+
attach,
10+
detach,
11+
set_value,
12+
)
13+
from opentelemetry.sdk.metrics._internal.export import (
14+
MetricExportResult,
15+
PeriodicExportingMetricReader,
16+
)
17+
from opentelemetry.sdk.metrics._internal.point import (
18+
Metric,
19+
NumberDataPoint,
20+
Sum,
21+
)
22+
from opentelemetry.sdk.metrics.export import (
23+
MetricsData,
24+
ResourceMetrics,
25+
ScopeMetrics,
26+
)
27+
from opentelemetry.sdk.resources import Resource
728
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
29+
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
830

931

1032
class ExportStatusSpanProcessor(SimpleSpanProcessor):
@@ -18,13 +40,37 @@ def on_end(self, span):
1840
detach(token)
1941

2042

43+
class ExportStatusMetricReader(PeriodicExportingMetricReader):
44+
def __init__(self, exporter, **kwargs):
45+
# Very short export interval for testing
46+
super().__init__(exporter, export_interval_millis=1, **kwargs)
47+
self.export_status = []
48+
49+
def _receive_metrics(self, metrics_data, timeout_millis=10_000, **kwargs):
50+
token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True))
51+
try:
52+
export_result = self._exporter.export(
53+
metrics_data, timeout_millis=timeout_millis
54+
)
55+
self.export_status.append(export_result)
56+
except Exception:
57+
self.export_status.append(MetricExportResult.FAILURE)
58+
finally:
59+
detach(token)
60+
61+
2162
class BaseTestOTLPExporter(ABC):
2263
@abstractmethod
2364
def get_span_processor(self):
2465
pass
2566

67+
@abstractmethod
68+
def get_metric_reader(self):
69+
pass
70+
2671
# pylint: disable=no-member
2772
def test_export(self):
73+
"""Test span export"""
2874
with self.tracer.start_as_current_span("foo"):
2975
with self.tracer.start_as_current_span("bar"):
3076
with self.tracer.start_as_current_span("baz"):
@@ -35,3 +81,86 @@ def test_export(self):
3581
for export_status in self.span_processor.export_status:
3682
self.assertEqual(export_status.name, "SUCCESS")
3783
self.assertEqual(export_status.value, 0)
84+
85+
def test_metrics_export(self):
86+
"""Test metrics export from full metrics SDK pipeline"""
87+
counter = self.meter.create_counter("test_counter")
88+
histogram = self.meter.create_histogram("test_histogram")
89+
up_down_counter = self.meter.create_up_down_counter(
90+
"test_up_down_counter"
91+
)
92+
93+
counter.add(1, {"key1": "value1"})
94+
counter.add(2, {"key2": "value2"})
95+
histogram.record(1.5, {"key3": "value3"})
96+
histogram.record(2.5, {"key4": "value4"})
97+
up_down_counter.add(3, {"key5": "value5"})
98+
up_down_counter.add(-1, {"key6": "value6"})
99+
self.metric_reader.force_flush(timeout_millis=5000)
100+
time.sleep(0.1)
101+
102+
# Verify at least one export happened
103+
self.assertTrue(len(self.metric_reader.export_status) >= 1)
104+
# Verify all exports succeeded
105+
for export_status in self.metric_reader.export_status:
106+
self.assertEqual(export_status.name, "SUCCESS")
107+
self.assertEqual(export_status.value, 0)
108+
109+
@abstractmethod
110+
def test_metrics_export_batch_size_two(self):
111+
"""Test metrics max_export_batch_size=2 directly through exporter"""
112+
113+
def _create_test_metrics_data(self, num_data_points=6):
114+
"""Create test metrics data with specified number of data points."""
115+
data_points = [
116+
NumberDataPoint(
117+
attributes={"key": f"value{i}"},
118+
start_time_unix_nano=1000000 + i,
119+
time_unix_nano=2000000 + i,
120+
value=i + 1.0,
121+
)
122+
for i in range(num_data_points)
123+
]
124+
metric = Metric(
125+
name="otel_test_counter_foobar",
126+
description="Test counter metric for batch verification",
127+
unit="1",
128+
data=Sum(
129+
data_points=data_points,
130+
aggregation_temporality=1, # CUMULATIVE
131+
is_monotonic=True,
132+
),
133+
)
134+
scope_metrics = ScopeMetrics(
135+
scope=InstrumentationScope(name="test_scope"),
136+
metrics=[metric],
137+
schema_url=None,
138+
)
139+
resource_metrics = ResourceMetrics(
140+
resource=Resource.create({"service.name": "test-service"}),
141+
scope_metrics=[scope_metrics],
142+
schema_url=None,
143+
)
144+
145+
return MetricsData(resource_metrics=[resource_metrics]), data_points
146+
147+
def _verify_batch_export_result(
148+
self, result, data_points, batch_counter, max_batch_size=2
149+
):
150+
"""Verify export result and batch count for export batching tests."""
151+
self.assertEqual(
152+
result.name, "SUCCESS", f"Expected SUCCESS, got: {result}"
153+
)
154+
self.assertEqual(
155+
result.value, 0, f"Expected result code 0, got: {result.value}"
156+
)
157+
158+
expected_batches = (
159+
len(data_points) + max_batch_size - 1
160+
) // max_batch_size
161+
self.assertEqual(
162+
batch_counter.export_call_count,
163+
expected_batches,
164+
f"Expected {expected_batches} export calls with max_export_batch_size={max_batch_size} and {len(data_points)} data points, "
165+
f"but got {batch_counter.export_call_count} calls",
166+
)
Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,38 @@
11
# Copyright The OpenTelemetry Authors
22
# SPDX-License-Identifier: Apache-2.0
33

4-
from opentelemetry import trace
4+
from opentelemetry import metrics, trace
5+
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import (
6+
OTLPMetricExporter,
7+
)
58
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
69
OTLPSpanExporter,
710
)
11+
from opentelemetry.sdk.metrics import MeterProvider
812
from opentelemetry.sdk.trace import TracerProvider
13+
from opentelemetry.test.globals_test import (
14+
reset_metrics_globals,
15+
reset_trace_globals,
16+
)
917
from opentelemetry.test.test_base import TestBase
1018

11-
from . import BaseTestOTLPExporter, ExportStatusSpanProcessor
19+
from . import (
20+
BaseTestOTLPExporter,
21+
ExportStatusMetricReader,
22+
ExportStatusSpanProcessor,
23+
)
24+
25+
26+
class BatchCountingGRPCExporter(OTLPMetricExporter):
27+
"""gRPC exporter that counts actual batch export calls for testing."""
28+
29+
def __init__(self, *args, **kwargs):
30+
super().__init__(*args, **kwargs)
31+
self.export_call_count = 0
32+
33+
def _export(self, *args, **kwargs):
34+
self.export_call_count += 1
35+
return super()._export(*args, **kwargs)
1236

1337

1438
class TestOTLPGRPCExporter(BaseTestOTLPExporter, TestBase):
@@ -18,11 +42,37 @@ def get_span_processor(self):
1842
OTLPSpanExporter(insecure=True, timeout=1)
1943
)
2044

45+
def get_metric_reader(self):
46+
return ExportStatusMetricReader(
47+
OTLPMetricExporter(
48+
insecure=True, timeout=1, max_export_batch_size=2
49+
)
50+
)
51+
2152
def setUp(self):
2253
super().setUp()
2354

55+
reset_trace_globals()
2456
trace.set_tracer_provider(TracerProvider())
2557
self.tracer = trace.get_tracer(__name__)
2658
self.span_processor = self.get_span_processor()
27-
2859
trace.get_tracer_provider().add_span_processor(self.span_processor)
60+
61+
reset_metrics_globals()
62+
self.metric_reader = self.get_metric_reader()
63+
meter_provider = MeterProvider(metric_readers=[self.metric_reader])
64+
metrics.set_meter_provider(meter_provider)
65+
self.meter = metrics.get_meter(__name__)
66+
67+
def test_metrics_export_batch_size_two(self):
68+
"""Test metrics max_export_batch_size=2 directly through gRPC exporter"""
69+
batch_counter = BatchCountingGRPCExporter(
70+
endpoint="localhost:4317", insecure=True, max_export_batch_size=2
71+
)
72+
metrics_data, data_points = self._create_test_metrics_data(
73+
num_data_points=6
74+
)
75+
result = batch_counter.export(metrics_data)
76+
self._verify_batch_export_result(
77+
result, data_points, batch_counter, max_batch_size=2
78+
)
Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,75 @@
11
# Copyright The OpenTelemetry Authors
22
# SPDX-License-Identifier: Apache-2.0
33

4-
from opentelemetry import trace
4+
from opentelemetry import metrics, trace
5+
from opentelemetry.exporter.otlp.proto.http.metric_exporter import (
6+
OTLPMetricExporter,
7+
)
58
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
69
OTLPSpanExporter,
710
)
11+
from opentelemetry.sdk.metrics import MeterProvider
812
from opentelemetry.sdk.trace import TracerProvider
13+
from opentelemetry.test.globals_test import (
14+
reset_metrics_globals,
15+
reset_trace_globals,
16+
)
917
from opentelemetry.test.test_base import TestBase
1018

11-
from . import BaseTestOTLPExporter, ExportStatusSpanProcessor
19+
from . import (
20+
BaseTestOTLPExporter,
21+
ExportStatusMetricReader,
22+
ExportStatusSpanProcessor,
23+
)
24+
25+
26+
class BatchCountingHTTPExporter(OTLPMetricExporter):
27+
"""HTTP exporter that counts actual batch export calls for testing."""
28+
29+
def __init__(self, *args, **kwargs):
30+
super().__init__(*args, **kwargs)
31+
self.export_call_count = 0
32+
33+
def _export(self, *args, **kwargs):
34+
self.export_call_count += 1
35+
return super()._export(*args, **kwargs)
1236

1337

1438
class TestOTLPHTTPExporter(BaseTestOTLPExporter, TestBase):
1539
# pylint: disable=no-self-use
1640
def get_span_processor(self):
1741
return ExportStatusSpanProcessor(OTLPSpanExporter())
1842

43+
def get_metric_reader(self):
44+
return ExportStatusMetricReader(
45+
OTLPMetricExporter(max_export_batch_size=2)
46+
)
47+
1948
def setUp(self):
2049
super().setUp()
2150

51+
reset_trace_globals()
2252
trace.set_tracer_provider(TracerProvider())
2353
self.tracer = trace.get_tracer(__name__)
2454
self.span_processor = self.get_span_processor()
25-
2655
trace.get_tracer_provider().add_span_processor(self.span_processor)
56+
57+
reset_metrics_globals()
58+
self.metric_reader = self.get_metric_reader()
59+
meter_provider = MeterProvider(metric_readers=[self.metric_reader])
60+
metrics.set_meter_provider(meter_provider)
61+
self.meter = metrics.get_meter(__name__)
62+
63+
def test_metrics_export_batch_size_two(self):
64+
"""Test metrics max_export_batch_size=2 directly through HTTP exporter"""
65+
batch_counter = BatchCountingHTTPExporter(
66+
endpoint="http://localhost:4318/v1/metrics",
67+
max_export_batch_size=2,
68+
)
69+
metrics_data, data_points = self._create_test_metrics_data(
70+
num_data_points=6
71+
)
72+
result = batch_counter.export(metrics_data)
73+
self._verify_batch_export_result(
74+
result, data_points, batch_counter, max_batch_size=2
75+
)

0 commit comments

Comments
 (0)