22import pytest
33
44from utils import context , features , missing_feature , scenarios
5+
56from utils .docker_fixtures import TestAgentAPI
67from .conftest import APMLibrary
78
1011
1112DEFAULT_METER_NAME = "parametric-api"
1213DEFAULT_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
1517DEFAULT_INSTRUMENT_UNIT = "triggers"
1618DEFAULT_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
110114def 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(
135145def 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