diff --git a/manifests/dotnet.yml b/manifests/dotnet.yml index a93cee70c30..bd5c981052b 100644 --- a/manifests/dotnet.yml +++ b/manifests/dotnet.yml @@ -1026,10 +1026,16 @@ 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_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) + 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/golang.yml b/manifests/golang.yml index 9e3e8793f4d..dd6738a9086 100644 --- a/manifests/golang.yml +++ b/manifests/golang.yml @@ -1534,8 +1534,14 @@ 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_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_FR15_Client_Computed_Stats_Header: 'missing_feature' + 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_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/java.yml b/manifests/java.yml index 0fffc86f5af..1f168981bc6 100644 --- a/manifests/java.yml +++ b/manifests/java.yml @@ -3989,7 +3989,15 @@ 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_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) + 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 c489b9660fd..bba0e151b30 100644 --- a/manifests/nodejs.yml +++ b/manifests/nodejs.yml @@ -2381,12 +2381,22 @@ 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_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_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) + 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 1bc3f1f11f9..df40ace7d0e 100644 --- a/manifests/python.yml +++ b/manifests/python.yml @@ -2057,8 +2057,18 @@ 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 # Easy win for all weblogs and version 4.12.2 - 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_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_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: 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) + 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 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 6601ca31a24..964eeb0a052 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,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, @@ -41,12 +41,19 @@ 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 -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 resource 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 / +boolValue); _dd.stats_computed is a string-valued Datadog convention. """ import base64 @@ -79,8 +86,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") -# OTel StatusCode (span status) values that denote an error, across possible serializations. -ERROR_STATUS_VALUES: tuple[Any, ...] = (2, "ERROR", "STATUS_CODE_ERROR") # 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 = { @@ -94,8 +99,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", @@ -250,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") @@ -408,7 +415,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_traces_and_metrics_library_env: dict[str, str], # noqa: ARG002 test_agent: TestAgentAPI, @@ -432,6 +439,32 @@ def test_fr01_5_disabled_when_traces_exporter_not_otlp( ) time.sleep(0.2) + @pytest.mark.parametrize( + "library_env", + [{**DEFAULT_ENVVARS_OTLP, "DD_APM_TRACING_ENABLED": "false"}], + ) + def test_fr01_6_disabled_when_apm_tracing_disabled( + self, + otlp_traces_and_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 @@ -482,7 +515,16 @@ 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", + "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 @@ -491,6 +533,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"): @@ -701,7 +746,7 @@ def test_fr06_2_span_kind( metrics = _wait_for_otlp_metrics(test_agent) attrs = _data_point_attrs(_duration_data_points(metrics)[0]) - assert attrs.get("span.kind") == "server", f"Expected span.kind=server, 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_OTLP}]) def test_fr06_3_http_method( @@ -795,9 +840,7 @@ def test_fr06_8_status_code_error( metrics = _wait_for_otlp_metrics(test_agent) 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 @@ -816,8 +859,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 its data - point omits service.name. + deployment.environment.name / service.version. """ with test_library as t: with t.dd_start_span(name="web.request", service=SERVICE, typestr="web"): @@ -836,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, @@ -848,10 +885,9 @@ 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. + """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"): @@ -866,12 +902,30 @@ 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. + # 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}" ) - assert SERVICE not in services_on_points, ( - f"Default service must not repeat on data points: {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( @@ -1205,11 +1259,9 @@ def test_fr08_8_process_tags( test_agent: TestAgentAPI, test_library: APMLibrary, ): - """Process tags are emitted as individual datadog. resource attributes in default mode. - - 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. + """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"): @@ -1217,9 +1269,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}]) @@ -1253,11 +1305,77 @@ 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_OTLP}]) + def test_fr08_13_is_trace_root( + self, + otlp_traces_and_metrics_library_env: dict[str, str], # noqa: ARG002 + test_agent: TestAgentAPI, + test_library: APMLibrary, + ): + """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") 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 = _wait_for_otlp_metrics(test_agent) + 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)}" + ) + + 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.parametrize("library_env", [{**DEFAULT_ENVVARS_OTLP}]) + def test_fr08_14_peer_tags( + self, + otlp_traces_and_metrics_library_env: dict[str, str], # noqa: ARG002 + test_agent: TestAgentAPI, + test_library: APMLibrary, + ): + """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="postgres.query", service="postgres", typestr="db") as span: + span.set_meta("db.hostname", "prod-db-1") + t.dd_flush() + + 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}" + @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). """ @@ -1280,22 +1398,23 @@ 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"): 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_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}" @@ -1312,18 +1431,18 @@ 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"): pass 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}" + 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, ( + f"Expected deployment.region:us-east-1 in datadog.tracer_tags, got: {tracer_tags}" ) @pytest.mark.parametrize( @@ -1336,8 +1455,8 @@ 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. + """Span tags named in DD_TRACE_STATS_ADDITIONAL_TAGS are split into individual data-point + 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: @@ -1345,15 +1464,10 @@ 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]) - 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}" @scenarios.parametric @@ -1437,7 +1551,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}"