From eba2e29f3d336a8f7743ea01271be4b0693a397b Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Wed, 22 Jul 2026 15:09:29 -0400 Subject: [PATCH 01/16] test(otlp-trace-metrics): address open review comments from PR #6834 Resolves the mechanically-actionable open threads from PR #6834: - FR02 mutual-exclusion test now also enables native stats computation explicitly, proving OTLP wins even when both mechanisms are turned on. - FR01 test renamed to test_fr01_5_disabled_when_tracing_is_disabled, plus a new test_fr01_6 covering DD_APM_TRACING_ENABLED=false. - span.kind casing is now tolerant of both "server" and "SERVER" pending spec finalization, via a new SPAN_KIND_SERVER_VALUES tuple. - FR06 resource/span-name test now also asserts datadog.resource.name is absent, matching the module's "in both modes" claim. Two open design questions (service.name repetition on data points, and the OTel-semantics-mode attribute set) are flagged with docstring notes rather than resolved, since they depend on the still-unfinalized OTEL spec (RFC SEMCON-1093). Co-Authored-By: Claude Sonnet 5 --- tests/parametric/test_otlp_trace_metrics.py | 67 +++++++++++++++++++-- 1 file changed, 62 insertions(+), 5 deletions(-) diff --git a/tests/parametric/test_otlp_trace_metrics.py b/tests/parametric/test_otlp_trace_metrics.py index 930a6b6210d..fbee8c96313 100644 --- a/tests/parametric/test_otlp_trace_metrics.py +++ b/tests/parametric/test_otlp_trace_metrics.py @@ -79,6 +79,10 @@ AGGREGATION_TEMPORALITY_DELTA: tuple[int, str] = (1, "AGGREGATION_TEMPORALITY_DELTA") # OTel StatusCode (span status) values that denote an error, across possible serializations. ERROR_STATUS_VALUES: tuple[Any, ...] = (2, "ERROR", "STATUS_CODE_ERROR") +# span.kind casing is still an open question pending OTEL spec finalization (RFC SEMCON-1093): +# OTel SpanKind enum names are upper-case ("SERVER"), but the OTel Span Metrics Connector emits +# lower-case ("server"). Accept both until the spec settles on one. +SPAN_KIND_SERVER_VALUES = ("server", "SERVER") # Expected telemetry.sdk.language resource-attribute value per system-tests library name. The Go # tracer reports the OTel-standard "go" token rather than the system-tests "golang" library name. _SDK_LANGUAGE_BY_LIBRARY = { @@ -347,7 +351,7 @@ def test_fr01_4_disabled_when_metrics_export_off( "library_env", [{**_BASE_ENVVARS, "OTEL_TRACES_EXPORTER": "none", "DD_METRICS_OTEL_ENABLED": "true"}], ) - def test_fr01_5_disabled_when_traces_exporter_not_otlp( + def test_fr01_5_disabled_when_tracing_is_disabled( self, otlp_trace_metrics_library_env: dict[str, str], # noqa: ARG002 test_agent: TestAgentAPI, @@ -371,6 +375,32 @@ def test_fr01_5_disabled_when_traces_exporter_not_otlp( ) time.sleep(0.2) + @pytest.mark.parametrize( + "library_env", + [{**DEFAULT_ENVVARS, "DD_APM_TRACING_ENABLED": "false"}], + ) + def test_fr01_6_disabled_when_apm_tracing_disabled( + self, + otlp_trace_metrics_library_env: dict[str, str], # noqa: ARG002 + test_agent: TestAgentAPI, + test_library: APMLibrary, + ): + """OTLP trace metrics stay disabled when APM tracing itself is disabled (APM standalone mode), + even though the OTLP metrics gates are otherwise satisfied. + """ + with test_library as t: + with t.dd_start_span(name="web.request", service=SERVICE, typestr="web"): + pass + t.dd_flush() + + deadline = time.monotonic() + 8 + while time.monotonic() < deadline: + metrics = test_agent.metrics() + assert not _duration_data_points(metrics), ( + f"OTLP trace metrics must be disabled when APM tracing is disabled, got: {_all_metric_names(metrics)}" + ) + time.sleep(0.2) + @scenarios.parametric @features.client_side_stats_supported @@ -421,7 +451,10 @@ def test_fr02_2_no_native_or_smc_metric_names( class Test_FR02_Mutual_Exclusion: """FR02: Trace metrics are exported in exactly one path (OTLP XOR native v0.6 stats).""" - @pytest.mark.parametrize("library_env", [{**DEFAULT_ENVVARS}]) + @pytest.mark.parametrize( + "library_env", + [{**DEFAULT_ENVVARS, "DD_TRACE_STATS_COMPUTATION_ENABLED": "1"}], + ) def test_fr02_3_otlp_suppresses_native_stats( self, otlp_trace_metrics_library_env: dict[str, str], # noqa: ARG002 @@ -430,6 +463,9 @@ def test_fr02_3_otlp_suppresses_native_stats( ): """With OTLP enabled: metrics go to /v1/metrics, no native v0.6 stats, and traces carry the Datadog-Client-Computed-Stats header so the Agent skips server-side stats computation. + + DD_TRACE_STATS_COMPUTATION_ENABLED is also set here to prove OTLP wins when both mechanisms + are explicitly enabled, not just when native stats computation is left at its default. """ with test_library as t: with t.dd_start_span(name="web.request", service=SERVICE, typestr="web"): @@ -621,6 +657,9 @@ def test_fr06_1_resource_span_name( metrics = test_agent.wait_for_num_otlp_metrics(num=1) attrs = _data_point_attrs(_duration_data_points(metrics)[0]) assert attrs.get("span.name") == "/users", f"Expected span.name=/users, got attrs: {attrs}" + assert "datadog.resource.name" not in attrs, ( + f"datadog.resource.name must be absent in Datadog-default mode: {attrs}" + ) @pytest.mark.parametrize("library_env", [{**DEFAULT_ENVVARS}]) def test_fr06_2_span_kind( @@ -637,7 +676,11 @@ def test_fr06_2_span_kind( metrics = test_agent.wait_for_num_otlp_metrics(num=1) attrs = _data_point_attrs(_duration_data_points(metrics)[0]) - assert attrs.get("span.kind") == "server", f"Expected span.kind=server, got attrs: {attrs}" + # Casing is intentionally tolerant pending OTEL spec finalization (RFC SEMCON-1093); see + # SPAN_KIND_SERVER_VALUES. + assert attrs.get("span.kind") in SPAN_KIND_SERVER_VALUES, ( + f"Expected span.kind in {SPAN_KIND_SERVER_VALUES}, got attrs: {attrs}" + ) @pytest.mark.parametrize("library_env", [{**DEFAULT_ENVVARS}]) def test_fr06_3_http_method( @@ -739,7 +782,14 @@ def test_fr06_8_status_code_error( @scenarios.parametric @features.client_side_stats_supported class Test_FR06_Otel_Resource_Attributes: - """FR06: Environment configuration maps to OTel resource attributes.""" + """FR06: Environment configuration maps to OTel resource attributes. + + OPEN QUESTION (PR #6834, pending OTEL spec finalization / RFC SEMCON-1093): test_fr06_9 and + test_fr06_14 below assert that a data point omits service.name when it matches the resource's + default service, mirroring "don't repeat what the resource already implies." Reviewers raised + whether SMC parity instead requires service.name on every data point regardless of whether it + matches the resource default. Left as-is pending spec guidance; do not change without it. + """ @pytest.mark.parametrize( "library_env", @@ -902,7 +952,14 @@ def test_fr06_13_telemetry_sdk_language( @scenarios.parametric @features.client_side_stats_supported class Test_FR07_Otel_Semantics_Mode: - """FR07: With DD_TRACE_OTEL_SEMANTICS_ENABLED=true, only OTel attributes are emitted.""" + """FR07: With DD_TRACE_OTEL_SEMANTICS_ENABLED=true, only OTel attributes are emitted. + + OPEN QUESTION (PR #6834, pending OTEL spec finalization / RFC SEMCON-1093): test_fr07_2 below + asserts datadog.resource.name, datadog.span.type, and datadog.operation.name are all absent in + OTel-semantics mode. Reviewers questioned whether OTel-semantics mode should still emit some + Datadog-only attributes that have no OTel equivalent (env, http.*, etc.) rather than restricting + to SMC's default attribute set. Left as-is pending spec guidance; do not change without it. + """ @pytest.mark.parametrize("library_env", [{**OTEL_SEMANTICS_ENVVARS}]) def test_fr07_1_no_datadog_attributes( From b48223e8cf2b62f2ece043abaf4cfb44748c495e Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Thu, 23 Jul 2026 14:30:24 -0400 Subject: [PATCH 02/16] test(otlp-trace-metrics): resolve open design questions with SMC-parity assertions Resolves the two open design questions from PR #6834 review with concrete decisions: service.name is now required on every data point, and OTel- semantics mode is restricted to exactly the Span Metrics Connector's default attribute set (service.name, span.name, span.kind, status.code). Also narrows ERROR_STATUS_VALUES and SPAN_KIND_SERVER_VALUES to the SMC's actual output format (STATUS_CODE_ERROR / SPAN_KIND_SERVER), based on reading the SMC connector source and traceutil.go directly rather than guessing at tolerant casing. Verified live against TEST_LIBRARY=python: 4 tests fail against the current Python implementation and are gated missing_feature in manifests/python.yml (fr01_6, fr06_2, fr06_8, fr09_2), in addition to the 3 already-known gates for the service.name/SMC-attrs requirements. --- manifests/nodejs.yml | 3 + manifests/python.yml | 7 ++ tests/parametric/test_otlp_trace_metrics.py | 86 +++++++++++---------- 3 files changed, 56 insertions(+), 40 deletions(-) diff --git a/manifests/nodejs.yml b/manifests/nodejs.yml index a36894eed25..a8ebcfa3378 100644 --- a/manifests/nodejs.yml +++ b/manifests/nodejs.yml @@ -2202,6 +2202,9 @@ manifest: tests/parametric/test_otlp_trace_metrics.py: *ref_5_111_0 tests/parametric/test_otlp_trace_metrics.py::Test_FR05_Sampling_Independence::test_fr05_1_metrics_computed_before_sampling: missing_feature (nodejs does not drop sampled-out traces when client-side stats are computed) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_10_hostname: irrelevant (DD_HOSTNAME is only supported in Python) + tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_14_custom_service_on_data_point: missing_feature (service.name is not yet always emitted on the data point; currently omitted when it matches the resource default) + tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_9_service_env_version: missing_feature (service.name is not yet always emitted on the data point; currently omitted when it matches the resource default) + tests/parametric/test_otlp_trace_metrics.py::Test_FR07_Otel_Semantics_Mode::test_fr07_3_only_smc_default_attributes: missing_feature (OTel-semantics mode still emits extra attributes beyond the Span Metrics Connector default set, e.g. http.* attributes) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags: 'missing_feature (DD_TAGS are not yet emitted as datadog. resource attributes; support coming in a future PR)' tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_5_top_level_child_different_service: missing_feature (nodejs does not tag child spans with a different service than their parent as top-level) tests/parametric/test_parametric_endpoints.py::TestRemoteConfigApplyEndpoint: incomplete_test_app (POST /trace/remote-config/apply only implemented in the python parametric app) diff --git a/manifests/python.yml b/manifests/python.yml index 988fa4263e7..981073249a1 100644 --- a/manifests/python.yml +++ b/manifests/python.yml @@ -2000,8 +2000,15 @@ manifest: tests/parametric/test_otel_span_with_baggage.py::Test_Otel_Span_With_Baggage: v2.18.0 tests/parametric/test_otel_tracer.py::Test_Otel_Tracer: v2.8.0 tests/parametric/test_otlp_trace_metrics.py: v4.13.0-dev + tests/parametric/test_otlp_trace_metrics.py::Test_FR01_Enablement_Configuration::test_fr01_6_disabled_when_apm_tracing_disabled: missing_feature (OTLP trace metrics are still emitted when DD_APM_TRACING_ENABLED=false) + tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_14_custom_service_on_data_point: missing_feature (service.name is not yet always emitted on the data point; currently omitted when it matches the resource default) + tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_9_service_env_version: missing_feature (service.name is not yet always emitted on the data point; currently omitted when it matches the resource default) + tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_2_span_kind: missing_feature (span.kind is emitted as the raw Datadog value, e.g. "server", not the SMC's SPAN_KIND_* convention) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_7_rpc_status_code: missing_feature (Requires libdatadog v38, will be added in future release) + tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_8_status_code_error: missing_feature (status.code is emitted as an integer, e.g. 2, not the SMC's STATUS_CODE_ERROR string convention) + tests/parametric/test_otlp_trace_metrics.py::Test_FR07_Otel_Semantics_Mode::test_fr07_3_only_smc_default_attributes: missing_feature (OTel-semantics mode still emits extra attributes beyond the Span Metrics Connector default set, e.g. http.* attributes) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags: 'missing_feature (DD_TAGS are not yet emitted as datadog. resource attributes; support coming in a future PR)' + tests/parametric/test_otlp_trace_metrics.py::Test_FR09_Red_Metric_Derivation::test_fr09_2_error_count: missing_feature (status.code is emitted as an integer, e.g. 2, not the SMC's STATUS_CODE_ERROR string convention, so the error span is not counted) tests/parametric/test_parametric_endpoints.py::Test_Parametric_DDTrace_Baggage: v2.16.0 tests/parametric/test_parametric_endpoints.py::Test_Parametric_FFE_Start: v4.0.0 tests/parametric/test_parametric_endpoints.py::Test_Parametric_Otel_Baggage: v2.16.0 diff --git a/tests/parametric/test_otlp_trace_metrics.py b/tests/parametric/test_otlp_trace_metrics.py index fbee8c96313..8a4e956f576 100644 --- a/tests/parametric/test_otlp_trace_metrics.py +++ b/tests/parametric/test_otlp_trace_metrics.py @@ -32,8 +32,8 @@ telemetry.sdk.language (the library's OTel language token, e.g. "go" for golang). * service.name, service.version and deployment.environment.name are reported as resource attributes (the configured default service). No InstrumentationScope is emitted (it would be redundant with - the telemetry.sdk.* resource attributes); a span whose service differs from the configured default - additionally carries service.name on its data point. + the telemetry.sdk.* resource attributes); every data point also carries service.name, matching + the OTel Span Metrics Connector's default dimensions (see SMC_DEFAULT_DATA_POINT_ATTRIBUTES). * OTLP metric flush/export cadence is fixed at 10s and is not overridable by OTEL_METRIC_EXPORT_INTERVAL. The internal _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL (milliseconds) shortens it in tests only. * Transport differs per library and is out of scope for parity: dd-trace-py exports HTTP/JSON only, @@ -41,7 +41,8 @@ Datadog span tags are translated to OTel semantic-convention attributes on the exported metric: grpc.status.code -> rpc.response.status_code, http.method -> http.request.method, -http.status_code -> http.response.status_code, and span.kind -> span.kind. host.name is reported when +http.status_code -> http.response.status_code, and span.kind -> span.kind (value re-cased to the +Span Metrics Connector's own convention, e.g. "server" -> "SPAN_KIND_SERVER"). host.name is reported when DD_TRACE_REPORT_HOSTNAME is enabled; its source is library-specific (libdatadog tracers honor DD_HOSTNAME, while dd-trace-js does not yet support DD_HOSTNAME and uses os.hostname()), so tests assert presence, not value. Process tags (DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED) are emitted as datadog. resource @@ -77,12 +78,20 @@ # as their string name (see https://protobuf.dev/programming-guides/json/), but parsers must also # accept the integer, so a standards-compliant OTLP/JSON exporter may emit either representation. AGGREGATION_TEMPORALITY_DELTA: tuple[int, str] = (1, "AGGREGATION_TEMPORALITY_DELTA") -# OTel StatusCode (span status) values that denote an error, across possible serializations. -ERROR_STATUS_VALUES: tuple[Any, ...] = (2, "ERROR", "STATUS_CODE_ERROR") -# span.kind casing is still an open question pending OTEL spec finalization (RFC SEMCON-1093): -# OTel SpanKind enum names are upper-case ("SERVER"), but the OTel Span Metrics Connector emits -# lower-case ("server"). Accept both until the spec settles on one. -SPAN_KIND_SERVER_VALUES = ("server", "SERVER") +# The OTel Span Metrics Connector's default status.code dimension is always this exact string: +# traceutil.StatusCodeStr() maps StatusCodeError -> "STATUS_CODE_ERROR" and writes it via +# attr.PutStr (always a string, never an int). The bare "ERROR" value only appears under a +# different, feature-gated key (otel.status_code), not status.code, so it does not apply here. +# See https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/connector/spanmetricsconnector/connector.go +ERROR_STATUS_VALUES: tuple[str, ...] = ("STATUS_CODE_ERROR",) +# The OTel Span Metrics Connector's default span.kind dimension is always this exact string: +# traceutil.SpanKindStr() maps SpanKindServer -> "SPAN_KIND_SERVER" and writes it via attr.PutStr. +# See https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/internal/coreinternal/traceutil/traceutil.go +SPAN_KIND_SERVER_VALUES: tuple[str, ...] = ("SPAN_KIND_SERVER",) +# The OTel Span Metrics Connector's default dimensions (service.name, span.name, span.kind, +# status.code); collector.instance.id and otel.status_code are feature-gated and off by default. +# OTel-semantics mode must not emit data-point attributes beyond this set (FR07). +SMC_DEFAULT_DATA_POINT_ATTRIBUTES = frozenset({"service.name", "span.name", "span.kind", "status.code"}) # Expected telemetry.sdk.language resource-attribute value per system-tests library name. The Go # tracer reports the OTel-standard "go" token rather than the system-tests "golang" library name. _SDK_LANGUAGE_BY_LIBRARY = { @@ -676,8 +685,7 @@ def test_fr06_2_span_kind( metrics = test_agent.wait_for_num_otlp_metrics(num=1) attrs = _data_point_attrs(_duration_data_points(metrics)[0]) - # Casing is intentionally tolerant pending OTEL spec finalization (RFC SEMCON-1093); see - # SPAN_KIND_SERVER_VALUES. + # SMC parity: the value matches traceutil.SpanKindStr(), not the raw Datadog span.kind tag. assert attrs.get("span.kind") in SPAN_KIND_SERVER_VALUES, ( f"Expected span.kind in {SPAN_KIND_SERVER_VALUES}, got attrs: {attrs}" ) @@ -784,11 +792,9 @@ def test_fr06_8_status_code_error( class Test_FR06_Otel_Resource_Attributes: """FR06: Environment configuration maps to OTel resource attributes. - OPEN QUESTION (PR #6834, pending OTEL spec finalization / RFC SEMCON-1093): test_fr06_9 and - test_fr06_14 below assert that a data point omits service.name when it matches the resource's - default service, mirroring "don't repeat what the resource already implies." Reviewers raised - whether SMC parity instead requires service.name on every data point regardless of whether it - matches the resource default. Left as-is pending spec guidance; do not change without it. + service.name is a required data-point attribute on every data point, matching the OTel Span + Metrics Connector, which always attaches it as one of its default dimensions regardless of + whether it matches the resource's default service (PR #6834 review discussion). """ @pytest.mark.parametrize( @@ -802,8 +808,8 @@ def test_fr06_9_service_env_version( test_library: APMLibrary, ): """DD_SERVICE / DD_ENV / DD_VERSION map to the resource attributes service.name / - deployment.environment.name / service.version; the span uses the default service so its data - point omits service.name. + deployment.environment.name / service.version; service.name is also required on the span's + data point, matching the resource's default service. """ with test_library as t: with t.dd_start_span(name="web.request", service=SERVICE, typestr="web"): @@ -822,9 +828,10 @@ def test_fr06_9_service_env_version( or resource_attrs.get("deployment.environment.name") == "prod" ), f"Expected deployment environment=prod, got: {resource_attrs}" - # The span uses the configured default service, so its data point omits service.name. - assert SERVICE not in _data_point_services(metrics), ( - f"Default service must not repeat on data points: {_data_point_services(metrics)}" + # service.name is a required data-point attribute (SMC parity), even though it matches the + # resource's default service. + assert SERVICE in _data_point_services(metrics), ( + f"Expected service.name={SERVICE} on the data point: {_data_point_services(metrics)}" ) @pytest.mark.parametrize("library_env", [{**DEFAULT_ENVVARS}]) @@ -834,10 +841,10 @@ def test_fr06_14_custom_service_on_data_point( test_agent: TestAgentAPI, test_library: APMLibrary, ): - """A span whose service matches the configured default omits service.name on its data point - (it is implied by the resource); a span on a different service carries service.name on its - data point. Two root spans are used so both are top-level and therefore selected by the - client-side stats pipeline in every library. + """Both a span whose service matches the configured default and a span on a different + service carry service.name on their data point (SMC parity). Two root spans are used so + both are top-level and therefore selected by the client-side stats pipeline in every + library. """ with test_library as t: with t.dd_start_span(name="web.request", service=SERVICE, typestr="web"): @@ -852,13 +859,11 @@ def test_fr06_14_custom_service_on_data_point( f"Expected resource service.name={SERVICE}, got: {_resource_attributes(metrics)}" ) services_on_points = _data_point_services(metrics) - # The custom service is carried on its own data point; the default service is not repeated. + # Both the custom service and the default service carry service.name on their own data point. assert "postgres" in services_on_points, ( f"Expected postgres service.name on its data point: {services_on_points}" ) - assert SERVICE not in services_on_points, ( - f"Default service must not repeat on data points: {services_on_points}" - ) + assert SERVICE in services_on_points, f"Expected {SERVICE} service.name on its data point: {services_on_points}" @pytest.mark.parametrize( "library_env", @@ -952,13 +957,10 @@ def test_fr06_13_telemetry_sdk_language( @scenarios.parametric @features.client_side_stats_supported class Test_FR07_Otel_Semantics_Mode: - """FR07: With DD_TRACE_OTEL_SEMANTICS_ENABLED=true, only OTel attributes are emitted. - - OPEN QUESTION (PR #6834, pending OTEL spec finalization / RFC SEMCON-1093): test_fr07_2 below - asserts datadog.resource.name, datadog.span.type, and datadog.operation.name are all absent in - OTel-semantics mode. Reviewers questioned whether OTel-semantics mode should still emit some - Datadog-only attributes that have no OTel equivalent (env, http.*, etc.) rather than restricting - to SMC's default attribute set. Left as-is pending spec guidance; do not change without it. + """FR07: With DD_TRACE_OTEL_SEMANTICS_ENABLED=true, data points carry only the OTel Span + Metrics Connector's default attribute set (see SMC_DEFAULT_DATA_POINT_ATTRIBUTES); no + datadog.*-prefixed attributes and no additional OTel semantic-convention attributes beyond + that default set (e.g. http.*) are emitted (PR #6834 review discussion). """ @pytest.mark.parametrize("library_env", [{**OTEL_SEMANTICS_ENVVARS}]) @@ -1000,13 +1002,15 @@ def test_fr07_2_no_datadog_resource_or_type( assert "datadog.operation.name" not in attrs, f"datadog.operation.name must be absent: {attrs}" @pytest.mark.parametrize("library_env", [{**OTEL_SEMANTICS_ENVVARS}]) - def test_fr07_3_otel_attributes_present( + def test_fr07_3_only_smc_default_attributes( self, otlp_trace_metrics_library_env: dict[str, str], # noqa: ARG002 test_agent: TestAgentAPI, test_library: APMLibrary, ): - """OTel semantic-convention attributes are still emitted in OTel-semantics mode.""" + """OTel semantic-convention attributes beyond the SMC default set (e.g. http.*) are not + emitted in OTel-semantics mode, even though they are set on the span. + """ with test_library as t: with t.dd_start_span(name="web.request", service=SERVICE, typestr="web") as span: span.set_meta("http.method", "GET") @@ -1015,8 +1019,10 @@ def test_fr07_3_otel_attributes_present( metrics = test_agent.wait_for_num_otlp_metrics(num=1) attrs = _data_point_attrs(_duration_data_points(metrics)[0]) - assert attrs.get("http.request.method") == "GET", f"Expected http.request.method=GET, got attrs: {attrs}" - assert attrs.get("http.route") == "/users/{id}", f"Expected http.route=/users/{{id}}, got attrs: {attrs}" + assert "http.request.method" not in attrs, f"http.request.method must be absent: {attrs}" + assert "http.route" not in attrs, f"http.route must be absent: {attrs}" + extra_keys = set(attrs) - SMC_DEFAULT_DATA_POINT_ATTRIBUTES + assert not extra_keys, f"Attributes beyond the SMC default set must be absent: {extra_keys}" @pytest.mark.parametrize( "library_env", From 6001f0025fdd2a7941de2a0d4d959e7f9f05c244 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Tue, 4 Aug 2026 13:09:44 -0400 Subject: [PATCH 03/16] test(otlp-trace-metrics): align with updated peer/process tags and is_trace_root spec Reverts the SMC-parity restriction on OTel-semantics-mode attributes and the unconditional service.name-on-every-data-point requirement from #7363, since the RFC does not require either. Adds coverage for datadog.is_trace_root and datadog.peer_tags (skipped pending test-agent support for a peer_tags allowlist), and rewrites the process-tags test for the single datadog.process_tags list-attribute shape. Drops the DD_TAGS / OTEL_RESOURCE_ATTRIBUTES tests, which are now out of scope, along with their now-stale manifest gates. Co-Authored-By: Claude Sonnet 5 --- manifests/golang.yml | 1 - manifests/java.yml | 1 - manifests/nodejs.yml | 6 +- manifests/python.yml | 4 - tests/parametric/test_otlp_trace_metrics.py | 192 ++++++++++---------- 5 files changed, 93 insertions(+), 111 deletions(-) diff --git a/manifests/golang.yml b/manifests/golang.yml index a7a3cdf892e..729de7f7c05 100644 --- a/manifests/golang.yml +++ b/manifests/golang.yml @@ -1472,7 +1472,6 @@ manifest: : "irrelevant (\"Go tracer decided to always set _dd1.sr.eausr: 1 for truthy analytics.event inputs, else 0\")" tests/parametric/test_otel_span_with_baggage.py::Test_Otel_Span_With_Baggage: missing_feature tests/parametric/test_otlp_trace_metrics.py: missing_feature - tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags: 'missing_feature (DD_TAGS are not yet emitted as datadog. resource attributes; support coming in a future PR)' tests/parametric/test_parametric_endpoints.py::TestRemoteConfigApplyEndpoint: incomplete_test_app (POST /trace/remote-config/apply only implemented in the python parametric app) tests/parametric/test_parametric_endpoints.py::Test_Parametric_DDSpan_Add_Link: missing_feature (add_link endpoint is not implemented) tests/parametric/test_parametric_endpoints.py::Test_Parametric_DDSpan_Set_Resource: missing_feature (does not support setting a resource name after span creation) diff --git a/manifests/java.yml b/manifests/java.yml index edf50677c2e..a8c6652fb1d 100644 --- a/manifests/java.yml +++ b/manifests/java.yml @@ -3928,7 +3928,6 @@ manifest: - declaration: missing_feature (OTel resource naming implemented in 1.24.0) component_version: <=1.23.0 tests/parametric/test_otlp_trace_metrics.py: missing_feature - tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags: 'missing_feature (DD_TAGS are not yet emitted as datadog. resource attributes; support coming in a future PR)' tests/parametric/test_parametric_endpoints.py::TestRemoteConfigApplyEndpoint: incomplete_test_app (POST /trace/remote-config/apply only implemented in the python parametric app) tests/parametric/test_parametric_endpoints.py::Test_Parametric_DDSpan_Add_Link: incomplete_test_app (add_link endpoint is not implemented) tests/parametric/test_parametric_endpoints.py::Test_Parametric_DDTrace_Baggage: # Modified by easy win activation script diff --git a/manifests/nodejs.yml b/manifests/nodejs.yml index a8ebcfa3378..62dce43e838 100644 --- a/manifests/nodejs.yml +++ b/manifests/nodejs.yml @@ -2202,11 +2202,9 @@ manifest: tests/parametric/test_otlp_trace_metrics.py: *ref_5_111_0 tests/parametric/test_otlp_trace_metrics.py::Test_FR05_Sampling_Independence::test_fr05_1_metrics_computed_before_sampling: missing_feature (nodejs does not drop sampled-out traces when client-side stats are computed) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_10_hostname: irrelevant (DD_HOSTNAME is only supported in Python) - tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_14_custom_service_on_data_point: missing_feature (service.name is not yet always emitted on the data point; currently omitted when it matches the resource default) - tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_9_service_env_version: missing_feature (service.name is not yet always emitted on the data point; currently omitted when it matches the resource default) - tests/parametric/test_otlp_trace_metrics.py::Test_FR07_Otel_Semantics_Mode::test_fr07_3_only_smc_default_attributes: missing_feature (OTel-semantics mode still emits extra attributes beyond the Span Metrics Connector default set, e.g. http.* attributes) - tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags: 'missing_feature (DD_TAGS are not yet emitted as datadog. resource attributes; support coming in a future PR)' + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_13_is_trace_root: missing_feature (datadog.is_trace_root is not yet emitted on OTLP trace-metric data points) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_5_top_level_child_different_service: missing_feature (nodejs does not tag child spans with a different service than their parent as top-level) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_8_process_tags: missing_feature (process tags are still emitted as individual datadog. resource attributes, not the single datadog.process_tags list attribute) tests/parametric/test_parametric_endpoints.py::TestRemoteConfigApplyEndpoint: incomplete_test_app (POST /trace/remote-config/apply only implemented in the python parametric app) tests/parametric/test_parametric_endpoints.py::Test_Parametric_DDSpan_Set_Resource: incomplete_test_app (set_resource endpoint is not implemented) tests/parametric/test_parametric_endpoints.py::Test_Parametric_DDSpan_Start: bug (APMAPI-778) # The resource name of the child span is overidden by the parent span. diff --git a/manifests/python.yml b/manifests/python.yml index 981073249a1..2a5a4d502d1 100644 --- a/manifests/python.yml +++ b/manifests/python.yml @@ -2001,13 +2001,9 @@ manifest: tests/parametric/test_otel_tracer.py::Test_Otel_Tracer: v2.8.0 tests/parametric/test_otlp_trace_metrics.py: v4.13.0-dev tests/parametric/test_otlp_trace_metrics.py::Test_FR01_Enablement_Configuration::test_fr01_6_disabled_when_apm_tracing_disabled: missing_feature (OTLP trace metrics are still emitted when DD_APM_TRACING_ENABLED=false) - tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_14_custom_service_on_data_point: missing_feature (service.name is not yet always emitted on the data point; currently omitted when it matches the resource default) - tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_9_service_env_version: missing_feature (service.name is not yet always emitted on the data point; currently omitted when it matches the resource default) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_2_span_kind: missing_feature (span.kind is emitted as the raw Datadog value, e.g. "server", not the SMC's SPAN_KIND_* convention) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_7_rpc_status_code: missing_feature (Requires libdatadog v38, will be added in future release) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_8_status_code_error: missing_feature (status.code is emitted as an integer, e.g. 2, not the SMC's STATUS_CODE_ERROR string convention) - tests/parametric/test_otlp_trace_metrics.py::Test_FR07_Otel_Semantics_Mode::test_fr07_3_only_smc_default_attributes: missing_feature (OTel-semantics mode still emits extra attributes beyond the Span Metrics Connector default set, e.g. http.* attributes) - tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags: 'missing_feature (DD_TAGS are not yet emitted as datadog. resource attributes; support coming in a future PR)' tests/parametric/test_otlp_trace_metrics.py::Test_FR09_Red_Metric_Derivation::test_fr09_2_error_count: missing_feature (status.code is emitted as an integer, e.g. 2, not the SMC's STATUS_CODE_ERROR string convention, so the error span is not counted) tests/parametric/test_parametric_endpoints.py::Test_Parametric_DDTrace_Baggage: v2.16.0 tests/parametric/test_parametric_endpoints.py::Test_Parametric_FFE_Start: v4.0.0 diff --git a/tests/parametric/test_otlp_trace_metrics.py b/tests/parametric/test_otlp_trace_metrics.py index 8a4e956f576..6b3d681a52b 100644 --- a/tests/parametric/test_otlp_trace_metrics.py +++ b/tests/parametric/test_otlp_trace_metrics.py @@ -15,7 +15,7 @@ Test_FR06_Otel_Resource_Attributes FR07 DD_TRACE_OTEL_SEMANTICS_ENABLED=true -> only OTel attributes Test_FR07_Otel_Semantics_Mode FR08 DD_TRACE_OTEL_SEMANTICS_ENABLED=false (default) -> datadog.* allowed Test_FR08_Datadog_Attributes, - DD_TAGS / OTEL_RESOURCE_ATTRIBUTES / DD_TRACE_STATS_ADDITIONAL_TAGS Test_FR08_AdditionalTags + DD_TRACE_STATS_ADDITIONAL_TAGS (additional_metric_tags) Test_FR08_AdditionalTags FR09 Derive request/span count, error count, and duration Test_FR09_Red_Metric_Derivation FR10 Transport over OTLP HTTP/JSON (set in _BASE_ENVVARS, exercised by every test) FR11 SDKs without client-side stats are out of scope (handled by manifests / @features gating) @@ -32,8 +32,9 @@ telemetry.sdk.language (the library's OTel language token, e.g. "go" for golang). * service.name, service.version and deployment.environment.name are reported as resource attributes (the configured default service). No InstrumentationScope is emitted (it would be redundant with - the telemetry.sdk.* resource attributes); every data point also carries service.name, matching - the OTel Span Metrics Connector's default dimensions (see SMC_DEFAULT_DATA_POINT_ATTRIBUTES). + the telemetry.sdk.* resource attributes); a span whose service differs from the configured default + additionally carries service.name on its data point. Whether it is also repeated on a data point + whose service matches the default is not asserted (implementations differ; not required either way). * OTLP metric flush/export cadence is fixed at 10s and is not overridable by OTEL_METRIC_EXPORT_INTERVAL. The internal _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL (milliseconds) shortens it in tests only. * Transport differs per library and is out of scope for parity: dd-trace-py exports HTTP/JSON only, @@ -45,9 +46,14 @@ Span Metrics Connector's own convention, e.g. "server" -> "SPAN_KIND_SERVER"). host.name is reported when DD_TRACE_REPORT_HOSTNAME is enabled; its source is library-specific (libdatadog tracers honor DD_HOSTNAME, while dd-trace-js does not yet support DD_HOSTNAME and uses os.hostname()), so tests assert presence, not value. -Process tags (DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED) are emitted as datadog. resource -attributes in default mode (enabled by default in some SDKs). Status-code and boolean metric attributes -must be typed OTLP values (intValue / boolValue); _dd.stats_computed is a string-valued Datadog convention. +Process tags (DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED) surface as the single resource attribute +datadog.process_tags: an arrayValue of colon-joined "key:value" strings (enabled by default in some SDKs). +Peer tags surface the same way as the data-point attribute datadog.peer_tags. This is the same shape as +additional_metric_tags (see Test_FR08_AdditionalTags) -- do not flatten either of these +into per-key datadog. attributes. datadog.is_trace_root is a boolean data-point attribute, true only +for the span whose ParentID == 0; it is distinct from datadog.span.top_level (the service-entry marker), +which a non-root child span can also carry. Status-code and boolean metric attributes must be typed OTLP +values (intValue / boolValue); _dd.stats_computed is a string-valued Datadog convention. """ import base64 @@ -88,10 +94,6 @@ # traceutil.SpanKindStr() maps SpanKindServer -> "SPAN_KIND_SERVER" and writes it via attr.PutStr. # See https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/internal/coreinternal/traceutil/traceutil.go SPAN_KIND_SERVER_VALUES: tuple[str, ...] = ("SPAN_KIND_SERVER",) -# The OTel Span Metrics Connector's default dimensions (service.name, span.name, span.kind, -# status.code); collector.instance.id and otel.status_code are feature-gated and off by default. -# OTel-semantics mode must not emit data-point attributes beyond this set (FR07). -SMC_DEFAULT_DATA_POINT_ATTRIBUTES = frozenset({"service.name", "span.name", "span.kind", "status.code"}) # Expected telemetry.sdk.language resource-attribute value per system-tests library name. The Go # tracer reports the OTel-standard "go" token rather than the system-tests "golang" library name. _SDK_LANGUAGE_BY_LIBRARY = { @@ -105,8 +107,8 @@ "rust": "rust", "cpp": "cpp", } -# Known process-tag keys; any one emitted as a datadog. resource attribute satisfies FR08. Which tags -# are populated varies per library/runtime, so the test only requires that at least one is present. +# Known process-tag keys; any one appearing inside datadog.process_tags (as "key:value") satisfies FR08. +# Which tags are populated varies per library/runtime, so the test only requires one known key present. _PROCESS_TAG_KEYS = ( "entrypoint.name", "entrypoint.workdir", @@ -790,12 +792,7 @@ def test_fr06_8_status_code_error( @scenarios.parametric @features.client_side_stats_supported class Test_FR06_Otel_Resource_Attributes: - """FR06: Environment configuration maps to OTel resource attributes. - - service.name is a required data-point attribute on every data point, matching the OTel Span - Metrics Connector, which always attaches it as one of its default dimensions regardless of - whether it matches the resource's default service (PR #6834 review discussion). - """ + """FR06: Environment configuration maps to OTel resource attributes.""" @pytest.mark.parametrize( "library_env", @@ -808,8 +805,8 @@ def test_fr06_9_service_env_version( test_library: APMLibrary, ): """DD_SERVICE / DD_ENV / DD_VERSION map to the resource attributes service.name / - deployment.environment.name / service.version; service.name is also required on the span's - data point, matching the resource's default service. + deployment.environment.name / service.version. The span uses the default service, so whether + its data point also repeats service.name is not asserted here (implementations differ). """ with test_library as t: with t.dd_start_span(name="web.request", service=SERVICE, typestr="web"): @@ -828,12 +825,6 @@ def test_fr06_9_service_env_version( or resource_attrs.get("deployment.environment.name") == "prod" ), f"Expected deployment environment=prod, got: {resource_attrs}" - # service.name is a required data-point attribute (SMC parity), even though it matches the - # resource's default service. - assert SERVICE in _data_point_services(metrics), ( - f"Expected service.name={SERVICE} on the data point: {_data_point_services(metrics)}" - ) - @pytest.mark.parametrize("library_env", [{**DEFAULT_ENVVARS}]) def test_fr06_14_custom_service_on_data_point( self, @@ -841,10 +832,9 @@ def test_fr06_14_custom_service_on_data_point( test_agent: TestAgentAPI, test_library: APMLibrary, ): - """Both a span whose service matches the configured default and a span on a different - service carry service.name on their data point (SMC parity). Two root spans are used so - both are top-level and therefore selected by the client-side stats pipeline in every - library. + """A span on a different service than the configured default carries service.name on its + data point (it is not implied by the resource). Two root spans are used so both are + top-level and therefore selected by the client-side stats pipeline in every library. """ with test_library as t: with t.dd_start_span(name="web.request", service=SERVICE, typestr="web"): @@ -859,11 +849,11 @@ def test_fr06_14_custom_service_on_data_point( f"Expected resource service.name={SERVICE}, got: {_resource_attributes(metrics)}" ) services_on_points = _data_point_services(metrics) - # Both the custom service and the default service carry service.name on their own data point. + # The custom service is carried on its own data point. Whether the default service is also + # repeated on its own data point is not asserted (implementations differ; not required either way). assert "postgres" in services_on_points, ( f"Expected postgres service.name on its data point: {services_on_points}" ) - assert SERVICE in services_on_points, f"Expected {SERVICE} service.name on its data point: {services_on_points}" @pytest.mark.parametrize( "library_env", @@ -957,11 +947,7 @@ def test_fr06_13_telemetry_sdk_language( @scenarios.parametric @features.client_side_stats_supported class Test_FR07_Otel_Semantics_Mode: - """FR07: With DD_TRACE_OTEL_SEMANTICS_ENABLED=true, data points carry only the OTel Span - Metrics Connector's default attribute set (see SMC_DEFAULT_DATA_POINT_ATTRIBUTES); no - datadog.*-prefixed attributes and no additional OTel semantic-convention attributes beyond - that default set (e.g. http.*) are emitted (PR #6834 review discussion). - """ + """FR07: With DD_TRACE_OTEL_SEMANTICS_ENABLED=true, only OTel attributes are emitted.""" @pytest.mark.parametrize("library_env", [{**OTEL_SEMANTICS_ENVVARS}]) def test_fr07_1_no_datadog_attributes( @@ -1002,15 +988,13 @@ def test_fr07_2_no_datadog_resource_or_type( assert "datadog.operation.name" not in attrs, f"datadog.operation.name must be absent: {attrs}" @pytest.mark.parametrize("library_env", [{**OTEL_SEMANTICS_ENVVARS}]) - def test_fr07_3_only_smc_default_attributes( + def test_fr07_3_otel_attributes_present( self, otlp_trace_metrics_library_env: dict[str, str], # noqa: ARG002 test_agent: TestAgentAPI, test_library: APMLibrary, ): - """OTel semantic-convention attributes beyond the SMC default set (e.g. http.*) are not - emitted in OTel-semantics mode, even though they are set on the span. - """ + """OTel semantic-convention attributes are still emitted in OTel-semantics mode.""" with test_library as t: with t.dd_start_span(name="web.request", service=SERVICE, typestr="web") as span: span.set_meta("http.method", "GET") @@ -1019,10 +1003,8 @@ def test_fr07_3_only_smc_default_attributes( metrics = test_agent.wait_for_num_otlp_metrics(num=1) attrs = _data_point_attrs(_duration_data_points(metrics)[0]) - assert "http.request.method" not in attrs, f"http.request.method must be absent: {attrs}" - assert "http.route" not in attrs, f"http.route must be absent: {attrs}" - extra_keys = set(attrs) - SMC_DEFAULT_DATA_POINT_ATTRIBUTES - assert not extra_keys, f"Attributes beyond the SMC default set must be absent: {extra_keys}" + assert attrs.get("http.request.method") == "GET", f"Expected http.request.method=GET, got attrs: {attrs}" + assert attrs.get("http.route") == "/users/{id}", f"Expected http.route=/users/{{id}}, got attrs: {attrs}" @pytest.mark.parametrize( "library_env", @@ -1204,11 +1186,12 @@ def test_fr08_8_process_tags( test_agent: TestAgentAPI, test_library: APMLibrary, ): - """Process tags are emitted as individual datadog. resource attributes in default mode. + """Process tags surface as the single resource attribute datadog.process_tags: an arrayValue + of colon-joined "key:value" strings (same shape as additional_metric_tags; + see test_fr08_12_stats_additional_tags), not one datadog. attribute per tag. - The comma-separated key:value process-tag string is split and each key is prefixed with datadog. and - emitted as a resource attribute. Which process tags are populated varies per library/runtime, so - the assertion only requires that at least one known process tag is present as a datadog. attribute. + Which process tags are populated varies per library/runtime, so the assertion only requires + that at least one known process-tag key is present inside datadog.process_tags. """ with test_library as t: with t.dd_start_span(name="web.request", service=SERVICE, typestr="web"): @@ -1216,9 +1199,9 @@ def test_fr08_8_process_tags( t.dd_flush() metrics = test_agent.wait_for_num_otlp_metrics(num=1) - resource_attrs = _resource_attributes(metrics) - assert any(f"datadog.{tag}" in resource_attrs for tag in _PROCESS_TAG_KEYS), ( - f"Expected at least one datadog. resource attribute, got: {list(resource_attrs)}" + process_tags = _resource_attributes(metrics).get("datadog.process_tags") or [] + assert any(str(entry).split(":", 1)[0] in _PROCESS_TAG_KEYS for entry in process_tags), ( + f"Expected a known process-tag key inside datadog.process_tags, got: {process_tags}" ) @pytest.mark.parametrize("library_env", [{**DEFAULT_ENVVARS}]) @@ -1252,78 +1235,82 @@ def test_fr08_9_top_level_not_mixed_with_measured( f"point, got: {[_data_point_attrs(dp) for dp in points]}" ) - -@scenarios.parametric -@features.client_side_stats_supported -class Test_FR08_AdditionalTags: - """FR08: DD_TAGS (tracer_dd_tags) / OTEL_RESOURCE_ATTRIBUTES surface as resource attributes and - DD_TRACE_STATS_ADDITIONAL_TAGS (additional_metric_tags) as data-point attributes (support pending in some SDKs). - """ - - @pytest.mark.parametrize( - "library_env", - [ - { - **DEFAULT_ENVVARS, - "DD_TAGS": ( - "team:apm,tier:backend," - "service:ignored-svc,env:ignored-env,version:ignored-ver," - "runtime_id:ignored-rid,runtime-id:ignored-rid2" - ), - } - ], - ) - def test_fr08_10_dd_tags_resource_attributes( + @pytest.mark.parametrize("library_env", [{**DEFAULT_ENVVARS}]) + def test_fr08_13_is_trace_root( self, otlp_trace_metrics_library_env: dict[str, str], # noqa: ARG002 test_agent: TestAgentAPI, test_library: APMLibrary, ): - """Global DD_TAGS surface as the tracer_dd_tags resource-attribute container (repeated key:value - strings) in default mode; reserved service/env/version/runtime_id/runtime-id keys are ignored. + """datadog.is_trace_root is true only for the span whose ParentID == 0, distinct from + datadog.span.top_level: a different-service child is a service-entry span (top_level=true) + but is not the trace root (is_trace_root=false). """ with test_library as t: - with t.dd_start_span(name="web.request", service=SERVICE, typestr="web"): - pass + with ( + t.dd_start_span(name="web.request", service=SERVICE, typestr="web") as root, + t.dd_start_span(name="child.op", service=SERVICE, parent_id=root.span_id) as same_service_child, + t.dd_start_span(name="postgres.query", service="postgres", parent_id=root.span_id), + ): + same_service_child.set_metric(SPAN_MEASURED_KEY, 1) t.dd_flush() metrics = test_agent.wait_for_num_otlp_metrics(num=1) - resource_attrs = _resource_attributes(metrics) - tracer_dd_tags = resource_attrs.get("tracer_dd_tags") or [] - assert "team:apm" in tracer_dd_tags, f"Expected team:apm in tracer_dd_tags, got: {resource_attrs}" - assert "tier:backend" in tracer_dd_tags, f"Expected tier:backend in tracer_dd_tags, got: {resource_attrs}" - for reserved in ("service", "env", "version", "runtime_id", "runtime-id"): - assert not any(str(entry).startswith(f"{reserved}:") for entry in tracer_dd_tags), ( - f"Reserved DD_TAGS key {reserved!r} must be ignored, got: {tracer_dd_tags}" - ) - assert resource_attrs.get("service.name") == SERVICE, ( - f"DD_TAGS service must not override configured service.name={SERVICE}, got: {resource_attrs}" + points = _duration_data_points(metrics) + + root_point = _find_data_point(points, **{"datadog.operation.name": "web.request"}) + assert root_point is not None, "No data point for the root span" + assert _data_point_attrs(root_point).get("datadog.is_trace_root") is True, ( + f"Expected datadog.is_trace_root=true on the root span: {_data_point_attrs(root_point)}" ) - @pytest.mark.parametrize( - "library_env", - [{**DEFAULT_ENVVARS, "OTEL_RESOURCE_ATTRIBUTES": "team=apm,deployment.region=us-east-1"}], + same_service_point = _find_data_point(points, **{"datadog.operation.name": "child.op"}) + assert same_service_point is not None, "No data point for the same-service child span" + assert _data_point_attrs(same_service_point).get("datadog.is_trace_root") is False, ( + f"Expected datadog.is_trace_root=false on a non-root child: {_data_point_attrs(same_service_point)}" + ) + + service_entry_point = _find_data_point(points, **{"datadog.operation.name": "postgres.query"}) + assert service_entry_point is not None, "No data point for the service-entry child span" + service_entry_attrs = _data_point_attrs(service_entry_point) + assert service_entry_attrs.get("datadog.span.top_level") is True, ( + f"Expected datadog.span.top_level=true on the service-entry child: {service_entry_attrs}" + ) + assert service_entry_attrs.get("datadog.is_trace_root") is False, ( + f"A service-entry child is not the trace root; expected datadog.is_trace_root=false: {service_entry_attrs}" + ) + + @pytest.mark.skip( + reason="Blocked on test-agent support: the ddapm-test-agent /info response has no mechanism " + "today to advertise a peer_tags allowlist, which client-side stats peer-tag aggregation " + "requires. Un-skip once the test agent (or a system-tests-side override) supports it." ) - def test_fr08_11_otel_resource_attributes_env( + @pytest.mark.parametrize("library_env", [{**DEFAULT_ENVVARS}]) + def test_fr08_14_peer_tags( self, otlp_trace_metrics_library_env: dict[str, str], # noqa: ARG002 test_agent: TestAgentAPI, test_library: APMLibrary, ): - """OTEL_RESOURCE_ATTRIBUTES is an alias for DD_TAGS, so its entries also surface in the - tracer_dd_tags resource-attribute container. + """Peer tags surface as the single data-point attribute datadog.peer_tags: an arrayValue of + colon-joined "key:value" strings (same shape as datadog.process_tags), for span tags that + the Agent's peer_tags allowlist covers (e.g. db.hostname, aws.s3.bucket, grpc.target). """ with test_library as t: - with t.dd_start_span(name="web.request", service=SERVICE, typestr="web"): - pass + with t.dd_start_span(name="postgres.query", service="postgres", typestr="db") as span: + span.set_meta("db.hostname", "prod-db-1") t.dd_flush() metrics = test_agent.wait_for_num_otlp_metrics(num=1) - tracer_dd_tags = _resource_attributes(metrics).get("tracer_dd_tags") or [] - assert "team:apm" in tracer_dd_tags, f"Expected team:apm in tracer_dd_tags, got: {tracer_dd_tags}" - assert "deployment.region:us-east-1" in tracer_dd_tags, ( - f"Expected deployment.region:us-east-1 in tracer_dd_tags, got: {tracer_dd_tags}" - ) + attrs = _data_point_attrs(_duration_data_points(metrics)[0]) + peer_tags = attrs.get("datadog.peer_tags") or [] + assert "db.hostname:prod-db-1" in peer_tags, f"Expected db.hostname:prod-db-1 in datadog.peer_tags: {attrs}" + + +@scenarios.parametric +@features.client_side_stats_supported +class Test_FR08_AdditionalTags: + """FR08: DD_TRACE_STATS_ADDITIONAL_TAGS (additional_metric_tags) surfaces as a data-point attribute.""" @pytest.mark.parametrize( "library_env", @@ -1337,6 +1324,9 @@ def test_fr08_12_stats_additional_tags( ): """Span tags named in DD_TRACE_STATS_ADDITIONAL_TAGS surface as the additional_metric_tags data-point container (repeated key:value strings), since their values vary per span. + + This colon-joined-list shape is the reference pattern datadog.peer_tags and + datadog.process_tags must also follow -- do not flatten them into datadog. attributes. """ with test_library as t: with t.dd_start_span(name="web.request", service=SERVICE, typestr="web") as span: From da4da6b59e1ce928c043465420d320a4c24fd667 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Tue, 4 Aug 2026 13:19:48 -0400 Subject: [PATCH 04/16] fix(otlp-trace-metrics): additional_metric_tags is split per-key, not one list attribute additional_metric_tags does not share peer_tags/process_tags' combined colon-joined-list shape. Each DD_TRACE_STATS_ADDITIONAL_TAGS key surfaces as its own data-point attribute (e.g. customer.tier=gold), with no datadog. prefix and no additional_metric_tags container attribute. Co-Authored-By: Claude Sonnet 5 --- tests/parametric/test_otlp_trace_metrics.py | 35 +++++++++++---------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/tests/parametric/test_otlp_trace_metrics.py b/tests/parametric/test_otlp_trace_metrics.py index 6b3d681a52b..5636512a262 100644 --- a/tests/parametric/test_otlp_trace_metrics.py +++ b/tests/parametric/test_otlp_trace_metrics.py @@ -48,9 +48,11 @@ while dd-trace-js does not yet support DD_HOSTNAME and uses os.hostname()), so tests assert presence, not value. Process tags (DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED) surface as the single resource attribute datadog.process_tags: an arrayValue of colon-joined "key:value" strings (enabled by default in some SDKs). -Peer tags surface the same way as the data-point attribute datadog.peer_tags. This is the same shape as -additional_metric_tags (see Test_FR08_AdditionalTags) -- do not flatten either of these -into per-key datadog. attributes. datadog.is_trace_root is a boolean data-point attribute, true only +Peer tags surface the same way as the data-point attribute datadog.peer_tags, sharing +datadog.process_tags' colon-joined-list shape -- do not flatten either into per-key datadog. +attributes. additional_metric_tags (see Test_FR08_AdditionalTags) is the opposite: DD_TRACE_STATS_ADDITIONAL_TAGS +tags are split into individual data-point attributes, one per configured key, with no datadog. prefix. +datadog.is_trace_root is a boolean data-point attribute, true only for the span whose ParentID == 0; it is distinct from datadog.span.top_level (the service-entry marker), which a non-root child span can also carry. Status-code and boolean metric attributes must be typed OTLP values (intValue / boolValue); _dd.stats_computed is a string-valued Datadog convention. @@ -1187,8 +1189,9 @@ def test_fr08_8_process_tags( test_library: APMLibrary, ): """Process tags surface as the single resource attribute datadog.process_tags: an arrayValue - of colon-joined "key:value" strings (same shape as additional_metric_tags; - see test_fr08_12_stats_additional_tags), not one datadog. attribute per tag. + of colon-joined "key:value" strings (same shape as datadog.peer_tags), not one datadog. + attribute per tag, and not split into individual entries the way additional_metric_tags is + (see test_fr08_12_stats_additional_tags). Which process tags are populated varies per library/runtime, so the assertion only requires that at least one known process-tag key is present inside datadog.process_tags. @@ -1310,7 +1313,7 @@ def test_fr08_14_peer_tags( @scenarios.parametric @features.client_side_stats_supported class Test_FR08_AdditionalTags: - """FR08: DD_TRACE_STATS_ADDITIONAL_TAGS (additional_metric_tags) surfaces as a data-point attribute.""" + """FR08: DD_TRACE_STATS_ADDITIONAL_TAGS (additional_metric_tags) surfaces as individual data-point attributes.""" @pytest.mark.parametrize( "library_env", @@ -1322,11 +1325,9 @@ def test_fr08_12_stats_additional_tags( test_agent: TestAgentAPI, test_library: APMLibrary, ): - """Span tags named in DD_TRACE_STATS_ADDITIONAL_TAGS surface as the additional_metric_tags - data-point container (repeated key:value strings), since their values vary per span. - - This colon-joined-list shape is the reference pattern datadog.peer_tags and - datadog.process_tags must also follow -- do not flatten them into datadog. attributes. + """Span tags named in DD_TRACE_STATS_ADDITIONAL_TAGS are split into individual data-point + attributes, each keyed by its own tag name with no datadog. prefix -- unlike datadog.peer_tags + and datadog.process_tags, which stay combined in a single colon-joined-list attribute. """ with test_library as t: with t.dd_start_span(name="web.request", service=SERVICE, typestr="web") as span: @@ -1336,13 +1337,13 @@ def test_fr08_12_stats_additional_tags( metrics = test_agent.wait_for_num_otlp_metrics(num=1) attrs = _data_point_attrs(_duration_data_points(metrics)[0]) - additional_metric_tags = attrs.get("additional_metric_tags") or [] - assert "customer.tier:gold" in additional_metric_tags, ( - f"Expected customer.tier:gold in additional_metric_tags, got: {attrs}" - ) - assert "region:us-east-1" in additional_metric_tags, ( - f"Expected region:us-east-1 in additional_metric_tags, got: {attrs}" + assert attrs.get("customer.tier") == "gold", f"Expected customer.tier=gold as its own attribute, got: {attrs}" + assert attrs.get("region") == "us-east-1", f"Expected region=us-east-1 as its own attribute, got: {attrs}" + assert "additional_metric_tags" not in attrs, ( + f"additional_metric_tags must not be a single container attribute: {attrs}" ) + assert "datadog.customer.tier" not in attrs, f"Additional tags must not carry a datadog. prefix: {attrs}" + assert "datadog.region" not in attrs, f"Additional tags must not carry a datadog. prefix: {attrs}" @scenarios.parametric From a8f62cfd61cf921eb3ffef06dc9b3d26868b2f6d Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Tue, 4 Aug 2026 13:22:46 -0400 Subject: [PATCH 05/16] test(otlp-trace-metrics): also guard against a datadog.-prefixed additional_metric_tags container Co-Authored-By: Claude Sonnet 5 --- tests/parametric/test_otlp_trace_metrics.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/parametric/test_otlp_trace_metrics.py b/tests/parametric/test_otlp_trace_metrics.py index 5636512a262..61e7a930113 100644 --- a/tests/parametric/test_otlp_trace_metrics.py +++ b/tests/parametric/test_otlp_trace_metrics.py @@ -1342,6 +1342,9 @@ def test_fr08_12_stats_additional_tags( assert "additional_metric_tags" not in attrs, ( f"additional_metric_tags must not be a single container attribute: {attrs}" ) + assert "datadog.additional_metric_tags" not in attrs, ( + f"additional_metric_tags must not be a container attribute, prefixed or not: {attrs}" + ) assert "datadog.customer.tier" not in attrs, f"Additional tags must not carry a datadog. prefix: {attrs}" assert "datadog.region" not in attrs, f"Additional tags must not carry a datadog. prefix: {attrs}" From 5167b16e8a5283fb72ca9fcde29c8d20e8a0ac61 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Tue, 4 Aug 2026 13:34:57 -0400 Subject: [PATCH 06/16] test(otlp-trace-metrics): drop absence assertions, trim doc verbosity Remove new assertions that only check a value is absent (datadog.resource.name, additional_metric_tags/datadog. containers) -- they test for the absence of alternate shapes rather than validating the spec's actual dimensions. Trim duplicated "not asserted / implementations differ" doc caveats introduced alongside them. No assertion on an existing/expected value changed. Co-authored-by: Cursor --- tests/parametric/test_otlp_trace_metrics.py | 51 ++++++--------------- 1 file changed, 15 insertions(+), 36 deletions(-) diff --git a/tests/parametric/test_otlp_trace_metrics.py b/tests/parametric/test_otlp_trace_metrics.py index 61e7a930113..0e2496b178a 100644 --- a/tests/parametric/test_otlp_trace_metrics.py +++ b/tests/parametric/test_otlp_trace_metrics.py @@ -33,8 +33,7 @@ * service.name, service.version and deployment.environment.name are reported as resource attributes (the configured default service). No InstrumentationScope is emitted (it would be redundant with the telemetry.sdk.* resource attributes); a span whose service differs from the configured default - additionally carries service.name on its data point. Whether it is also repeated on a data point - whose service matches the default is not asserted (implementations differ; not required either way). + additionally carries service.name on its data point. * OTLP metric flush/export cadence is fixed at 10s and is not overridable by OTEL_METRIC_EXPORT_INTERVAL. The internal _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL (milliseconds) shortens it in tests only. * Transport differs per library and is out of scope for parity: dd-trace-py exports HTTP/JSON only, @@ -46,16 +45,14 @@ Span Metrics Connector's own convention, e.g. "server" -> "SPAN_KIND_SERVER"). host.name is reported when DD_TRACE_REPORT_HOSTNAME is enabled; its source is library-specific (libdatadog tracers honor DD_HOSTNAME, while dd-trace-js does not yet support DD_HOSTNAME and uses os.hostname()), so tests assert presence, not value. -Process tags (DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED) surface as the single resource attribute -datadog.process_tags: an arrayValue of colon-joined "key:value" strings (enabled by default in some SDKs). -Peer tags surface the same way as the data-point attribute datadog.peer_tags, sharing -datadog.process_tags' colon-joined-list shape -- do not flatten either into per-key datadog. -attributes. additional_metric_tags (see Test_FR08_AdditionalTags) is the opposite: DD_TRACE_STATS_ADDITIONAL_TAGS -tags are split into individual data-point attributes, one per configured key, with no datadog. prefix. -datadog.is_trace_root is a boolean data-point attribute, true only -for the span whose ParentID == 0; it is distinct from datadog.span.top_level (the service-entry marker), -which a non-root child span can also carry. Status-code and boolean metric attributes must be typed OTLP -values (intValue / boolValue); _dd.stats_computed is a string-valued Datadog convention. +Process tags (DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED) surface as the resource attribute +datadog.process_tags, and peer tags as the data-point attribute datadog.peer_tags -- both an arrayValue +of colon-joined "key:value" strings. additional_metric_tags (DD_TRACE_STATS_ADDITIONAL_TAGS, see +Test_FR08_AdditionalTags) instead splits each configured key into its own data-point attribute. +datadog.is_trace_root is a boolean data-point attribute, true only for the span whose ParentID == 0; +it is distinct from datadog.span.top_level (the service-entry marker), which a non-root child span +can also carry. Status-code and boolean metric attributes must be typed OTLP values (intValue / +boolValue); _dd.stats_computed is a string-valued Datadog convention. """ import base64 @@ -670,9 +667,6 @@ def test_fr06_1_resource_span_name( metrics = test_agent.wait_for_num_otlp_metrics(num=1) attrs = _data_point_attrs(_duration_data_points(metrics)[0]) assert attrs.get("span.name") == "/users", f"Expected span.name=/users, got attrs: {attrs}" - assert "datadog.resource.name" not in attrs, ( - f"datadog.resource.name must be absent in Datadog-default mode: {attrs}" - ) @pytest.mark.parametrize("library_env", [{**DEFAULT_ENVVARS}]) def test_fr06_2_span_kind( @@ -807,8 +801,7 @@ def test_fr06_9_service_env_version( test_library: APMLibrary, ): """DD_SERVICE / DD_ENV / DD_VERSION map to the resource attributes service.name / - deployment.environment.name / service.version. The span uses the default service, so whether - its data point also repeats service.name is not asserted here (implementations differ). + deployment.environment.name / service.version. """ with test_library as t: with t.dd_start_span(name="web.request", service=SERVICE, typestr="web"): @@ -851,8 +844,7 @@ def test_fr06_14_custom_service_on_data_point( f"Expected resource service.name={SERVICE}, got: {_resource_attributes(metrics)}" ) services_on_points = _data_point_services(metrics) - # The custom service is carried on its own data point. Whether the default service is also - # repeated on its own data point is not asserted (implementations differ; not required either way). + # The custom service is carried on its own data point. assert "postgres" in services_on_points, ( f"Expected postgres service.name on its data point: {services_on_points}" ) @@ -1188,13 +1180,9 @@ def test_fr08_8_process_tags( test_agent: TestAgentAPI, test_library: APMLibrary, ): - """Process tags surface as the single resource attribute datadog.process_tags: an arrayValue - of colon-joined "key:value" strings (same shape as datadog.peer_tags), not one datadog. - attribute per tag, and not split into individual entries the way additional_metric_tags is - (see test_fr08_12_stats_additional_tags). - - Which process tags are populated varies per library/runtime, so the assertion only requires - that at least one known process-tag key is present inside datadog.process_tags. + """Process tags surface as the resource attribute datadog.process_tags: an arrayValue of + colon-joined "key:value" strings. Which tags are populated varies per library/runtime, so the + assertion only requires that at least one known process-tag key is present. """ with test_library as t: with t.dd_start_span(name="web.request", service=SERVICE, typestr="web"): @@ -1326,8 +1314,7 @@ def test_fr08_12_stats_additional_tags( test_library: APMLibrary, ): """Span tags named in DD_TRACE_STATS_ADDITIONAL_TAGS are split into individual data-point - attributes, each keyed by its own tag name with no datadog. prefix -- unlike datadog.peer_tags - and datadog.process_tags, which stay combined in a single colon-joined-list attribute. + attributes, each keyed by its own tag name. """ with test_library as t: with t.dd_start_span(name="web.request", service=SERVICE, typestr="web") as span: @@ -1339,14 +1326,6 @@ def test_fr08_12_stats_additional_tags( attrs = _data_point_attrs(_duration_data_points(metrics)[0]) assert attrs.get("customer.tier") == "gold", f"Expected customer.tier=gold as its own attribute, got: {attrs}" assert attrs.get("region") == "us-east-1", f"Expected region=us-east-1 as its own attribute, got: {attrs}" - assert "additional_metric_tags" not in attrs, ( - f"additional_metric_tags must not be a single container attribute: {attrs}" - ) - assert "datadog.additional_metric_tags" not in attrs, ( - f"additional_metric_tags must not be a container attribute, prefixed or not: {attrs}" - ) - assert "datadog.customer.tier" not in attrs, f"Additional tags must not carry a datadog. prefix: {attrs}" - assert "datadog.region" not in attrs, f"Additional tags must not carry a datadog. prefix: {attrs}" @scenarios.parametric From e779e64b7f8e4f87705c98c03af035889215da75 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Tue, 4 Aug 2026 13:57:44 -0400 Subject: [PATCH 07/16] test(otlp-trace-metrics): skip process_tags/additional_tags for python Verified locally against the merged dd-trace-py#18354 build: datadog.process_tags is never emitted on OTLP trace metrics, and DD_TRACE_STATS_ADDITIONAL_TAGS keys are not forwarded to the native exporter at all. Mark both missing_feature until implemented. Co-authored-by: Cursor --- manifests/python.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/manifests/python.yml b/manifests/python.yml index 2a5a4d502d1..1dd54a74680 100644 --- a/manifests/python.yml +++ b/manifests/python.yml @@ -2004,6 +2004,8 @@ manifest: tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_2_span_kind: missing_feature (span.kind is emitted as the raw Datadog value, e.g. "server", not the SMC's SPAN_KIND_* convention) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_7_rpc_status_code: missing_feature (Requires libdatadog v38, will be added in future release) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_8_status_code_error: missing_feature (status.code is emitted as an integer, e.g. 2, not the SMC's STATUS_CODE_ERROR string convention) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags::test_fr08_12_stats_additional_tags: missing_feature (DD_TRACE_STATS_ADDITIONAL_TAGS is not forwarded to the native OTLP trace-metrics exporter) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_8_process_tags: missing_feature (datadog.process_tags is not emitted on OTLP trace metrics) tests/parametric/test_otlp_trace_metrics.py::Test_FR09_Red_Metric_Derivation::test_fr09_2_error_count: missing_feature (status.code is emitted as an integer, e.g. 2, not the SMC's STATUS_CODE_ERROR string convention, so the error span is not counted) tests/parametric/test_parametric_endpoints.py::Test_Parametric_DDTrace_Baggage: v2.16.0 tests/parametric/test_parametric_endpoints.py::Test_Parametric_FFE_Start: v4.0.0 From e49782ee9bfac1462bca2e2094ea0d792a016ab3 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Tue, 4 Aug 2026 14:14:20 -0400 Subject: [PATCH 08/16] refactor(otlp-trace-metrics): inline single-value constants, trim comments ERROR_STATUS_VALUES and SPAN_KIND_SERVER_VALUES each held one literal value; inline them directly in the assertions and drop the surrounding explanatory comments to shrink the diff. Co-authored-by: Cursor --- tests/parametric/test_otlp_trace_metrics.py | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/tests/parametric/test_otlp_trace_metrics.py b/tests/parametric/test_otlp_trace_metrics.py index 0e2496b178a..f2c56f21dff 100644 --- a/tests/parametric/test_otlp_trace_metrics.py +++ b/tests/parametric/test_otlp_trace_metrics.py @@ -83,16 +83,6 @@ # as their string name (see https://protobuf.dev/programming-guides/json/), but parsers must also # accept the integer, so a standards-compliant OTLP/JSON exporter may emit either representation. AGGREGATION_TEMPORALITY_DELTA: tuple[int, str] = (1, "AGGREGATION_TEMPORALITY_DELTA") -# The OTel Span Metrics Connector's default status.code dimension is always this exact string: -# traceutil.StatusCodeStr() maps StatusCodeError -> "STATUS_CODE_ERROR" and writes it via -# attr.PutStr (always a string, never an int). The bare "ERROR" value only appears under a -# different, feature-gated key (otel.status_code), not status.code, so it does not apply here. -# See https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/connector/spanmetricsconnector/connector.go -ERROR_STATUS_VALUES: tuple[str, ...] = ("STATUS_CODE_ERROR",) -# The OTel Span Metrics Connector's default span.kind dimension is always this exact string: -# traceutil.SpanKindStr() maps SpanKindServer -> "SPAN_KIND_SERVER" and writes it via attr.PutStr. -# See https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/internal/coreinternal/traceutil/traceutil.go -SPAN_KIND_SERVER_VALUES: tuple[str, ...] = ("SPAN_KIND_SERVER",) # Expected telemetry.sdk.language resource-attribute value per system-tests library name. The Go # tracer reports the OTel-standard "go" token rather than the system-tests "golang" library name. _SDK_LANGUAGE_BY_LIBRARY = { @@ -683,10 +673,7 @@ def test_fr06_2_span_kind( metrics = test_agent.wait_for_num_otlp_metrics(num=1) attrs = _data_point_attrs(_duration_data_points(metrics)[0]) - # SMC parity: the value matches traceutil.SpanKindStr(), not the raw Datadog span.kind tag. - assert attrs.get("span.kind") in SPAN_KIND_SERVER_VALUES, ( - f"Expected span.kind in {SPAN_KIND_SERVER_VALUES}, got attrs: {attrs}" - ) + assert attrs.get("span.kind") == "SPAN_KIND_SERVER", f"Expected span.kind=SPAN_KIND_SERVER, got: {attrs}" @pytest.mark.parametrize("library_env", [{**DEFAULT_ENVVARS}]) def test_fr06_3_http_method( @@ -780,9 +767,7 @@ def test_fr06_8_status_code_error( metrics = test_agent.wait_for_num_otlp_metrics(num=1) attrs = _data_point_attrs(_duration_data_points(metrics)[0]) - assert attrs.get("status.code") in ERROR_STATUS_VALUES, ( - f"Expected status.code in {ERROR_STATUS_VALUES}, got attrs: {attrs}" - ) + assert attrs.get("status.code") == "STATUS_CODE_ERROR", f"Expected status.code=STATUS_CODE_ERROR, got: {attrs}" @scenarios.parametric @@ -1409,7 +1394,7 @@ def test_fr09_2_error_count( data_points = _duration_data_points(metrics) total = sum(int(dp["count"]) for dp in data_points) error_count = sum( - int(dp["count"]) for dp in data_points if _data_point_attrs(dp).get("status.code") in ERROR_STATUS_VALUES + int(dp["count"]) for dp in data_points if _data_point_attrs(dp).get("status.code") == "STATUS_CODE_ERROR" ) assert total == 2, f"Expected 2 selected spans, got {total}" assert error_count == 1, f"Expected exactly one error span, got {error_count}" From bbc575264442b5669ff4e2ca07b4b0fb0dda9414 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Tue, 4 Aug 2026 14:20:13 -0400 Subject: [PATCH 09/16] test(otlp-trace-metrics): move peer_tags skip from decorator to manifest test_fr08_14_peer_tags is blocked on ddapm-test-agent support, not a per-library gap, but manifests are the preferred activation mechanism. Declare it missing_feature in python.yml and nodejs.yml (the only manifests where the surrounding class isn't already skipped wholesale) instead of an in-line @pytest.mark.skip. Co-authored-by: Cursor --- manifests/nodejs.yml | 1 + manifests/python.yml | 1 + tests/parametric/test_otlp_trace_metrics.py | 5 ----- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/manifests/nodejs.yml b/manifests/nodejs.yml index 62dce43e838..b08b8144959 100644 --- a/manifests/nodejs.yml +++ b/manifests/nodejs.yml @@ -2203,6 +2203,7 @@ manifest: tests/parametric/test_otlp_trace_metrics.py::Test_FR05_Sampling_Independence::test_fr05_1_metrics_computed_before_sampling: missing_feature (nodejs does not drop sampled-out traces when client-side stats are computed) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_10_hostname: irrelevant (DD_HOSTNAME is only supported in Python) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_13_is_trace_root: missing_feature (datadog.is_trace_root is not yet emitted on OTLP trace-metric data points) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_14_peer_tags: missing_feature (Blocked on test-agent support for advertising a peer_tags allowlist in /info) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_5_top_level_child_different_service: missing_feature (nodejs does not tag child spans with a different service than their parent as top-level) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_8_process_tags: missing_feature (process tags are still emitted as individual datadog. resource attributes, not the single datadog.process_tags list attribute) tests/parametric/test_parametric_endpoints.py::TestRemoteConfigApplyEndpoint: incomplete_test_app (POST /trace/remote-config/apply only implemented in the python parametric app) diff --git a/manifests/python.yml b/manifests/python.yml index 1dd54a74680..4ffc66e0b52 100644 --- a/manifests/python.yml +++ b/manifests/python.yml @@ -2005,6 +2005,7 @@ manifest: tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_7_rpc_status_code: missing_feature (Requires libdatadog v38, will be added in future release) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_8_status_code_error: missing_feature (status.code is emitted as an integer, e.g. 2, not the SMC's STATUS_CODE_ERROR string convention) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags::test_fr08_12_stats_additional_tags: missing_feature (DD_TRACE_STATS_ADDITIONAL_TAGS is not forwarded to the native OTLP trace-metrics exporter) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_14_peer_tags: missing_feature (Blocked on test-agent support for advertising a peer_tags allowlist in /info) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_8_process_tags: missing_feature (datadog.process_tags is not emitted on OTLP trace metrics) tests/parametric/test_otlp_trace_metrics.py::Test_FR09_Red_Metric_Derivation::test_fr09_2_error_count: missing_feature (status.code is emitted as an integer, e.g. 2, not the SMC's STATUS_CODE_ERROR string convention, so the error span is not counted) tests/parametric/test_parametric_endpoints.py::Test_Parametric_DDTrace_Baggage: v2.16.0 diff --git a/tests/parametric/test_otlp_trace_metrics.py b/tests/parametric/test_otlp_trace_metrics.py index f2c56f21dff..c600f027945 100644 --- a/tests/parametric/test_otlp_trace_metrics.py +++ b/tests/parametric/test_otlp_trace_metrics.py @@ -1256,11 +1256,6 @@ def test_fr08_13_is_trace_root( f"A service-entry child is not the trace root; expected datadog.is_trace_root=false: {service_entry_attrs}" ) - @pytest.mark.skip( - reason="Blocked on test-agent support: the ddapm-test-agent /info response has no mechanism " - "today to advertise a peer_tags allowlist, which client-side stats peer-tag aggregation " - "requires. Un-skip once the test agent (or a system-tests-side override) supports it." - ) @pytest.mark.parametrize("library_env", [{**DEFAULT_ENVVARS}]) def test_fr08_14_peer_tags( self, From 22fc2f6abb391217175172a4dbec9058992e07d8 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Tue, 4 Aug 2026 14:44:50 -0400 Subject: [PATCH 10/16] test(otlp-trace-metrics): fix python manifest after main merge The merge from main changed test_fr08_8_process_tags to check for individual datadog. resource attributes (matching JS/.NET), which python already emits, so drop its now-stale missing_feature entry. Add missing_feature entries for the three tests that now fail against python: test_fr08_13_is_trace_root (emitted as _datadog.is_trace_root, with a leading underscore) and the two new DD_TAGS/OTEL_RESOURCE_ATTRIBUTES tracer_dd_tags tests (not yet reported for OTLP trace metrics). Co-authored-by: Cursor --- manifests/python.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/manifests/python.yml b/manifests/python.yml index a7b73b1cb2b..14407a41810 100644 --- a/manifests/python.yml +++ b/manifests/python.yml @@ -2060,9 +2060,11 @@ manifest: tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_2_span_kind: missing_feature (span.kind is emitted as the raw Datadog value, e.g. "server", not the SMC's SPAN_KIND_* convention) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_7_rpc_status_code: missing_feature (Requires libdatadog v38, will be added in future release) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_8_status_code_error: missing_feature (status.code is emitted as an integer, e.g. 2, not the SMC's STATUS_CODE_ERROR string convention) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags::test_fr08_10_dd_tags_resource_attributes: missing_feature (DD_TAGS is not yet reported as tracer_dd_tags on the OTLP trace metrics resource) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags::test_fr08_11_otel_resource_attributes_env: missing_feature (OTEL_RESOURCE_ATTRIBUTES is not yet reported as tracer_dd_tags on the OTLP trace metrics resource) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags::test_fr08_12_stats_additional_tags: missing_feature (DD_TRACE_STATS_ADDITIONAL_TAGS is not forwarded to the native OTLP trace-metrics exporter) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_13_is_trace_root: missing_feature (datadog.is_trace_root is emitted as _datadog.is_trace_root, with a leading underscore) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_14_peer_tags: missing_feature (Blocked on test-agent support for advertising a peer_tags allowlist in /info) - tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_8_process_tags: missing_feature (datadog.process_tags is not emitted on OTLP trace metrics) tests/parametric/test_otlp_trace_metrics.py::Test_FR09_Red_Metric_Derivation::test_fr09_2_error_count: missing_feature (status.code is emitted as an integer, e.g. 2, not the SMC's STATUS_CODE_ERROR string convention, so the error span is not counted) tests/parametric/test_parametric_endpoints.py::Test_Parametric_DDTrace_Baggage: v2.16.0 tests/parametric/test_parametric_endpoints.py::Test_Parametric_FFE_Start: v4.0.0 From 7e08a9f299ab5cbab8be435917acebc101eec48e Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Tue, 4 Aug 2026 17:19:17 -0400 Subject: [PATCH 11/16] test(otlp-trace-metrics): align process_tags/DD_TAGS assertions with spec, expand manifest skips - test_fr08_8_process_tags: revert to checking the single combined datadog.process_tags arrayValue (the main merge had regressed it to check per-key datadog. attributes, which no longer matches its own docstring; flagged in PR review comment 5183792859). - test_fr08_10/test_fr08_11 (DD_TAGS / OTEL_RESOURCE_ATTRIBUTES): rename the expected resource attribute from tracer_dd_tags to datadog.tracer_tags, using the same colon-joined arrayValue shape as datadog.process_tags. - Add/refresh missing_feature skips in python/nodejs/java/dotnet manifests based on the cross-repo OTLP trace-metrics findings audit and the latest CI run (span.kind canonicalization, status.code SMC string convention, is_trace_root wiring, process_tags shape, DD_APM_TRACING_ENABLED gating). Co-authored-by: Cursor --- manifests/dotnet.yml | 9 +++- manifests/java.yml | 8 +++- manifests/nodejs.yml | 8 ++++ manifests/python.yml | 5 ++- tests/parametric/test_otlp_trace_metrics.py | 48 +++++++++++---------- 5 files changed, 50 insertions(+), 28 deletions(-) diff --git a/manifests/dotnet.yml b/manifests/dotnet.yml index e7499329c76..cf8de2b12b2 100644 --- a/manifests/dotnet.yml +++ b/manifests/dotnet.yml @@ -1025,10 +1025,15 @@ manifest: tests/parametric/test_otlp_trace_metrics.py::Test_FR02_Mutual_Exclusion::test_fr02_3_otlp_suppresses_native_stats: irrelevant (OTLP trace metrics alongside native (non-OTLP) trace export was removed and will not be supported; OTLP trace metrics now require OTEL_TRACES_EXPORTER=otlp) tests/parametric/test_otlp_trace_metrics.py::Test_FR04_Span_Selection::test_fr04_2_unmeasured_child_excluded: v3.51.0 tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_10_hostname: missing_feature (host.name is not yet reported on the OTLP trace metrics resource) - tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags::test_fr08_10_dd_tags_resource_attributes: missing_feature (DD_TAGS is not yet reported as tracer_dd_tags on the OTLP trace metrics resource) - tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags::test_fr08_11_otel_resource_attributes_env: missing_feature (OTEL_RESOURCE_ATTRIBUTES is not yet reported as tracer_dd_tags on the OTLP trace metrics resource) + tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_2_span_kind: missing_feature (span.kind is emitted as the raw Datadog value, e.g. "server", not the SMC's SPAN_KIND_* convention) + tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_8_status_code_error: missing_feature (status.code is emitted as an integer, e.g. 2, not the SMC's STATUS_CODE_ERROR string convention) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags::test_fr08_10_dd_tags_resource_attributes: missing_feature (DD_TAGS is not yet reported as datadog.tracer_tags on the OTLP trace metrics resource) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags::test_fr08_11_otel_resource_attributes_env: missing_feature (OTEL_RESOURCE_ATTRIBUTES is not yet reported as datadog.tracer_tags on the OTLP trace metrics resource) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags::test_fr08_12_stats_additional_tags: missing_feature (DD_TRACE_STATS_ADDITIONAL_TAGS is not yet emitted as additional_metric_tags on OTLP trace metrics data points) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_13_is_trace_root: missing_feature (datadog.is_trace_root is computed but never emitted on OTLP trace metrics data points) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_14_peer_tags: missing_feature (Blocked on test-agent support for advertising a peer_tags allowlist in /info) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_8_process_tags: missing_feature (process tags are not yet emitted on the OTLP trace metrics resource) + tests/parametric/test_otlp_trace_metrics.py::Test_FR09_Red_Metric_Derivation::test_fr09_2_error_count: missing_feature (status.code is emitted as an integer, e.g. 2, not the SMC's STATUS_CODE_ERROR string convention, so the error span is not counted) tests/parametric/test_otlp_trace_metrics.py::Test_FR15_Client_Computed_Stats_Header::test_fr15_1_header_set_when_enabled: irrelevant (OTLP trace metrics alongside native (non-OTLP) trace export was removed and will not be supported; OTLP trace metrics now require OTEL_TRACES_EXPORTER=otlp) tests/parametric/test_parametric_endpoints.py::TestRemoteConfigApplyEndpoint: incomplete_test_app (POST /trace/remote-config/apply only implemented in the python parametric app) tests/parametric/test_parametric_endpoints.py::Test_Parametric_DDSpan_Add_Link: incomplete_test_app (add_link parametric endpoint is not implemented) diff --git a/manifests/java.yml b/manifests/java.yml index dc41e37f6e6..fef9900f74f 100644 --- a/manifests/java.yml +++ b/manifests/java.yml @@ -3988,8 +3988,14 @@ manifest: - declaration: missing_feature (OTel resource naming implemented in 1.24.0) component_version: <=1.23.0 tests/parametric/test_otlp_trace_metrics.py: v1.65.0-SNAPSHOT # Easy win for all weblogs and version 1.64.2 - tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags: 'missing_feature (DD_TAGS are not yet emitted as datadog. resource attributes; support coming in a future PR)' + tests/parametric/test_otlp_trace_metrics.py::Test_FR01_Enablement_Configuration::test_fr01_6_disabled_when_apm_tracing_disabled: missing_feature (OTLP trace metrics are still emitted when DD_APM_TRACING_ENABLED=false) + tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_2_span_kind: missing_feature (span.kind is emitted as the raw Datadog value, e.g. "server", not the SMC's SPAN_KIND_* convention) + tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_8_status_code_error: missing_feature (status.code is emitted as the bare string "ERROR" instead of the SMC's STATUS_CODE_ERROR convention, and is omitted entirely for non-error spans) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags: missing_feature (DD_TAGS/OTEL_RESOURCE_ATTRIBUTES are not yet emitted as the datadog.tracer_tags resource attribute; support coming in a future PR) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_13_is_trace_root: missing_feature (datadog.is_trace_root is computed but never emitted on OTLP trace metrics data points) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_14_peer_tags: missing_feature (Blocked on test-agent support for advertising a peer_tags allowlist in /info) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_8_process_tags: missing_feature (process_tags is split into per-key datadog. resource attributes instead of one combined datadog.process_tags arrayValue) + tests/parametric/test_otlp_trace_metrics.py::Test_FR09_Red_Metric_Derivation::test_fr09_2_error_count: missing_feature (status.code is emitted as the bare string "ERROR" instead of the SMC's STATUS_CODE_ERROR convention, so the error span is not counted) tests/parametric/test_parametric_endpoints.py::TestRemoteConfigApplyEndpoint: incomplete_test_app (POST /trace/remote-config/apply only implemented in the python parametric app) tests/parametric/test_parametric_endpoints.py::Test_Parametric_DDSpan_Add_Link: incomplete_test_app (add_link endpoint is not implemented) tests/parametric/test_parametric_endpoints.py::Test_Parametric_DDTrace_Baggage: # Modified by easy win activation script diff --git a/manifests/nodejs.yml b/manifests/nodejs.yml index 918b2c1814d..6f16942f86f 100644 --- a/manifests/nodejs.yml +++ b/manifests/nodejs.yml @@ -2380,13 +2380,21 @@ manifest: tests/parametric/test_otel_span_with_baggage.py::Test_Otel_Span_With_Baggage: *ref_5_32_0 tests/parametric/test_otel_tracer.py::Test_Otel_Tracer::test_otel_force_flush: missing_feature (Not implemented) tests/parametric/test_otlp_trace_metrics.py: *ref_5_111_0 + tests/parametric/test_otlp_trace_metrics.py::Test_FR01_Enablement_Configuration::test_fr01_6_disabled_when_apm_tracing_disabled: missing_feature (OTLP trace metrics are still emitted when DD_APM_TRACING_ENABLED=false) tests/parametric/test_otlp_trace_metrics.py::Test_FR05_Sampling_Independence::test_fr05_1_metrics_computed_before_sampling: # TODO: a lower version might be supported - declaration: missing_feature (nodejs does not drop sampled-out traces when client-side stats are computed) component_version: <6.8.0 tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_10_hostname: irrelevant (DD_HOSTNAME is only supported in Python) + tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_2_span_kind: missing_feature (span.kind is emitted as the raw Datadog value, e.g. "server", not the SMC's SPAN_KIND_* convention) + tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_8_status_code_error: missing_feature (status.code is emitted as an integer, e.g. 2, not the SMC's STATUS_CODE_ERROR string convention) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags::test_fr08_10_dd_tags_resource_attributes: missing_feature (DD_TAGS is not yet reported as datadog.tracer_tags on the OTLP trace metrics resource) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags::test_fr08_11_otel_resource_attributes_env: missing_feature (OTEL_RESOURCE_ATTRIBUTES is not yet reported as datadog.tracer_tags on the OTLP trace metrics resource) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags::test_fr08_12_stats_additional_tags: missing_feature (DD_TRACE_STATS_ADDITIONAL_TAGS is not forwarded to the native OTLP trace-metrics exporter) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_13_is_trace_root: missing_feature (datadog.is_trace_root is not yet emitted on OTLP trace-metric data points) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_14_peer_tags: missing_feature (Blocked on test-agent support for advertising a peer_tags allowlist in /info) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_5_top_level_child_different_service: missing_feature (nodejs does not tag child spans with a different service than their parent as top-level) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_8_process_tags: missing_feature (process_tags is split into per-key datadog. resource attributes instead of one combined datadog.process_tags arrayValue) + tests/parametric/test_otlp_trace_metrics.py::Test_FR09_Red_Metric_Derivation::test_fr09_2_error_count: missing_feature (status.code is emitted as an integer, e.g. 2, not the SMC's STATUS_CODE_ERROR string convention, so the error span is not counted) tests/parametric/test_parametric_endpoints.py::TestRemoteConfigApplyEndpoint: incomplete_test_app (POST /trace/remote-config/apply only implemented in the python parametric app) tests/parametric/test_parametric_endpoints.py::Test_Parametric_DDSpan_Set_Resource: incomplete_test_app (set_resource endpoint is not implemented) tests/parametric/test_parametric_endpoints.py::Test_Parametric_DDSpan_Start: # The resource name of the child span is overidden by the parent span. diff --git a/manifests/python.yml b/manifests/python.yml index 14407a41810..6848a845ebe 100644 --- a/manifests/python.yml +++ b/manifests/python.yml @@ -2060,11 +2060,12 @@ manifest: tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_2_span_kind: missing_feature (span.kind is emitted as the raw Datadog value, e.g. "server", not the SMC's SPAN_KIND_* convention) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_7_rpc_status_code: missing_feature (Requires libdatadog v38, will be added in future release) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_8_status_code_error: missing_feature (status.code is emitted as an integer, e.g. 2, not the SMC's STATUS_CODE_ERROR string convention) - tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags::test_fr08_10_dd_tags_resource_attributes: missing_feature (DD_TAGS is not yet reported as tracer_dd_tags on the OTLP trace metrics resource) - tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags::test_fr08_11_otel_resource_attributes_env: missing_feature (OTEL_RESOURCE_ATTRIBUTES is not yet reported as tracer_dd_tags on the OTLP trace metrics resource) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags::test_fr08_10_dd_tags_resource_attributes: missing_feature (DD_TAGS is not yet reported as datadog.tracer_tags on the OTLP trace metrics resource) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags::test_fr08_11_otel_resource_attributes_env: missing_feature (OTEL_RESOURCE_ATTRIBUTES is not yet reported as datadog.tracer_tags on the OTLP trace metrics resource) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags::test_fr08_12_stats_additional_tags: missing_feature (DD_TRACE_STATS_ADDITIONAL_TAGS is not forwarded to the native OTLP trace-metrics exporter) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_13_is_trace_root: missing_feature (datadog.is_trace_root is emitted as _datadog.is_trace_root, with a leading underscore) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_14_peer_tags: missing_feature (Blocked on test-agent support for advertising a peer_tags allowlist in /info) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_8_process_tags: missing_feature (process_tags is split into per-key datadog. resource attributes instead of one combined datadog.process_tags arrayValue) tests/parametric/test_otlp_trace_metrics.py::Test_FR09_Red_Metric_Derivation::test_fr09_2_error_count: missing_feature (status.code is emitted as an integer, e.g. 2, not the SMC's STATUS_CODE_ERROR string convention, so the error span is not counted) tests/parametric/test_parametric_endpoints.py::Test_Parametric_DDTrace_Baggage: v2.16.0 tests/parametric/test_parametric_endpoints.py::Test_Parametric_FFE_Start: v4.0.0 diff --git a/tests/parametric/test_otlp_trace_metrics.py b/tests/parametric/test_otlp_trace_metrics.py index 3544894bcce..83169d9a226 100644 --- a/tests/parametric/test_otlp_trace_metrics.py +++ b/tests/parametric/test_otlp_trace_metrics.py @@ -46,9 +46,10 @@ DD_TRACE_REPORT_HOSTNAME is enabled; its source is library-specific (libdatadog tracers honor DD_HOSTNAME, while dd-trace-js does not yet support DD_HOSTNAME and uses os.hostname()), so tests assert presence, not value. Process tags (DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED) surface as the resource attribute -datadog.process_tags, and peer tags as the data-point attribute datadog.peer_tags -- both an arrayValue -of colon-joined "key:value" strings. additional_metric_tags (DD_TRACE_STATS_ADDITIONAL_TAGS, see -Test_FR08_AdditionalTags) instead splits each configured key into its own data-point attribute. +datadog.process_tags, peer tags as the data-point attribute datadog.peer_tags, and global tags +(DD_TAGS / OTEL_RESOURCE_ATTRIBUTES) as the resource attribute datadog.tracer_tags -- all three an +arrayValue of colon-joined "key:value" strings. additional_metric_tags (DD_TRACE_STATS_ADDITIONAL_TAGS, +see Test_FR08_AdditionalTags) instead splits each configured key into its own data-point attribute. datadog.is_trace_root is a boolean data-point attribute, true only for the span whose ParentID == 0; it is distinct from datadog.span.top_level (the service-entry marker), which a non-root child span can also carry. Status-code and boolean metric attributes must be typed OTLP values (intValue / @@ -98,7 +99,7 @@ "rust": "rust", "cpp": "cpp", } -# Known process-tag keys; any one appearing as its own datadog. resource attribute satisfies FR08. +# Known process-tag keys; any one appearing inside datadog.process_tags (as "key:value") satisfies FR08. # Which tags are populated varies per library/runtime, so the test only requires one known key present. _PROCESS_TAG_KEYS = ( "entrypoint.name", @@ -1234,9 +1235,9 @@ def test_fr08_8_process_tags( test_agent: TestAgentAPI, test_library: APMLibrary, ): - """Process tags surface as the resource attribute datadog.process_tags: an arrayValue of - colon-joined "key:value" strings. Which tags are populated varies per library/runtime, so the - assertion only requires that at least one known process-tag key is present. + """Process tags surface as the single resource attribute datadog.process_tags: an arrayValue + of colon-joined "key:value" strings. Which tags are populated varies per library/runtime, so + the assertion only requires that at least one known process-tag key is present. """ with test_library as t: with t.dd_start_span(name="web.request", service=SERVICE, typestr="web"): @@ -1244,9 +1245,9 @@ def test_fr08_8_process_tags( t.dd_flush() metrics = _wait_for_otlp_metrics(test_agent) - resource_attrs = _resource_attributes(metrics) - assert any(f"datadog.{tag}" in resource_attrs for tag in _PROCESS_TAG_KEYS), ( - f"Expected at least one datadog. resource attribute, got: {list(resource_attrs)}" + process_tags = _resource_attributes(metrics).get("datadog.process_tags") or [] + assert any(str(entry).split(":", 1)[0] in _PROCESS_TAG_KEYS for entry in process_tags), ( + f"Expected a known process-tag key inside datadog.process_tags, got: {process_tags}" ) @pytest.mark.parametrize("library_env", [{**DEFAULT_ENVVARS_OTLP}]) @@ -1350,7 +1351,7 @@ def test_fr08_14_peer_tags( @scenarios.parametric @features.client_side_stats_supported class Test_FR08_AdditionalTags: - """FR08: DD_TAGS (tracer_dd_tags) / OTEL_RESOURCE_ATTRIBUTES surface as resource attributes and + """FR08: DD_TAGS (datadog.tracer_tags) / OTEL_RESOURCE_ATTRIBUTES surface as resource attributes and DD_TRACE_STATS_ADDITIONAL_TAGS (additional_metric_tags) as data-point attributes (support pending in some SDKs). """ @@ -1373,8 +1374,9 @@ def test_fr08_10_dd_tags_resource_attributes( test_agent: TestAgentAPI, test_library: APMLibrary, ): - """Global DD_TAGS surface as the tracer_dd_tags resource-attribute container (repeated key:value - strings) in default mode; reserved service/env/version/runtime_id/runtime-id keys are ignored. + """Global DD_TAGS surface as the resource attribute datadog.tracer_tags: an arrayValue of + colon-joined "key:value" strings (same shape as datadog.process_tags), in default mode; + reserved service/env/version/runtime_id/runtime-id keys are ignored. """ with test_library as t: with t.dd_start_span(name="web.request", service=SERVICE, typestr="web"): @@ -1383,12 +1385,12 @@ def test_fr08_10_dd_tags_resource_attributes( metrics = test_agent.wait_for_num_otlp_metrics(num=1) resource_attrs = _resource_attributes(metrics) - tracer_dd_tags = resource_attrs.get("tracer_dd_tags") or [] - assert "team:apm" in tracer_dd_tags, f"Expected team:apm in tracer_dd_tags, got: {resource_attrs}" - assert "tier:backend" in tracer_dd_tags, f"Expected tier:backend in tracer_dd_tags, got: {resource_attrs}" + tracer_tags = resource_attrs.get("datadog.tracer_tags") or [] + assert "team:apm" in tracer_tags, f"Expected team:apm in datadog.tracer_tags, got: {resource_attrs}" + assert "tier:backend" in tracer_tags, f"Expected tier:backend in datadog.tracer_tags, got: {resource_attrs}" for reserved in ("service", "env", "version", "runtime_id", "runtime-id"): - assert not any(str(entry).startswith(f"{reserved}:") for entry in tracer_dd_tags), ( - f"Reserved DD_TAGS key {reserved!r} must be ignored, got: {tracer_dd_tags}" + assert not any(str(entry).startswith(f"{reserved}:") for entry in tracer_tags), ( + f"Reserved DD_TAGS key {reserved!r} must be ignored, got: {tracer_tags}" ) assert resource_attrs.get("service.name") == SERVICE, ( f"DD_TAGS service must not override configured service.name={SERVICE}, got: {resource_attrs}" @@ -1405,7 +1407,7 @@ def test_fr08_11_otel_resource_attributes_env( test_library: APMLibrary, ): """OTEL_RESOURCE_ATTRIBUTES is an alias for DD_TAGS, so its entries also surface in the - tracer_dd_tags resource-attribute container. + datadog.tracer_tags resource attribute. """ with test_library as t: with t.dd_start_span(name="web.request", service=SERVICE, typestr="web"): @@ -1413,10 +1415,10 @@ def test_fr08_11_otel_resource_attributes_env( t.dd_flush() metrics = test_agent.wait_for_num_otlp_metrics(num=1) - tracer_dd_tags = _resource_attributes(metrics).get("tracer_dd_tags") or [] - assert "team:apm" in tracer_dd_tags, f"Expected team:apm in tracer_dd_tags, got: {tracer_dd_tags}" - assert "deployment.region:us-east-1" in tracer_dd_tags, ( - f"Expected deployment.region:us-east-1 in tracer_dd_tags, got: {tracer_dd_tags}" + tracer_tags = _resource_attributes(metrics).get("datadog.tracer_tags") or [] + assert "team:apm" in tracer_tags, f"Expected team:apm in datadog.tracer_tags, got: {tracer_tags}" + assert "deployment.region:us-east-1" in tracer_tags, ( + f"Expected deployment.region:us-east-1 in datadog.tracer_tags, got: {tracer_tags}" ) @pytest.mark.parametrize( From 8064399c5fd5c16fab78a8a65c5e9f03180f38e9 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Wed, 5 Aug 2026 01:06:06 -0400 Subject: [PATCH 12/16] test(otlp-trace-metrics): address Codex review and fix java capabilities Fix OTLP env/fixture usage in FR01/FR02/FR08 tests, normalize Java protobuf payloads via _wait_for_otlp_metrics, align default service.name assertions with spec, and register FFE_FLAG_CONFIGURATION_RULES for java >=1.64.2. Co-authored-by: Cursor --- manifests/dotnet.yml | 1 + manifests/java.yml | 1 + manifests/nodejs.yml | 1 + manifests/python.yml | 1 + tests/parametric/capabilities.yml | 3 + tests/parametric/test_otlp_trace_metrics.py | 66 ++++++++++++++------- 6 files changed, 52 insertions(+), 21 deletions(-) diff --git a/manifests/dotnet.yml b/manifests/dotnet.yml index cf8de2b12b2..e7c15cf2d87 100644 --- a/manifests/dotnet.yml +++ b/manifests/dotnet.yml @@ -1025,6 +1025,7 @@ manifest: tests/parametric/test_otlp_trace_metrics.py::Test_FR02_Mutual_Exclusion::test_fr02_3_otlp_suppresses_native_stats: irrelevant (OTLP trace metrics alongside native (non-OTLP) trace export was removed and will not be supported; OTLP trace metrics now require OTEL_TRACES_EXPORTER=otlp) tests/parametric/test_otlp_trace_metrics.py::Test_FR04_Span_Selection::test_fr04_2_unmeasured_child_excluded: v3.51.0 tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_10_hostname: missing_feature (host.name is not yet reported on the OTLP trace metrics resource) + tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_15_default_service_on_data_point: missing_feature (service.name is omitted from the data point when the span uses the configured default service) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_2_span_kind: missing_feature (span.kind is emitted as the raw Datadog value, e.g. "server", not the SMC's SPAN_KIND_* convention) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_8_status_code_error: missing_feature (status.code is emitted as an integer, e.g. 2, not the SMC's STATUS_CODE_ERROR string convention) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags::test_fr08_10_dd_tags_resource_attributes: missing_feature (DD_TAGS is not yet reported as datadog.tracer_tags on the OTLP trace metrics resource) diff --git a/manifests/java.yml b/manifests/java.yml index fef9900f74f..a2f5c0f65f4 100644 --- a/manifests/java.yml +++ b/manifests/java.yml @@ -3989,6 +3989,7 @@ manifest: component_version: <=1.23.0 tests/parametric/test_otlp_trace_metrics.py: v1.65.0-SNAPSHOT # Easy win for all weblogs and version 1.64.2 tests/parametric/test_otlp_trace_metrics.py::Test_FR01_Enablement_Configuration::test_fr01_6_disabled_when_apm_tracing_disabled: missing_feature (OTLP trace metrics are still emitted when DD_APM_TRACING_ENABLED=false) + tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_15_default_service_on_data_point: missing_feature (service.name is omitted from the data point when the span uses the configured default service) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_2_span_kind: missing_feature (span.kind is emitted as the raw Datadog value, e.g. "server", not the SMC's SPAN_KIND_* convention) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_8_status_code_error: missing_feature (status.code is emitted as the bare string "ERROR" instead of the SMC's STATUS_CODE_ERROR convention, and is omitted entirely for non-error spans) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags: missing_feature (DD_TAGS/OTEL_RESOURCE_ATTRIBUTES are not yet emitted as the datadog.tracer_tags resource attribute; support coming in a future PR) diff --git a/manifests/nodejs.yml b/manifests/nodejs.yml index 6f16942f86f..14f1cbe9cf6 100644 --- a/manifests/nodejs.yml +++ b/manifests/nodejs.yml @@ -2385,6 +2385,7 @@ manifest: - declaration: missing_feature (nodejs does not drop sampled-out traces when client-side stats are computed) component_version: <6.8.0 tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_10_hostname: irrelevant (DD_HOSTNAME is only supported in Python) + tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_15_default_service_on_data_point: missing_feature (service.name is omitted from the data point when the span uses the configured default service) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_2_span_kind: missing_feature (span.kind is emitted as the raw Datadog value, e.g. "server", not the SMC's SPAN_KIND_* convention) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_8_status_code_error: missing_feature (status.code is emitted as an integer, e.g. 2, not the SMC's STATUS_CODE_ERROR string convention) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags::test_fr08_10_dd_tags_resource_attributes: missing_feature (DD_TAGS is not yet reported as datadog.tracer_tags on the OTLP trace metrics resource) diff --git a/manifests/python.yml b/manifests/python.yml index 6848a845ebe..aaa19fd8477 100644 --- a/manifests/python.yml +++ b/manifests/python.yml @@ -2057,6 +2057,7 @@ manifest: tests/parametric/test_otel_tracer.py::Test_Otel_Tracer: v2.8.0 tests/parametric/test_otlp_trace_metrics.py: v4.13.0-dev # Easy win for all weblogs and version 4.12.2 tests/parametric/test_otlp_trace_metrics.py::Test_FR01_Enablement_Configuration::test_fr01_6_disabled_when_apm_tracing_disabled: missing_feature (OTLP trace metrics are still emitted when DD_APM_TRACING_ENABLED=false) + tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_15_default_service_on_data_point: missing_feature (service.name is omitted from the data point when the span uses the configured default service) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_2_span_kind: missing_feature (span.kind is emitted as the raw Datadog value, e.g. "server", not the SMC's SPAN_KIND_* convention) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_7_rpc_status_code: missing_feature (Requires libdatadog v38, will be added in future release) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_8_status_code_error: missing_feature (status.code is emitted as an integer, e.g. 2, not the SMC's STATUS_CODE_ERROR string convention) diff --git a/tests/parametric/capabilities.yml b/tests/parametric/capabilities.yml index 450bc80d965..b7e23e8e38e 100644 --- a/tests/parametric/capabilities.yml +++ b/tests/parametric/capabilities.yml @@ -61,6 +61,9 @@ capabilities: '>=1.54.0': - APM_TRACING_MULTICONFIG + '>=1.64.2': + - FFE_FLAG_CONFIGURATION_RULES + nodejs: base: - APM_TRACING_CUSTOM_TAGS diff --git a/tests/parametric/test_otlp_trace_metrics.py b/tests/parametric/test_otlp_trace_metrics.py index 83169d9a226..a95e6d88da9 100644 --- a/tests/parametric/test_otlp_trace_metrics.py +++ b/tests/parametric/test_otlp_trace_metrics.py @@ -32,8 +32,8 @@ telemetry.sdk.language (the library's OTel language token, e.g. "go" for golang). * service.name, service.version and deployment.environment.name are reported as resource attributes (the configured default service). No InstrumentationScope is emitted (it would be redundant with - the telemetry.sdk.* resource attributes); a span whose service differs from the configured default - additionally carries service.name on its data point. + the telemetry.sdk.* resource attributes); every data point also unconditionally carries its own + service.name, whether or not it matches the configured default. * OTLP metric flush/export cadence is fixed at 10s and is not overridable by OTEL_METRIC_EXPORT_INTERVAL. The internal _DD_TRACE_METRICS_OTEL_FLUSH_INTERVAL (milliseconds) shortens it in tests only. * Transport differs per library and is out of scope for parity: dd-trace-py exports HTTP/JSON only, @@ -255,7 +255,9 @@ def _resource_attributes(metrics: list[Any]) -> dict[str, Any]: def _data_point_services(metrics: list[Any]) -> set[Any]: - """Collect every service.name carried on a duration data point (custom/non-default services).""" + """Collect every service.name carried on a duration data point (every data point carries one, + including spans on the configured default service). + """ services: set[Any] = set() for data_point in _duration_data_points(metrics): service = _data_point_attrs(data_point).get("service.name") @@ -439,11 +441,11 @@ def test_fr01_5_disabled_when_tracing_is_disabled( @pytest.mark.parametrize( "library_env", - [{**DEFAULT_ENVVARS, "DD_APM_TRACING_ENABLED": "false"}], + [{**DEFAULT_ENVVARS_OTLP, "DD_APM_TRACING_ENABLED": "false"}], ) def test_fr01_6_disabled_when_apm_tracing_disabled( self, - otlp_trace_metrics_library_env: dict[str, str], # noqa: ARG002 + otlp_traces_and_metrics_library_env: dict[str, str], # noqa: ARG002 test_agent: TestAgentAPI, test_library: APMLibrary, ): @@ -515,11 +517,17 @@ class Test_FR02_Mutual_Exclusion: @pytest.mark.parametrize( "library_env", - [{**DEFAULT_ENVVARS, "DD_TRACE_STATS_COMPUTATION_ENABLED": "1"}], + [ + { + **DEFAULT_ENVVARS_OTLP, + "DD_TRACE_STATS_COMPUTATION_ENABLED": "1", + "DD_TRACE_TRACER_METRICS_ENABLED": "true", + } + ], ) def test_fr02_3_otlp_suppresses_native_stats( self, - otlp_trace_metrics_library_env: dict[str, str], # noqa: ARG002 + otlp_traces_and_metrics_library_env: dict[str, str], # noqa: ARG002 test_agent: TestAgentAPI, test_library: APMLibrary, ): @@ -870,11 +878,6 @@ def test_fr06_9_service_env_version( or resource_attrs.get("deployment.environment.name") == "prod" ), f"Expected deployment environment=prod, got: {resource_attrs}" - # The span uses the configured default service, so its data point omits service.name. - assert SERVICE not in _data_point_services(metrics), ( - f"Default service must not repeat on data points: {_data_point_services(metrics)}" - ) - @pytest.mark.parametrize("library_env", [{**DEFAULT_ENVVARS_OTLP}]) def test_fr06_14_custom_service_on_data_point( self, @@ -904,6 +907,27 @@ def test_fr06_14_custom_service_on_data_point( f"Expected postgres service.name on its data point: {services_on_points}" ) + @pytest.mark.parametrize("library_env", [{**DEFAULT_ENVVARS_OTLP}]) + def test_fr06_15_default_service_on_data_point( + self, + otlp_traces_and_metrics_library_env: dict[str, str], # noqa: ARG002 + test_agent: TestAgentAPI, + test_library: APMLibrary, + ): + """A span on the configured default service still carries service.name on its data point; + it is not omitted just because it matches the resource-level default. + """ + with test_library as t: + with t.dd_start_span(name="web.request", service=SERVICE, typestr="web"): + pass + t.dd_flush() + + metrics = _wait_for_otlp_metrics(test_agent) + services_on_points = _data_point_services(metrics) + assert SERVICE in services_on_points, ( + f"Expected default service {SERVICE} on its data point: {services_on_points}" + ) + @pytest.mark.parametrize( "library_env", [{**DEFAULT_ENVVARS_OTLP, "DD_HOSTNAME": "ddhostname", "DD_TRACE_REPORT_HOSTNAME": "true"}], @@ -1281,10 +1305,10 @@ def test_fr08_9_top_level_not_mixed_with_measured( f"point, got: {[_data_point_attrs(dp) for dp in points]}" ) - @pytest.mark.parametrize("library_env", [{**DEFAULT_ENVVARS}]) + @pytest.mark.parametrize("library_env", [{**DEFAULT_ENVVARS_OTLP}]) def test_fr08_13_is_trace_root( self, - otlp_trace_metrics_library_env: dict[str, str], # noqa: ARG002 + otlp_traces_and_metrics_library_env: dict[str, str], # noqa: ARG002 test_agent: TestAgentAPI, test_library: APMLibrary, ): @@ -1301,7 +1325,7 @@ def test_fr08_13_is_trace_root( same_service_child.set_metric(SPAN_MEASURED_KEY, 1) t.dd_flush() - metrics = test_agent.wait_for_num_otlp_metrics(num=1) + metrics = _wait_for_otlp_metrics(test_agent) points = _duration_data_points(metrics) root_point = _find_data_point(points, **{"datadog.operation.name": "web.request"}) @@ -1326,10 +1350,10 @@ def test_fr08_13_is_trace_root( f"A service-entry child is not the trace root; expected datadog.is_trace_root=false: {service_entry_attrs}" ) - @pytest.mark.parametrize("library_env", [{**DEFAULT_ENVVARS}]) + @pytest.mark.parametrize("library_env", [{**DEFAULT_ENVVARS_OTLP}]) def test_fr08_14_peer_tags( self, - otlp_trace_metrics_library_env: dict[str, str], # noqa: ARG002 + otlp_traces_and_metrics_library_env: dict[str, str], # noqa: ARG002 test_agent: TestAgentAPI, test_library: APMLibrary, ): @@ -1342,7 +1366,7 @@ def test_fr08_14_peer_tags( span.set_meta("db.hostname", "prod-db-1") t.dd_flush() - metrics = test_agent.wait_for_num_otlp_metrics(num=1) + metrics = _wait_for_otlp_metrics(test_agent) attrs = _data_point_attrs(_duration_data_points(metrics)[0]) peer_tags = attrs.get("datadog.peer_tags") or [] assert "db.hostname:prod-db-1" in peer_tags, f"Expected db.hostname:prod-db-1 in datadog.peer_tags: {attrs}" @@ -1383,7 +1407,7 @@ def test_fr08_10_dd_tags_resource_attributes( pass t.dd_flush() - metrics = test_agent.wait_for_num_otlp_metrics(num=1) + metrics = _wait_for_otlp_metrics(test_agent) resource_attrs = _resource_attributes(metrics) tracer_tags = resource_attrs.get("datadog.tracer_tags") or [] assert "team:apm" in tracer_tags, f"Expected team:apm in datadog.tracer_tags, got: {resource_attrs}" @@ -1414,7 +1438,7 @@ def test_fr08_11_otel_resource_attributes_env( pass t.dd_flush() - metrics = test_agent.wait_for_num_otlp_metrics(num=1) + metrics = _wait_for_otlp_metrics(test_agent) tracer_tags = _resource_attributes(metrics).get("datadog.tracer_tags") or [] assert "team:apm" in tracer_tags, f"Expected team:apm in datadog.tracer_tags, got: {tracer_tags}" assert "deployment.region:us-east-1" in tracer_tags, ( @@ -1440,7 +1464,7 @@ def test_fr08_12_stats_additional_tags( span.set_meta("region", "us-east-1") t.dd_flush() - metrics = test_agent.wait_for_num_otlp_metrics(num=1) + metrics = _wait_for_otlp_metrics(test_agent) attrs = _data_point_attrs(_duration_data_points(metrics)[0]) assert attrs.get("customer.tier") == "gold", f"Expected customer.tier=gold as its own attribute, got: {attrs}" assert attrs.get("region") == "us-east-1", f"Expected region=us-east-1 as its own attribute, got: {attrs}" From 3029fb745953dcb474af3b5af8f9488564cbd417 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Wed, 5 Aug 2026 01:53:03 -0400 Subject: [PATCH 13/16] test(otlp-trace-metrics): keep native trace export in test_fr02_3 test_fr02_3 asserts Datadog-Client-Computed-Stats on native trace requests, so keep DEFAULT_ENVVARS (not OTLP trace export) while still setting Java's DD_TRACE_TRACER_METRICS_ENABLED alongside DD_TRACE_STATS_COMPUTATION_ENABLED. Co-authored-by: Cursor --- tests/parametric/test_otlp_trace_metrics.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/parametric/test_otlp_trace_metrics.py b/tests/parametric/test_otlp_trace_metrics.py index a95e6d88da9..964eeb0a052 100644 --- a/tests/parametric/test_otlp_trace_metrics.py +++ b/tests/parametric/test_otlp_trace_metrics.py @@ -519,7 +519,7 @@ class Test_FR02_Mutual_Exclusion: "library_env", [ { - **DEFAULT_ENVVARS_OTLP, + **DEFAULT_ENVVARS, "DD_TRACE_STATS_COMPUTATION_ENABLED": "1", "DD_TRACE_TRACER_METRICS_ENABLED": "true", } @@ -527,7 +527,7 @@ class Test_FR02_Mutual_Exclusion: ) def test_fr02_3_otlp_suppresses_native_stats( self, - otlp_traces_and_metrics_library_env: dict[str, str], # noqa: ARG002 + otlp_trace_metrics_library_env: dict[str, str], # noqa: ARG002 test_agent: TestAgentAPI, test_library: APMLibrary, ): From 29959332451abcd51002ea507f21787cc5d5256b Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Wed, 5 Aug 2026 10:13:54 -0400 Subject: [PATCH 14/16] Remove missing feature tests from golang.yml Removed several tests marked as missing features from the golang manifest. --- manifests/golang.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/manifests/golang.yml b/manifests/golang.yml index 7d9acac5421..1391bc3f6e8 100644 --- a/manifests/golang.yml +++ b/manifests/golang.yml @@ -1534,15 +1534,8 @@ manifest: : "irrelevant (\"Go tracer decided to always set _dd1.sr.eausr: 1 for truthy analytics.event inputs, else 0\")" tests/parametric/test_otel_span_with_baggage.py::Test_Otel_Span_With_Baggage: missing_feature tests/parametric/test_otlp_trace_metrics.py: v2.11.0-dev + tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_15_default_service_on_data_point: missing_feature (default service not on data point) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags: missing_feature (datadog.tracer_tags / DD_TRACE_STATS_ADDITIONAL_TAGS per-key attrs not yet implemented) - tests/parametric/test_otlp_trace_metrics.py::Test_FR15_Client_Computed_Stats_Header: missing_feature - tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_9_service_env_version: missing_feature (deployment.environment resource mapping) - tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_11_hostname_omitted: missing_feature (host.name not omitted when DD_TRACE_REPORT_HOSTNAME is unset) - tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_12_telemetry_sdk_name: missing_feature (telemetry.sdk.name resource attribute) - tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_14_custom_service_on_data_point: missing_feature (custom service not on data point) - tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_7_rpc_status_code: missing_feature (rpc.response.status_code mapping incomplete) - tests/parametric/test_otlp_trace_metrics.py::Test_FR07_Otel_Semantics_Mode::test_fr07_2_no_datadog_resource_or_type: missing_feature (OTel-semantics mode still emits datadog resource/type attrs) - tests/parametric/test_otlp_trace_metrics.py::Test_FR07_Otel_Semantics_Mode::test_fr07_3_otel_attributes_present: missing_feature (OTel-semantics mode attribute set) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_2_span_type: missing_feature (datadog.span.type not emitted) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_6_origin: missing_feature (datadog.origin not emitted) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_13_is_trace_root: missing_feature (datadog.is_trace_root not emitted) From 9524079b712d01df48bb126e2d27cd2d5f4a7ae6 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Wed, 5 Aug 2026 10:44:26 -0400 Subject: [PATCH 15/16] fmt --- manifests/golang.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manifests/golang.yml b/manifests/golang.yml index 1391bc3f6e8..9fdec49aabe 100644 --- a/manifests/golang.yml +++ b/manifests/golang.yml @@ -1536,10 +1536,10 @@ manifest: tests/parametric/test_otlp_trace_metrics.py: v2.11.0-dev tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_15_default_service_on_data_point: missing_feature (default service not on data point) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags: missing_feature (datadog.tracer_tags / DD_TRACE_STATS_ADDITIONAL_TAGS per-key attrs not yet implemented) - tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_2_span_type: missing_feature (datadog.span.type not emitted) - tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_6_origin: missing_feature (datadog.origin not emitted) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_13_is_trace_root: missing_feature (datadog.is_trace_root not emitted) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_14_peer_tags: missing_feature (Blocked on test-agent support for advertising a peer_tags allowlist in /info) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_2_span_type: missing_feature (datadog.span.type not emitted) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_6_origin: missing_feature (datadog.origin not emitted) tests/parametric/test_parametric_endpoints.py::TestRemoteConfigApplyEndpoint: incomplete_test_app (POST /trace/remote-config/apply only implemented in the python parametric app) tests/parametric/test_parametric_endpoints.py::Test_Parametric_DDSpan_Add_Link: missing_feature (add_link endpoint is not implemented) tests/parametric/test_parametric_endpoints.py::Test_Parametric_DDSpan_Set_Resource: missing_feature (does not support setting a resource name after span creation) From 8b21a05b72645d3292460846084eb4792ec3bc1a Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Wed, 5 Aug 2026 11:43:14 -0400 Subject: [PATCH 16/16] Align OTLP trace metrics manifest skips with golang CI and enable python fr06_7 Add golang missing_feature skips for the four parametric failures on 2.11.0-dev.1 and drop fr08_2/fr08_6 skips that XPASS on dev. Enable python test_fr06_7_rpc_status_code at v4.14.0-dev once libdatadog v38 lands. Co-authored-by: Cursor --- manifests/golang.yml | 6 ++++-- manifests/python.yml | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/manifests/golang.yml b/manifests/golang.yml index 9fdec49aabe..dd6738a9086 100644 --- a/manifests/golang.yml +++ b/manifests/golang.yml @@ -1535,11 +1535,13 @@ manifest: tests/parametric/test_otel_span_with_baggage.py::Test_Otel_Span_With_Baggage: missing_feature tests/parametric/test_otlp_trace_metrics.py: v2.11.0-dev tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_15_default_service_on_data_point: missing_feature (default service not on data point) + tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_2_span_kind: missing_feature (span.kind is emitted as the raw Datadog value, e.g. "server", not the SMC's SPAN_KIND_* convention) + tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_8_status_code_error: missing_feature (status.code is emitted as an integer, e.g. 2, not the SMC's STATUS_CODE_ERROR string convention) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags: missing_feature (datadog.tracer_tags / DD_TRACE_STATS_ADDITIONAL_TAGS per-key attrs not yet implemented) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_13_is_trace_root: missing_feature (datadog.is_trace_root not emitted) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_14_peer_tags: missing_feature (Blocked on test-agent support for advertising a peer_tags allowlist in /info) - tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_2_span_type: missing_feature (datadog.span.type not emitted) - tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_6_origin: missing_feature (datadog.origin not emitted) + tests/parametric/test_otlp_trace_metrics.py::Test_FR08_Datadog_Attributes::test_fr08_8_process_tags: missing_feature (process_tags is split into per-key datadog. resource attributes instead of one combined datadog.process_tags arrayValue) + tests/parametric/test_otlp_trace_metrics.py::Test_FR09_Red_Metric_Derivation::test_fr09_2_error_count: missing_feature (status.code is emitted as an integer, e.g. 2, not the SMC's STATUS_CODE_ERROR string convention, so the error span is not counted) tests/parametric/test_parametric_endpoints.py::TestRemoteConfigApplyEndpoint: incomplete_test_app (POST /trace/remote-config/apply only implemented in the python parametric app) tests/parametric/test_parametric_endpoints.py::Test_Parametric_DDSpan_Add_Link: missing_feature (add_link endpoint is not implemented) tests/parametric/test_parametric_endpoints.py::Test_Parametric_DDSpan_Set_Resource: missing_feature (does not support setting a resource name after span creation) diff --git a/manifests/python.yml b/manifests/python.yml index 11b7e3331fd..df40ace7d0e 100644 --- a/manifests/python.yml +++ b/manifests/python.yml @@ -2060,7 +2060,7 @@ manifest: tests/parametric/test_otlp_trace_metrics.py::Test_FR01_Enablement_Configuration::test_fr01_6_disabled_when_apm_tracing_disabled: missing_feature (OTLP trace metrics are still emitted when DD_APM_TRACING_ENABLED=false) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Resource_Attributes::test_fr06_15_default_service_on_data_point: missing_feature (service.name is omitted from the data point when the span uses the configured default service) tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_2_span_kind: missing_feature (span.kind is emitted as the raw Datadog value, e.g. "server", not the SMC's SPAN_KIND_* convention) - tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_7_rpc_status_code: missing_feature (Requires libdatadog v38, will be added in future release) + tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_7_rpc_status_code: v4.14.0-dev tests/parametric/test_otlp_trace_metrics.py::Test_FR06_Otel_Span_Attributes::test_fr06_8_status_code_error: missing_feature (status.code is emitted as an integer, e.g. 2, not the SMC's STATUS_CODE_ERROR string convention) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags::test_fr08_10_dd_tags_resource_attributes: missing_feature (DD_TAGS is not yet reported as datadog.tracer_tags on the OTLP trace metrics resource) tests/parametric/test_otlp_trace_metrics.py::Test_FR08_AdditionalTags::test_fr08_11_otel_resource_attributes_env: missing_feature (OTEL_RESOURCE_ATTRIBUTES is not yet reported as datadog.tracer_tags on the OTLP trace metrics resource)