Skip to content

Commit ddbab50

Browse files
link04zacharycmontoyacbeauchesne
authored
Enables .NET OTEL Metrics Tests (#5535)
Co-authored-by: Zach Montoya <[email protected]> Co-authored-by: Charles de Beauchesne <[email protected]>
1 parent 55520b6 commit ddbab50

File tree

3 files changed

+531
-29
lines changed

3 files changed

+531
-29
lines changed

manifests/dotnet.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,18 @@ tests/:
585585
test_otel_env_vars.py:
586586
Test_Otel_Env_Vars: v2.53.0
587587
test_otel_logs.py: missing_feature
588-
test_otel_metrics.py: missing_feature
588+
test_otel_metrics.py:
589+
Test_Otel_Metrics_Api_Instrument: v3.29.0
590+
Test_Otel_Metrics_Api_Meter: v3.29.0
591+
Test_Otel_Metrics_Api_MeterProvider: v3.29.0
592+
Test_Otel_Metrics_Configuration_Enabled: v3.29.0
593+
Test_Otel_Metrics_Configuration_OTLP_Exporter_Metrics_Endpoint: v3.29.0
594+
Test_Otel_Metrics_Configuration_OTLP_Exporter_Metrics_Headers: v3.29.0
595+
Test_Otel_Metrics_Configuration_OTLP_Exporter_Metrics_Protocol: v3.29.0
596+
Test_Otel_Metrics_Configuration_Temporality_Preference: v3.29.0
597+
Test_Otel_Metrics_Host_Name: v3.29.0
598+
Test_Otel_Metrics_Resource_Attributes: v3.29.0
599+
Test_Otel_Metrics_Telemetry: v3.29.0
589600
test_otel_span_with_baggage.py:
590601
Test_Otel_Span_With_Baggage: missing_feature
591602
test_otel_tracer.py:

tests/parametric/test_otel_metrics.py

Lines changed: 91 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import pytest
33

44
from utils import context, features, missing_feature, scenarios
5+
56
from utils.docker_fixtures import TestAgentAPI
67
from .conftest import APMLibrary
78

@@ -10,7 +11,8 @@
1011

1112
DEFAULT_METER_NAME = "parametric-api"
1213
DEFAULT_METER_VERSION = "1.0.0"
13-
DEFAULT_SCHEMA_URL = "https://opentelemetry.io/schemas/1.27.0"
14+
# schema_url is not supported by .NET's System.Diagnostics.Metrics API
15+
DEFAULT_SCHEMA_URL = "" if context.library == "dotnet" else "https://opentelemetry.io/schemas/1.21.0"
1416

1517
DEFAULT_INSTRUMENT_UNIT = "triggers"
1618
DEFAULT_INSTRUMENT_DESCRIPTION = "test_description"
@@ -104,7 +106,9 @@ def assert_scope_metric(
104106
expected_scope_attributes.items()
105107
== {item["key"]: item["value"]["string_value"] for item in scope_metric["scope"]["attributes"]}.items()
106108
)
107-
assert scope_metric["schema_url"] == schema_url
109+
110+
if context.library != "dotnet": # .NET does not support schema_url
111+
assert scope_metric["schema_url"] == schema_url
108112

109113

110114
def assert_metric_info(metric: dict, name: str, unit: str, description: str):
@@ -121,7 +125,13 @@ def assert_sum_aggregation(
121125

122126
for sum_data_point in sum_aggregation["data_points"]:
123127
if attributes == {item["key"]: item["value"]["string_value"] for item in sum_data_point["attributes"]}:
124-
assert float(sum_data_point.get("as_int", 0)) == value
128+
if "as_double" in sum_data_point:
129+
actual_value = sum_data_point["as_double"]
130+
elif "as_int" in sum_data_point:
131+
actual_value = int(sum_data_point["as_int"])
132+
else:
133+
actual_value = None
134+
assert actual_value == value
125135
assert (
126136
attributes.items()
127137
== {item["key"]: item["value"]["string_value"] for item in sum_data_point["attributes"]}.items()
@@ -135,7 +145,13 @@ def assert_sum_aggregation(
135145
def assert_gauge_aggregation(gauge_aggregation: dict, value: int, attributes: dict[str, str]):
136146
for gauge_data_point in gauge_aggregation["data_points"]:
137147
if attributes == {item["key"]: item["value"]["string_value"] for item in gauge_data_point["attributes"]}:
138-
assert float(gauge_data_point.get("as_int", 0)) == value
148+
if "as_double" in gauge_data_point:
149+
actual_value = gauge_data_point["as_double"]
150+
elif "as_int" in gauge_data_point:
151+
actual_value = int(gauge_data_point["as_int"])
152+
else:
153+
actual_value = None
154+
assert actual_value == value
139155
assert "time_unix_nano" in gauge_data_point
140156
return
141157

@@ -195,7 +211,6 @@ def get_expected_bucket_counts(entries: list[int], bucket_boundaries: list[float
195211
@scenarios.parametric
196212
@features.otel_metrics_api
197213
@missing_feature(context.library == "cpp", reason="Not yet implemented", force_skip=True)
198-
@missing_feature(context.library == "dotnet", reason="Not yet implemented", force_skip=True)
199214
@missing_feature(context.library == "golang", reason="Not yet implemented", force_skip=True)
200215
@missing_feature(context.library == "java", reason="Not yet implemented", force_skip=True)
201216
@missing_feature(context.library == "php", reason="Not yet implemented", force_skip=True)
@@ -267,7 +282,6 @@ def test_otlp_metrics_disabled(
267282
@scenarios.parametric
268283
@features.otel_metrics_api
269284
@missing_feature(context.library == "cpp", reason="Not yet implemented", force_skip=True)
270-
@missing_feature(context.library == "dotnet", reason="Not yet implemented", force_skip=True)
271285
@missing_feature(context.library == "golang", reason="Not yet implemented", force_skip=True)
272286
@missing_feature(context.library == "java", reason="Not yet implemented", force_skip=True)
273287
@missing_feature(context.library == "php", reason="Not yet implemented", force_skip=True)
@@ -341,7 +355,6 @@ def test_otel_get_meter_by_distinct(
341355
@scenarios.parametric
342356
@features.otel_metrics_api
343357
@missing_feature(context.library == "cpp", reason="Not yet implemented", force_skip=True)
344-
@missing_feature(context.library == "dotnet", reason="Not yet implemented", force_skip=True)
345358
@missing_feature(context.library == "golang", reason="Not yet implemented", force_skip=True)
346359
@missing_feature(context.library == "java", reason="Not yet implemented", force_skip=True)
347360
@missing_feature(context.library == "php", reason="Not yet implemented", force_skip=True)
@@ -473,7 +486,11 @@ def test_otel_create_instruments_by_distinct(
473486

474487
# Assert that the ScopeMetrics has the correct Scope, SchemaUrl, and Metrics data
475488
assert_scope_metric(
476-
scope_metrics[0], DEFAULT_METER_NAME, DEFAULT_METER_VERSION, DEFAULT_SCHEMA_URL, DEFAULT_SCOPE_ATTRIBUTES
489+
scope_metrics[0],
490+
DEFAULT_METER_NAME,
491+
DEFAULT_METER_VERSION,
492+
DEFAULT_SCHEMA_URL,
493+
DEFAULT_SCOPE_ATTRIBUTES,
477494
)
478495

479496
# Instrument names are case-insensitive, so the measurements for 'name' and 'name_upper' will be recorded by the same Instrument,
@@ -562,7 +579,6 @@ def test_otel_create_instruments_by_distinct(
562579
@scenarios.parametric
563580
@features.otel_metrics_api
564581
@missing_feature(context.library == "cpp", reason="Not yet implemented", force_skip=True)
565-
@missing_feature(context.library == "dotnet", reason="Not yet implemented", force_skip=True)
566582
@missing_feature(context.library == "golang", reason="Not yet implemented", force_skip=True)
567583
@missing_feature(context.library == "java", reason="Not yet implemented", force_skip=True)
568584
@missing_feature(context.library == "php", reason="Not yet implemented", force_skip=True)
@@ -622,7 +638,11 @@ def test_otel_counter_add_non_negative_and_negative_values(
622638

623639
# Assert that the ScopeMetrics has the correct Scope, SchemaUrl, and Metrics data
624640
assert_scope_metric(
625-
scope_metrics[0], DEFAULT_METER_NAME, DEFAULT_METER_VERSION, DEFAULT_SCHEMA_URL, DEFAULT_SCOPE_ATTRIBUTES
641+
scope_metrics[0],
642+
DEFAULT_METER_NAME,
643+
DEFAULT_METER_VERSION,
644+
DEFAULT_SCHEMA_URL,
645+
DEFAULT_SCOPE_ATTRIBUTES,
626646
)
627647

628648
metric = scope_metrics[0]["metrics"][0]
@@ -669,7 +689,11 @@ def test_otel_counter_add_non_negative_values_with_different_tags(
669689

670690
# Assert that the ScopeMetrics has the correct Scope, SchemaUrl, and Metrics data
671691
assert_scope_metric(
672-
scope_metrics[0], DEFAULT_METER_NAME, DEFAULT_METER_VERSION, DEFAULT_SCHEMA_URL, DEFAULT_SCOPE_ATTRIBUTES
692+
scope_metrics[0],
693+
DEFAULT_METER_NAME,
694+
DEFAULT_METER_VERSION,
695+
DEFAULT_SCHEMA_URL,
696+
DEFAULT_SCOPE_ATTRIBUTES,
673697
)
674698

675699
metric = scope_metrics[0]["metrics"][0]
@@ -723,7 +747,11 @@ def test_otel_updowncounter_add_multiple_values(self, test_agent: TestAgentAPI,
723747

724748
# Assert that the ScopeMetrics has the correct Scope, SchemaUrl, and Metrics data
725749
assert_scope_metric(
726-
scope_metrics[0], DEFAULT_METER_NAME, DEFAULT_METER_VERSION, DEFAULT_SCHEMA_URL, DEFAULT_SCOPE_ATTRIBUTES
750+
scope_metrics[0],
751+
DEFAULT_METER_NAME,
752+
DEFAULT_METER_VERSION,
753+
DEFAULT_SCHEMA_URL,
754+
DEFAULT_SCOPE_ATTRIBUTES,
727755
)
728756

729757
metric = find_metric_by_name(scope_metrics[0], name)
@@ -772,7 +800,11 @@ def test_otel_updowncounter_add_multiple_values_with_different_tags(
772800

773801
# Assert that the ScopeMetrics has the correct Scope, SchemaUrl, and Metrics data
774802
assert_scope_metric(
775-
scope_metrics[0], DEFAULT_METER_NAME, DEFAULT_METER_VERSION, DEFAULT_SCHEMA_URL, DEFAULT_SCOPE_ATTRIBUTES
803+
scope_metrics[0],
804+
DEFAULT_METER_NAME,
805+
DEFAULT_METER_VERSION,
806+
DEFAULT_SCHEMA_URL,
807+
DEFAULT_SCOPE_ATTRIBUTES,
776808
)
777809

778810
metric = find_metric_by_name(scope_metrics[0], name)
@@ -824,7 +856,11 @@ def test_otel_gauge_record_multiple_values(self, test_agent: TestAgentAPI, test_
824856

825857
# Assert that the ScopeMetrics has the correct Scope, SchemaUrl, and Metrics data
826858
assert_scope_metric(
827-
scope_metrics[0], DEFAULT_METER_NAME, DEFAULT_METER_VERSION, DEFAULT_SCHEMA_URL, DEFAULT_SCOPE_ATTRIBUTES
859+
scope_metrics[0],
860+
DEFAULT_METER_NAME,
861+
DEFAULT_METER_VERSION,
862+
DEFAULT_SCHEMA_URL,
863+
DEFAULT_SCOPE_ATTRIBUTES,
828864
)
829865

830866
metric = find_metric_by_name(scope_metrics[0], name)
@@ -865,7 +901,11 @@ def test_otel_gauge_record_multiple_values_with_different_tags(
865901

866902
# Assert that the ScopeMetrics has the correct Scope, SchemaUrl, and Metrics data
867903
assert_scope_metric(
868-
scope_metrics[0], DEFAULT_METER_NAME, DEFAULT_METER_VERSION, DEFAULT_SCHEMA_URL, DEFAULT_SCOPE_ATTRIBUTES
904+
scope_metrics[0],
905+
DEFAULT_METER_NAME,
906+
DEFAULT_METER_VERSION,
907+
DEFAULT_SCHEMA_URL,
908+
DEFAULT_SCOPE_ATTRIBUTES,
869909
)
870910

871911
metric = find_metric_by_name(scope_metrics[0], name)
@@ -916,7 +956,11 @@ def test_otel_histogram_add_non_negative_and_negative_values(
916956

917957
# Assert that the ScopeMetrics has the correct Scope, SchemaUrl, and Metrics data
918958
assert_scope_metric(
919-
scope_metrics[0], DEFAULT_METER_NAME, DEFAULT_METER_VERSION, DEFAULT_SCHEMA_URL, DEFAULT_SCOPE_ATTRIBUTES
959+
scope_metrics[0],
960+
DEFAULT_METER_NAME,
961+
DEFAULT_METER_VERSION,
962+
DEFAULT_SCHEMA_URL,
963+
DEFAULT_SCOPE_ATTRIBUTES,
920964
)
921965

922966
metric = find_metric_by_name(scope_metrics[0], name)
@@ -970,7 +1014,11 @@ def test_otel_histogram_add_non_negative_values_with_different_tags(
9701014

9711015
# Assert that the ScopeMetrics has the correct Scope, SchemaUrl, and Metrics data
9721016
assert_scope_metric(
973-
scope_metrics[0], DEFAULT_METER_NAME, DEFAULT_METER_VERSION, DEFAULT_SCHEMA_URL, DEFAULT_SCOPE_ATTRIBUTES
1017+
scope_metrics[0],
1018+
DEFAULT_METER_NAME,
1019+
DEFAULT_METER_VERSION,
1020+
DEFAULT_SCHEMA_URL,
1021+
DEFAULT_SCOPE_ATTRIBUTES,
9741022
)
9751023

9761024
metric = find_metric_by_name(scope_metrics[0], name)
@@ -1023,7 +1071,11 @@ def test_otel_asynchronous_counter_constant_callback_value(
10231071

10241072
# Assert that the ScopeMetrics has the correct Scope, SchemaUrl, and Metrics data
10251073
assert_scope_metric(
1026-
scope_metrics[0], DEFAULT_METER_NAME, DEFAULT_METER_VERSION, DEFAULT_SCHEMA_URL, DEFAULT_SCOPE_ATTRIBUTES
1074+
scope_metrics[0],
1075+
DEFAULT_METER_NAME,
1076+
DEFAULT_METER_VERSION,
1077+
DEFAULT_SCHEMA_URL,
1078+
DEFAULT_SCOPE_ATTRIBUTES,
10271079
)
10281080

10291081
metric = find_metric_by_name(scope_metrics[0], name)
@@ -1060,7 +1112,11 @@ def test_otel_asynchronous_updowncounter_constant_callback_value(
10601112

10611113
# Assert that the ScopeMetrics has the correct Scope, SchemaUrl, and Metrics data
10621114
assert_scope_metric(
1063-
scope_metrics[0], DEFAULT_METER_NAME, DEFAULT_METER_VERSION, DEFAULT_SCHEMA_URL, DEFAULT_SCOPE_ATTRIBUTES
1115+
scope_metrics[0],
1116+
DEFAULT_METER_NAME,
1117+
DEFAULT_METER_VERSION,
1118+
DEFAULT_SCHEMA_URL,
1119+
DEFAULT_SCOPE_ATTRIBUTES,
10641120
)
10651121

10661122
metric = find_metric_by_name(scope_metrics[0], name)
@@ -1095,7 +1151,11 @@ def test_otel_asynchronous_gauge_constant_callback_value(self, test_agent: TestA
10951151

10961152
# Assert that the ScopeMetrics has the correct Scope, SchemaUrl, and Metrics data
10971153
assert_scope_metric(
1098-
scope_metrics[0], DEFAULT_METER_NAME, DEFAULT_METER_VERSION, DEFAULT_SCHEMA_URL, DEFAULT_SCOPE_ATTRIBUTES
1154+
scope_metrics[0],
1155+
DEFAULT_METER_NAME,
1156+
DEFAULT_METER_VERSION,
1157+
DEFAULT_SCHEMA_URL,
1158+
DEFAULT_SCOPE_ATTRIBUTES,
10991159
)
11001160

11011161
metric = find_metric_by_name(scope_metrics[0], name)
@@ -1106,7 +1166,6 @@ def test_otel_asynchronous_gauge_constant_callback_value(self, test_agent: TestA
11061166
@scenarios.parametric
11071167
@features.otel_metrics_api
11081168
@missing_feature(context.library == "cpp", reason="Not yet implemented", force_skip=True)
1109-
@missing_feature(context.library == "dotnet", reason="Not yet implemented", force_skip=True)
11101169
@missing_feature(context.library == "golang", reason="Not yet implemented", force_skip=True)
11111170
@missing_feature(context.library == "java", reason="Not yet implemented", force_skip=True)
11121171
@missing_feature(context.library == "php", reason="Not yet implemented", force_skip=True)
@@ -1299,7 +1358,6 @@ def test_otel_aggregation_temporality(
12991358
@scenarios.parametric
13001359
@features.otel_metrics_api
13011360
@missing_feature(context.library == "cpp", reason="Not yet implemented", force_skip=True)
1302-
@missing_feature(context.library == "dotnet", reason="Not yet implemented", force_skip=True)
13031361
@missing_feature(context.library == "golang", reason="Not yet implemented", force_skip=True)
13041362
@missing_feature(context.library == "java", reason="Not yet implemented", force_skip=True)
13051363
@missing_feature(context.library == "php", reason="Not yet implemented", force_skip=True)
@@ -1458,7 +1516,6 @@ def test_otlp_metrics_custom_endpoint_grpc(
14581516
@features.otel_metrics_api
14591517
@scenarios.parametric
14601518
@missing_feature(context.library == "cpp", reason="Not yet implemented", force_skip=True)
1461-
@missing_feature(context.library == "dotnet", reason="Not yet implemented", force_skip=True)
14621519
@missing_feature(context.library == "golang", reason="Not yet implemented", force_skip=True)
14631520
@missing_feature(context.library == "java", reason="Not yet implemented", force_skip=True)
14641521
@missing_feature(context.library == "php", reason="Not yet implemented", force_skip=True)
@@ -1541,7 +1598,6 @@ def test_custom_metrics_http_headers_included_in_otlp_export(
15411598
@features.otel_metrics_api
15421599
@scenarios.parametric
15431600
@missing_feature(context.library == "cpp", reason="Not yet implemented", force_skip=True)
1544-
@missing_feature(context.library == "dotnet", reason="Not yet implemented", force_skip=True)
15451601
@missing_feature(context.library == "golang", reason="Not yet implemented", force_skip=True)
15461602
@missing_feature(context.library == "java", reason="Not yet implemented", force_skip=True)
15471603
@missing_feature(context.library == "php", reason="Not yet implemented", force_skip=True)
@@ -1619,7 +1675,6 @@ def test_otlp_protocol_grpc(self, test_agent: TestAgentAPI, test_library: APMLib
16191675
@features.otel_metrics_api
16201676
@scenarios.parametric
16211677
@missing_feature(context.library == "cpp", reason="Not yet implemented", force_skip=True)
1622-
@missing_feature(context.library == "dotnet", reason="Not yet implemented", force_skip=True)
16231678
@missing_feature(context.library == "golang", reason="Not yet implemented", force_skip=True)
16241679
@missing_feature(context.library == "java", reason="Not yet implemented", force_skip=True)
16251680
@missing_feature(context.library == "nodejs", reason="Does not support DD_HOSTNAME")
@@ -1634,6 +1689,9 @@ class Test_Otel_Metrics_Host_Name:
16341689
- Resource attributes set through environment variable OTEL_RESOURCE_ATTRIBUTES are preserved
16351690
"""
16361691

1692+
@missing_feature(
1693+
context.library == "dotnet", reason="DD_HOSTNAME to host.name resource attribute mapping not yet implemented"
1694+
)
16371695
@pytest.mark.parametrize(
16381696
"library_env",
16391697
[
@@ -1721,7 +1779,6 @@ def test_hostname_omitted(self, test_agent: TestAgentAPI, test_library: APMLibra
17211779
@scenarios.parametric
17221780
@features.otel_metrics_api
17231781
@missing_feature(context.library == "cpp", reason="Not yet implemented", force_skip=True)
1724-
@missing_feature(context.library == "dotnet", reason="Not yet implemented", force_skip=True)
17251782
@missing_feature(context.library == "golang", reason="Not yet implemented", force_skip=True)
17261783
@missing_feature(context.library == "java", reason="Not yet implemented", force_skip=True)
17271784
@missing_feature(context.library == "php", reason="Not yet implemented", force_skip=True)
@@ -1873,7 +1930,6 @@ def test_dd_env_vars_override_otel(self, test_agent: TestAgentAPI, test_library:
18731930
@features.otel_metrics_api
18741931
@scenarios.parametric
18751932
@missing_feature(context.library == "cpp", reason="Not yet implemented", force_skip=True)
1876-
@missing_feature(context.library == "dotnet", reason="Not yet implemented", force_skip=True)
18771933
@missing_feature(context.library == "golang", reason="Not yet implemented", force_skip=True)
18781934
@missing_feature(context.library == "java", reason="Not yet implemented", force_skip=True)
18791935
@missing_feature(context.library == "php", reason="Not yet implemented", force_skip=True)
@@ -1913,7 +1969,6 @@ def test_telemetry_default_configurations(
19131969
configurations_by_name = test_agent.wait_for_telemetry_configurations()
19141970

19151971
for expected_env, expected_value in [
1916-
("OTEL_EXPORTER_OTLP_TIMEOUT", 10000),
19171972
("OTEL_EXPORTER_OTLP_METRICS_TIMEOUT", 10000),
19181973
("OTEL_METRIC_EXPORT_INTERVAL", 10000),
19191974
("OTEL_METRIC_EXPORT_TIMEOUT", 7500),
@@ -2038,6 +2093,10 @@ def test_telemetry_exporter_metrics_configurations(
20382093
f"Expected {expected_env} to be {expected_value}, configuration: {config}"
20392094
)
20402095

2096+
@missing_feature(
2097+
context.library == "dotnet",
2098+
reason="OTel metrics telemetry metrics (otel.metrics_export_attempts) not yet fully flushed in time",
2099+
)
20412100
@pytest.mark.parametrize(
20422101
"library_env",
20432102
[
@@ -2081,6 +2140,10 @@ def test_telemetry_metrics_http_protobuf(
20812140
assert "protocol:http" in metric.get("tags")
20822141
assert "encoding:protobuf" in metric.get("tags")
20832142

2143+
@missing_feature(
2144+
context.library == "dotnet",
2145+
reason="OTel metrics telemetry metrics (otel.metrics_export_attempts) not yet fully flushed in time",
2146+
)
20842147
@missing_feature(context.library == "nodejs", reason="Does not support grpc", force_skip=True)
20852148
@pytest.mark.parametrize(
20862149
"library_env",

0 commit comments

Comments
 (0)