diff --git a/manifests/cpp.yml b/manifests/cpp.yml index 456af68e290..0d8325a4454 100644 --- a/manifests/cpp.yml +++ b/manifests/cpp.yml @@ -162,10 +162,14 @@ manifest: : missing_feature (_dd.p.dm does not change when a sampling priority was extracted) ? tests/parametric/test_headers_tracestate_dd.py::Test_Headers_Tracestate_DD::test_headers_tracestate_dd_propagate_propagatedtags_change_sampling_same_dm : missing_feature (_dd.p.dm is never dropped) + tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_agent_populated_fields_empty_TS013: missing_feature (cpp has not implemented stats computation yet) tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_distinct_aggregationkeys_TS003: missing_feature (cpp has not implemented stats computation yet) + tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_http_method_endpoint_TS011: missing_feature (cpp has not implemented stats computation yet) tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_measured_spans_TS004: missing_feature (cpp has not implemented stats computation yet) tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_metrics_computed_after_span_finsh_TS009: missing_feature (cpp has not implemented stats computation yet) tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_metrics_msgpack_serialization_TS001: missing_feature (cpp has not implemented stats computation yet) + tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_partial_version_excluded_TS014: missing_feature (cpp has not implemented stats computation yet) + tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_payload_metadata_TS012: missing_feature (cpp has not implemented stats computation yet) tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_relative_error_TS008: missing_feature (relative error test is broken) tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_sample_rate_0_TS007: missing_feature (cpp has not implemented stats computation yet) tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_successes_errors_recorded_separately_TS006: missing_feature (cpp has not implemented stats computation yet) diff --git a/manifests/dotnet.yml b/manifests/dotnet.yml index 67862ed6aa2..60b34386708 100644 --- a/manifests/dotnet.yml +++ b/manifests/dotnet.yml @@ -872,6 +872,7 @@ manifest: tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_metrics_msgpack_serialization_TS001: - declaration: missing_feature component_version: '<3.43.0' + tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_partial_version_excluded_TS014: missing_feature (CSS v1.2.0 - .NET does not exclude spans with _dd.partial_version from stats aggregation) tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_relative_error_TS008: missing_feature (relative error test is broken) tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_sample_rate_0_TS007: - declaration: missing_feature diff --git a/manifests/golang.yml b/manifests/golang.yml index 3ba93c502db..13e09ec9bde 100644 --- a/manifests/golang.yml +++ b/manifests/golang.yml @@ -1130,6 +1130,7 @@ manifest: - declaration: missing_feature (Not implemented) component_version: <1.64.0 tests/parametric/test_headers_tracestate_dd.py::Test_Headers_Tracestate_DD::test_headers_tracestate_dd_propagate_propagatedtags: "missing_feature (\"False Bug: header[3,6]: can't guarantee the order of strings in the tracestate since they came from the map. BUG: header[4,5]: w3cTraceID shouldn't be present\")" + tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_payload_metadata_TS012: missing_feature (CSS v1.2.0 - dd-trace-go does not set payload-level RuntimeID; stats.go PayloadAggregationKey omits it) tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_relative_error_TS008: missing_feature (relative error test is broken) tests/parametric/test_llm_observability/: incomplete_test_app tests/parametric/test_otel_api_interoperability.py: missing_feature diff --git a/manifests/java.yml b/manifests/java.yml index 4e4fe32674a..ea9f050b4a5 100644 --- a/manifests/java.yml +++ b/manifests/java.yml @@ -3682,6 +3682,7 @@ manifest: ? tests/parametric/test_headers_tracestate_dd.py::Test_Headers_Tracestate_DD::test_headers_tracestate_dd_evicts_32_or_greater_list_members : - declaration: missing_feature (Implemented in 1.24.0) component_version: <1.24.0 + tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_partial_version_excluded_TS014: missing_feature (CSS v1.2.0 - dd-trace-java exclusion uses internal longRunningVersion field, not the _dd.partial_version metric) tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_relative_error_TS008: missing_feature (relative error test is broken) tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_sample_rate_0_TS007: # Modified by easy win activation script - declaration: 'missing_feature (FIXME: Undefined behavior according the java tracer core team)' diff --git a/manifests/nodejs.yml b/manifests/nodejs.yml index 74fc3314f8b..6ea4b3dda1a 100644 --- a/manifests/nodejs.yml +++ b/manifests/nodejs.yml @@ -2025,15 +2025,13 @@ manifest: : "missing_feature (\"Issue: Does not reset dm to DEFAULT\")" ? tests/parametric/test_headers_tracestate_dd.py::Test_Headers_Tracestate_DD::test_headers_tracestate_dd_propagate_propagatedtags_change_sampling_same_dm : "missing_feature (\"Issue: the decision maker is removed. Is that allowed behavior?\")" - tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_distinct_aggregationkeys_TS003: missing_feature (nodejs has not implemented stats computation yet) - tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_measured_spans_TS004: missing_feature (nodejs has not implemented stats computation yet) - tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_metrics_computed_after_span_finish_TS010: missing_feature (nodejs has not implemented stats computation yet) - tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_metrics_computed_after_span_finsh_TS009: missing_feature (nodejs has not implemented stats computation yet) - tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_metrics_msgpack_serialization_TS001: missing_feature (nodejs has not implemented stats computation yet) + tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_distinct_aggregationkeys_TS003: "missing_feature (CSS v1.2.0: dd-trace-js aggregation key uses span name as Resource instead of the explicit resource field, so distinct-resource buckets collapse)" + tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_metrics_msgpack_serialization_TS001: "missing_feature (CSS v1.2.0: dd-trace-js sets ClientGroupedStats.Resource to the span name instead of the explicit resource passed to startSpan)" + tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_partial_version_excluded_TS014: "missing_feature (CSS v1.2.0: dd-trace-js does not filter spans with _dd.partial_version metric set; span_stats.js onSpanFinished only filters by TOP_LEVEL_KEY/MEASURED)" tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_relative_error_TS008: missing_feature (relative error test is broken) - tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_sample_rate_0_TS007: missing_feature (nodejs has not implemented stats computation yet) - tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_successes_errors_recorded_separately_TS006: missing_feature (nodejs has not implemented stats computation yet) - tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_top_level_TS005: missing_feature (nodejs has not implemented stats computation yet) + tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_sample_rate_0_TS007: "missing_feature (CSS v1.2.0: dd-trace-js does not drop P0 traces when DD_TRACE_SAMPLE_RATE=0; traces are still emitted with _sampling_priority_v1=-1 instead of being dropped client-side)" + tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_successes_errors_recorded_separately_TS006: "missing_feature (CSS v1.2.0: dd-trace-js groups error and success spans into the same aggregation bucket; ErrorSummary distinct from OkSummary is not produced)" + tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_top_level_TS005: "missing_feature (CSS v1.2.0: dd-trace-js does not produce stats for nested top-level (service-entry) child spans when parent and child are in different services; only the outer span yields a stats entry)" tests/parametric/test_llm_observability/test_llm_observability.py::Test_CostTags: missing_feature (cost_tags not yet supported in Node.js LLMObs SDK) tests/parametric/test_llm_observability/test_llm_observability.py::Test_Enablement: *ref_5_66_0 tests/parametric/test_llm_observability/test_llm_observability.py::Test_Prompts: *ref_5_83_0 diff --git a/manifests/php.yml b/manifests/php.yml index 3d7bce90815..06884443c75 100644 --- a/manifests/php.yml +++ b/manifests/php.yml @@ -783,17 +783,12 @@ manifest: tests/parametric/test_headers_tracestate_dd.py::Test_Headers_Tracestate_DD::test_headers_tracestate_dd_propagate_propagatedtags: - declaration: bug (APMAPI-1539) component_version: '>=1.11.0' - tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_distinct_aggregationkeys_TS003: missing_feature (php has not implemented stats computation yet) - tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_measured_spans_TS004: missing_feature (php has not implemented stats computation yet) - tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_metrics_computed_after_span_finish_TS010: + tests/parametric/test_library_tracestats.py::Test_Library_Tracestats: + - declaration: "missing_feature (CSS v1.2.0: dd-trace-php ships CSS since v1.19.0, but the sidecar stats exporter retains buckets younger than ~20s (buffer_len=2 * bucket_size=10s) and dd_trace_synchronous_flush passes force=false, so short-lived parametric tests never observe a /v0.6/stats request)" + component_version: '>=1.19.0' - declaration: missing_feature (php has not implemented stats computation yet) - component_version: <1.16.0 - tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_metrics_computed_after_span_finsh_TS009: missing_feature (php has not implemented stats computation yet) - tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_metrics_msgpack_serialization_TS001: missing_feature (php has not implemented stats computation yet) + component_version: <1.19.0 tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_relative_error_TS008: missing_feature (relative error test is broken) - tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_sample_rate_0_TS007: missing_feature (php has not implemented stats computation yet) - tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_successes_errors_recorded_separately_TS006: missing_feature (php has not implemented stats computation yet) - tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_top_level_TS005: missing_feature (php has not implemented stats computation yet) tests/parametric/test_llm_observability/: irrelevant (library does not implement LLM Observability) tests/parametric/test_otel_api_interoperability.py::Test_Otel_API_Interoperability: v0.94.0 tests/parametric/test_otel_env_vars.py::Test_Otel_Env_Vars: v1.1.0 diff --git a/manifests/ruby.yml b/manifests/ruby.yml index 9f85d26f120..984950006e2 100644 --- a/manifests/ruby.yml +++ b/manifests/ruby.yml @@ -1508,10 +1508,14 @@ manifest: : # Modified by easy win activation script - declaration: "missing_feature (\"Issue: does not escape '~' characters to '=' in _dd.p.usr.id\")" component_version: <2.27.0 + tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_agent_populated_fields_empty_TS013: missing_feature (ruby has not implemented stats computation yet) tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_distinct_aggregationkeys_TS003: missing_feature (ruby has not implemented stats computation yet) + tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_http_method_endpoint_TS011: missing_feature (ruby has not implemented stats computation yet) tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_measured_spans_TS004: missing_feature (ruby has not implemented stats computation yet) tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_metrics_computed_after_span_finsh_TS009: missing_feature (ruby has not implemented stats computation yet) tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_metrics_msgpack_serialization_TS001: missing_feature (ruby has not implemented stats computation yet) + tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_partial_version_excluded_TS014: missing_feature (ruby has not implemented stats computation yet) + tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_payload_metadata_TS012: missing_feature (ruby has not implemented stats computation yet) tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_relative_error_TS008: missing_feature (relative error test is broken) tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_sample_rate_0_TS007: missing_feature (ruby has not implemented stats computation yet) tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_successes_errors_recorded_separately_TS006: missing_feature (ruby has not implemented stats computation yet) diff --git a/manifests/rust.yml b/manifests/rust.yml index 3782b3a2aa5..3f2e9aac625 100644 --- a/manifests/rust.yml +++ b/manifests/rust.yml @@ -154,10 +154,14 @@ manifest: tests/parametric/test_headers_tracestate_dd.py::Test_Headers_Tracestate_DD: v0.0.1 tests/parametric/test_headers_tracestate_dd.py::Test_Headers_Tracestate_DD::test_headers_tracestate_dd_propagate_propagatedtags: missing_feature (can't guarantee the order of strings in the tracestate since they came from the map.) tests/parametric/test_library_tracestats.py::Test_Library_Tracestats: '>=0.2.1' # Modified by easy win activation script + tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_agent_populated_fields_empty_TS013: "missing_feature (CSS v1.2.0: dd-trace-rs has no client-side stats implementation)" tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_distinct_aggregationkeys_TS003: missing_feature # Created by easy win activation script + tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_http_method_endpoint_TS011: "missing_feature (CSS v1.2.0: dd-trace-rs has no client-side stats implementation)" tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_measured_spans_TS004: missing_feature # Created by easy win activation script tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_metrics_computed_after_span_finsh_TS009: missing_feature # Created by easy win activation script tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_metrics_msgpack_serialization_TS001: missing_feature # Created by easy win activation script + tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_partial_version_excluded_TS014: "missing_feature (CSS v1.2.0: dd-trace-rs has no client-side stats implementation)" + tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_payload_metadata_TS012: "missing_feature (CSS v1.2.0: dd-trace-rs has no client-side stats implementation)" tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_relative_error_TS008: missing_feature # Created by easy win activation script tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_sample_rate_0_TS007: missing_feature # Created by easy win activation script tests/parametric/test_library_tracestats.py::Test_Library_Tracestats::test_successes_errors_recorded_separately_TS006: missing_feature # Created by easy win activation script diff --git a/tests/parametric/test_library_tracestats.py b/tests/parametric/test_library_tracestats.py index 9676a1766e3..de60617808a 100644 --- a/tests/parametric/test_library_tracestats.py +++ b/tests/parametric/test_library_tracestats.py @@ -22,6 +22,21 @@ def _human_stats(stats: V06StatsAggr) -> str: return str(filtered_copy) +def _find_raw_v06_stats(test_agent: TestAgentAPI) -> dict: + """Return the deserialized raw /v0.6/stats payload from the test agent. + + The decoded view exposed by `test_agent.get_v06_stats_requests()` is intentionally narrow + (it omits fields like HTTPMethod, HTTPEndpoint, RuntimeID, Sequence). For spec assertions + on those fields we need the raw msgpack body. + """ + raw_body: str | None = None + for request in test_agent.requests(): + if "v0.6/stats" in request["url"]: + raw_body = request["body"] + assert raw_body is not None, "Could not find /v0.6/stats request in test agent transcript" + return msgpack.unpackb(base64.b64decode(raw_body)) + + def enable_tracestats(sample_rate: float | None = None) -> pytest.MarkDecorator: env = { "DD_TRACE_STATS_COMPUTATION_ENABLED": "1", # reference, dotnet, python, golang @@ -444,3 +459,154 @@ def test_metrics_computed_after_span_finish_TS010(self, test_agent: TestAgentAPI requests = test_agent.get_v06_stats_requests() assert len(requests) == 0, "No stats were computed" + + @parametrize( + "library_env", + [ + { + "DD_TRACE_STATS_COMPUTATION_ENABLED": "1", + "DD_TRACE_TRACER_METRICS_ENABLED": "true", + # dd-trace-java only extracts HTTPMethod/HTTPEndpoint when this is on. + "DD_TRACE_RESOURCE_RENAMING_ENABLED": "true", + } + ], + ) + @enable_agent_version() + def test_http_method_endpoint_TS011(self, test_agent: TestAgentAPI, test_library: APMLibrary): + """When spans carry HTTP method and endpoint metadata + The stats aggregation entry must include HTTPMethod and HTTPEndpoint fields populated from + the span's http.method and http.endpoint/http.route metadata. CSS spec v1.2.0 §5 (ClientGroupedStats). + """ + with ( + test_library, + test_library.dd_start_span(name="web.request", resource="GET /users/:id", service="webserver") as span, + ): + span.set_meta(key="span.kind", val="server") + span.set_meta(key="http.method", val="GET") + # Tracers diverge on which tag they read for HTTP_endpoint: dd-trace-go uses `http.endpoint`, + # others may use `http.route`. Set both so this test is implementation-agnostic. + span.set_meta(key="http.endpoint", val="/users/:id") + span.set_meta(key="http.route", val="/users/:id") + span.set_meta(key="http.status_code", val="200") + + raw_stats = _find_raw_v06_stats(test_agent) + stats_entries = raw_stats["Stats"][0]["Stats"] + web_entry = next((s for s in stats_entries if s.get("Name") == "web.request"), None) + assert web_entry is not None, f"web.request stats entry not found in {stats_entries}" + + assert web_entry.get("HTTPMethod") == "GET", ( + f"Expected HTTPMethod='GET' in stats, got: {web_entry.get('HTTPMethod')!r}" + ) + assert web_entry.get("HTTPEndpoint") == "/users/:id", ( + f"Expected HTTPEndpoint='/users/:id' in stats, got: {web_entry.get('HTTPEndpoint')!r}" + ) + + @parametrize( + "library_env", + [ + { + "DD_TRACE_STATS_COMPUTATION_ENABLED": "1", + "DD_TRACE_TRACER_METRICS_ENABLED": "true", + # dd-trace-go and dd-trace-java only populate Hostname when DD_TRACE_REPORT_HOSTNAME + # is on (option.go:297 / Config.java:2005). Pin both the flag and the value. + "DD_TRACE_REPORT_HOSTNAME": "true", + "DD_HOSTNAME": "test-host", + # Spec §3 calls out env/service/version as deployment-level identifiers. Java's + # WellKnownTags does not apply the spec's "unknown-env" default when DD_ENV is unset, + # so we pin all three explicitly for deterministic assertions across SDKs. + "DD_ENV": "tracestats-env", + "DD_SERVICE": "tracestats-service", + "DD_VERSION": "1.2.3", + } + ], + ) + @enable_agent_version() + def test_payload_metadata_TS012(self, test_agent: TestAgentAPI, test_library: APMLibrary): + """The ClientStatsPayload must include deployment-level metadata fields. + CSS spec v1.2.0 §3 mandates Hostname, Env, Version, Service, RuntimeID, and Sequence are + populated by the tracer (constant per tracer instance, deployment-level identifiers). + """ + with test_library, test_library.dd_start_span(name="web.request", resource="/users", service="webserver"): + pass + + raw_stats = _find_raw_v06_stats(test_agent) + + # Hostname / Env / Version / RuntimeID are deployment-wide and live at the payload level. + for field in ("Hostname", "Env", "Version", "RuntimeID"): + assert field in raw_stats, f"Required field {field!r} missing from payload: {list(raw_stats.keys())}" + value = raw_stats[field] + assert isinstance(value, str), f"{field} must be a string, got {type(value)}" + assert value, f"{field} must be a non-empty string, got {value!r}" + + # Sequence may legitimately be 0 on the first payload, so only require it's an int. + assert "Sequence" in raw_stats, f"Sequence missing from payload: {list(raw_stats.keys())}" + assert isinstance(raw_stats["Sequence"], int), f"Sequence must be int, got {type(raw_stats['Sequence'])}" + + # Service is allowed at the payload level OR at the per-bucket ClientGroupedStats level. + # dd-trace-go intentionally only writes it per-bucket (stats.go:181), and the trace-agent + # accepts that — it just loses one partition-key dimension during inter-payload aggregation + # (client_stats_aggregator.go:178). The bucket-level Service is the spec-required source of + # truth that the backend ultimately consumes. + payload_service = raw_stats.get("Service") or "" + bucket_services = { + s.get("Service", "") for bucket in raw_stats.get("Stats", []) for s in bucket.get("Stats", []) + } + assert payload_service or any(bucket_services), ( + f"Expected Service either at payload level ({payload_service!r}) or in any " + f"ClientGroupedStats ({bucket_services!r})" + ) + + @enable_tracestats() + @enable_agent_version() + def test_agent_populated_fields_empty_TS013(self, test_agent: TestAgentAPI, test_library: APMLibrary): + """The tracer must leave agent-populated fields empty in the ClientStatsPayload. + CSS spec v1.2.0 §3: ContainerID, Tags, ImageTag, AgentAggregation, and ProcessTagsHash + are populated by the agent and must be empty/absent when the payload leaves the tracer. + """ + with test_library, test_library.dd_start_span(name="web.request", resource="/users", service="webserver"): + pass + + raw_stats = _find_raw_v06_stats(test_agent) + + # Each of these may either be absent from the msgpack payload or present with an empty value. + for field in ("ContainerID", "ImageTag", "AgentAggregation"): + value = raw_stats.get(field) + assert value in (None, "", b""), ( + f"{field} must be left empty by the tracer for the agent to populate, got: {value!r}" + ) + + tags = raw_stats.get("Tags") + assert tags in (None, [], ()), f"Tags must be left empty for the agent to populate, got: {tags!r}" + + process_tags_hash = raw_stats.get("ProcessTagsHash") + assert process_tags_hash in (None, 0), ( + f"ProcessTagsHash must be left empty/zero for the agent to populate, got: {process_tags_hash!r}" + ) + + @enable_tracestats() + @enable_agent_version() + def test_partial_version_excluded_TS014(self, test_agent: TestAgentAPI, test_library: APMLibrary): + """Spans marked as partial snapshots (`_dd.partial_version` >= 0) must NOT contribute to stats. + CSS spec v1.2.0 §7 (Span Exclusions). + """ + with test_library: + # A normal top-level span — must produce stats. + with test_library.dd_start_span(name="web.request", resource="/users", service="webserver", typestr="web"): + pass + + # A span flagged as a partial snapshot — must NOT produce stats. + with test_library.dd_start_span( + name="partial.snapshot", resource="/partial", service="webserver", typestr="web" + ) as partial_span: + partial_span.set_metric(key="_dd.partial_version", val=0) + + raw_stats = _find_raw_v06_stats(test_agent) + stats_entries = raw_stats["Stats"][0]["Stats"] + names = {s.get("Name") for s in stats_entries} + + assert "web.request" in names, ( + f"Sanity check: regular span should produce stats, but web.request missing from {names}" + ) + assert "partial.snapshot" not in names, ( + f"Spans with _dd.partial_version set must be excluded from stats, but found in {names}" + ) diff --git a/utils/build/docker/nodejs/parametric/server.js b/utils/build/docker/nodejs/parametric/server.js index d3e5e76d757..cab0c85e2c5 100644 --- a/utils/build/docker/nodejs/parametric/server.js +++ b/utils/build/docker/nodejs/parametric/server.js @@ -203,8 +203,45 @@ app.post('/trace/span/manual_drop', (req, res) => { }); app.post('/trace/stats/flush', (req, res) => { - // TODO: implement once available in Node.js Tracer - res.json({}); + // dd-trace-js implements CSS via SpanStatsProcessor on the SpanProcessor. + // There is no public flush API, so reach into internals and wait for the + // span-stats writer's HTTP send to complete before responding. + const processor = tracer?._tracer?._processor?._stats + if (!processor) { + res.json({}) + return + } + const writer = processor.exporter?._writer + if (!writer || typeof writer._sendPayload !== 'function') { + processor.onInterval() + res.json({}) + return + } + const originalSend = writer._sendPayload.bind(writer) + let sendInvoked = false + let responded = false + const respond = () => { + if (responded) return + responded = true + writer._sendPayload = originalSend + res.json({}) + } + writer._sendPayload = (data, count, done) => { + sendInvoked = true + originalSend(data, count, () => { + done() + respond() + }) + } + try { + processor.onInterval() + } catch (e) { + respond() + return + } + if (!sendInvoked) { + respond() + } }); app.post('/trace/span/error', (req, res) => { diff --git a/utils/build/docker/python/parametric/apm_test_client/server.py b/utils/build/docker/python/parametric/apm_test_client/server.py index bf950b4a055..e2288df76f3 100644 --- a/utils/build/docker/python/parametric/apm_test_client/server.py +++ b/utils/build/docker/python/parametric/apm_test_client/server.py @@ -423,14 +423,29 @@ class TraceStatsFlushReturn(BaseModel): @app.post("/trace/stats/flush") def trace_stats_flush(args: TraceStatsFlushArgs) -> TraceStatsFlushReturn: - stats_proc = [ - p - for p in ddtrace.tracer._span_processors - if hasattr(ddtrace.internal.processor, "stats") - if isinstance(p, ddtrace.internal.processor.stats.SpanStatsProcessorV06) - ] - if len(stats_proc): - stats_proc[0].periodic() + # Legacy path: older dd-trace-py versions used a Python-side SpanStatsProcessorV06. + if hasattr(ddtrace.internal.processor, "stats"): + stats_proc = [ + p + for p in ddtrace.tracer._span_processors + if isinstance(p, ddtrace.internal.processor.stats.SpanStatsProcessorV06) + ] + if stats_proc: + stats_proc[0].periodic() + return TraceStatsFlushReturn() + + # Modern path: dd-trace-py >= 3.x delegates CSS to libdatadog's native TraceExporter. + # The exporter only emits /v0.6/stats on its internal 10-second timer or on shutdown, + # so we force a shutdown+recreate to flush stats deterministically for the test. + writer = getattr(ddtrace.tracer._span_aggregator, "writer", None) + if writer is not None and hasattr(writer, "on_shutdown") and hasattr(writer, "recreate"): + writer.on_shutdown() + try: + ddtrace.tracer._span_aggregator.writer = writer.recreate() + except Exception: + # If recreate is unavailable or raises, the writer is left stopped — acceptable + # since the test client is reset after each parametric test. + pass return TraceStatsFlushReturn()