From 3526fd54af38faeb0658598ab6105409d44e9606 Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Thu, 4 Dec 2025 17:26:50 +0100 Subject: [PATCH 1/3] fix: Makes the test agent report version 7.65.0 for client side stats --- tests/parametric/test_library_tracestats.py | 52 ++++++++++++++------- tests/parametric/test_span_sampling.py | 9 ++-- utils/_context/_scenarios/__init__.py | 3 ++ 3 files changed, 43 insertions(+), 21 deletions(-) diff --git a/tests/parametric/test_library_tracestats.py b/tests/parametric/test_library_tracestats.py index 42884480ab7..08bc70e70c6 100644 --- a/tests/parametric/test_library_tracestats.py +++ b/tests/parametric/test_library_tracestats.py @@ -22,7 +22,7 @@ def _human_stats(stats: V06StatsAggr) -> str: return str(filtered_copy) -def enable_tracestats(sample_rate: float | None = None) -> pytest.MarkDecorator: +def enable_tracestats(sample_rate: float | None = None) -> tuple[pytest.MarkDecorator, pytest.MarkDecorator]: env = { "DD_TRACE_STATS_COMPUTATION_ENABLED": "1", # reference, dotnet, python, golang "DD_TRACE_TRACER_METRICS_ENABLED": "true", # java @@ -32,20 +32,25 @@ def enable_tracestats(sample_rate: float | None = None) -> pytest.MarkDecorator: if sample_rate is not None: assert 0 <= sample_rate <= 1.0 env.update({"DD_TRACE_SAMPLE_RATE": str(sample_rate)}) - return parametrize("library_env", [env]) + + # Java tracer requires agent version >= 7.65.0 for client-side stats + agent_env_config = {"TEST_AGENT_VERSION": "7.65.0"} + + return (parametrize("library_env", [env]), parametrize("agent_env", [agent_env_config])) @scenarios.parametric @features.client_side_stats_supported class Test_Library_Tracestats: - @enable_tracestats() + @enable_tracestats()[0] + @enable_tracestats()[1] @missing_feature(context.library == "cpp", reason="cpp has not implemented stats computation yet") @missing_feature(context.library == "nodejs", reason="nodejs has not implemented stats computation yet") @missing_feature(context.library == "php", reason="php has not implemented stats computation yet") @missing_feature(context.library == "ruby", reason="ruby has not implemented stats computation yet") @bug(context.library >= "dotnet@3.19.0", reason="APMSP-2074") def test_metrics_msgpack_serialization_TS001( - self, library_env: dict[str, str], test_agent: TestAgentAPI, test_library: APMLibrary + self, library_env: dict[str, str], agent_env: dict[str, str], test_agent: TestAgentAPI, test_library: APMLibrary ): """When spans are finished Each trace has stats metrics computed for it serialized properly in msgpack format with required fields @@ -94,14 +99,15 @@ def test_metrics_msgpack_serialization_TS001( for key in ("Hostname", "Env", "Version", "Stats"): assert key in decoded_request_body, f"{key} should be in stats request" - @enable_tracestats() + @enable_tracestats()[0] + @enable_tracestats()[1] @missing_feature(context.library == "cpp", reason="cpp has not implemented stats computation yet") @missing_feature(context.library == "nodejs", reason="nodejs has not implemented stats computation yet") @missing_feature(context.library == "php", reason="php has not implemented stats computation yet") @missing_feature(context.library == "ruby", reason="ruby has not implemented stats computation yet") @bug(context.library >= "dotnet@3.19.0", reason="APMSP-2074") def test_distinct_aggregationkeys_TS003( - self, library_env: dict[str, str], test_agent: TestAgentAPI, test_library: APMLibrary + self, library_env: dict[str, str], agent_env: dict[str, str], test_agent: TestAgentAPI, test_library: APMLibrary ): """When spans are created with a unique set of dimensions Each span has stats computed for it and is in its own bucket @@ -182,14 +188,15 @@ def test_distinct_aggregationkeys_TS003( "There should be seven stats entries in the bucket. There is one baseline entry and 6 that are unique along each of 6 dimensions." ) + @enable_tracestats()[0] + @enable_tracestats()[1] @missing_feature(context.library == "cpp", reason="cpp has not implemented stats computation yet") @missing_feature(context.library == "nodejs", reason="nodejs has not implemented stats computation yet") @missing_feature(context.library == "php", reason="php has not implemented stats computation yet") @missing_feature(context.library == "ruby", reason="ruby has not implemented stats computation yet") - @enable_tracestats() @bug(context.library >= "dotnet@3.19.0", reason="APMSP-2074") def test_measured_spans_TS004( - self, library_env: dict[str, str], test_agent: TestAgentAPI, test_library: APMLibrary + self, library_env: dict[str, str], agent_env: dict[str, str], test_agent: TestAgentAPI, test_library: APMLibrary ): """When spans are marked as measured Each has stats computed for it @@ -227,13 +234,16 @@ def test_measured_spans_TS004( assert op2_stats["Hits"] == 1 assert op2_stats["TopLevelHits"] == 0 + @enable_tracestats()[0] + @enable_tracestats()[1] @missing_feature(context.library == "cpp", reason="cpp has not implemented stats computation yet") @missing_feature(context.library == "nodejs", reason="nodejs has not implemented stats computation yet") @missing_feature(context.library == "php", reason="php has not implemented stats computation yet") @missing_feature(context.library == "ruby", reason="ruby has not implemented stats computation yet") - @enable_tracestats() @bug(context.library >= "dotnet@3.19.0", reason="APMSP-2074") - def test_top_level_TS005(self, library_env: dict[str, str], test_agent: TestAgentAPI, test_library: APMLibrary): + def test_top_level_TS005( + self, library_env: dict[str, str], agent_env: dict[str, str], test_agent: TestAgentAPI, test_library: APMLibrary + ): """When top level (service entry) spans are created Each top level span has trace stats computed for it. """ @@ -279,14 +289,15 @@ def test_top_level_TS005(self, library_env: dict[str, str], test_agent: TestAgen assert web_stats["TopLevelHits"] == 1 assert web_stats["Duration"] > 0 + @enable_tracestats()[0] + @enable_tracestats()[1] @missing_feature(context.library == "cpp", reason="cpp has not implemented stats computation yet") @missing_feature(context.library == "nodejs", reason="nodejs has not implemented stats computation yet") @missing_feature(context.library == "php", reason="php has not implemented stats computation yet") @missing_feature(context.library == "ruby", reason="ruby has not implemented stats computation yet") - @enable_tracestats() @bug(context.library >= "dotnet@3.19.0", reason="APMSP-2074") def test_successes_errors_recorded_separately_TS006( - self, library_env: dict[str, str], test_agent: TestAgentAPI, test_library: APMLibrary + self, library_env: dict[str, str], agent_env: dict[str, str], test_agent: TestAgentAPI, test_library: APMLibrary ): """When spans are marked as errors The errors count is incremented appropriately and the stats are aggregated into the ErrorSummary @@ -336,14 +347,17 @@ def test_successes_errors_recorded_separately_TS006( assert stat["OkSummary"] is not None assert stat["ErrorSummary"] is not None + @enable_tracestats(sample_rate=0.0)[0] + @enable_tracestats(sample_rate=0.0)[1] @missing_feature(context.library == "cpp", reason="cpp has not implemented stats computation yet") @missing_feature(context.library == "java", reason="FIXME: Undefined behavior according the java tracer core team") @missing_feature(context.library == "nodejs", reason="nodejs has not implemented stats computation yet") @missing_feature(context.library == "php", reason="php has not implemented stats computation yet") @missing_feature(context.library == "ruby", reason="ruby has not implemented stats computation yet") - @enable_tracestats(sample_rate=0.0) @bug(context.library >= "dotnet@3.19.0", reason="APMSP-2074") - def test_sample_rate_0_TS007(self, library_env: dict[str, str], test_agent: TestAgentAPI, test_library: APMLibrary): + def test_sample_rate_0_TS007( + self, library_env: dict[str, str], agent_env: dict[str, str], test_agent: TestAgentAPI, test_library: APMLibrary + ): """When the sample rate is 0 and trace stats is enabled non-P0 traces should be dropped trace stats should be produced @@ -363,10 +377,11 @@ def test_sample_rate_0_TS007(self, library_env: dict[str, str], test_agent: Test assert web_stats["TopLevelHits"] == 1 assert web_stats["Hits"] == 1 + @enable_tracestats()[0] + @enable_tracestats()[1] @missing_feature(reason="relative error test is broken") - @enable_tracestats() def test_relative_error_TS008( - self, library_env: dict[str, str], test_agent: TestAgentAPI, test_library: APMLibrary + self, library_env: dict[str, str], agent_env: dict[str, str], test_agent: TestAgentAPI, test_library: APMLibrary ): """When trace stats are computed for traces The stats should be accurate to within 1% of the real values @@ -410,14 +425,15 @@ def test_relative_error_TS008( rel=0.01, ), f"Quantile mismatch for quantile {quantile!r}" + @enable_tracestats()[0] + @enable_tracestats()[1] @missing_feature(context.library == "cpp", reason="cpp has not implemented stats computation yet") @missing_feature(context.library == "nodejs", reason="nodejs has not implemented stats computation yet") @missing_feature(context.library == "php", reason="php has not implemented stats computation yet") @missing_feature(context.library == "ruby", reason="ruby has not implemented stats computation yet") - @enable_tracestats() @bug(context.library >= "dotnet@3.19.0", reason="APMSP-2074") def test_metrics_computed_after_span_finsh_TS009( - self, library_env: dict[str, str], test_agent: TestAgentAPI, test_library: APMLibrary + self, library_env: dict[str, str], agent_env: dict[str, str], test_agent: TestAgentAPI, test_library: APMLibrary ): """When trace stats are computed for traces Metrics must be computed after spans are finished, otherwise components of the aggregation key may change after diff --git a/tests/parametric/test_span_sampling.py b/tests/parametric/test_span_sampling.py index ac7e9939a62..5412a55d284 100644 --- a/tests/parametric/test_span_sampling.py +++ b/tests/parametric/test_span_sampling.py @@ -695,8 +695,9 @@ def test_child_span_selected_by_sss015(self, test_agent: TestAgentAPI, test_libr } ], ) + @pytest.mark.parametrize("agent_env", [{"TEST_AGENT_VERSION": "7.65.0"}]) def test_root_span_selected_and_child_dropped_by_sss_when_dropping_policy_is_active016( - self, test_agent: TestAgentAPI, test_library: APMLibrary + self, library_env: dict[str, str], agent_env: dict[str, str], test_agent: TestAgentAPI, test_library: APMLibrary ): """Single spans selected by SSS must be kept and other spans expected to be dropped on the tracer side when dropping policy is active when tracer metrics enabled. @@ -768,8 +769,9 @@ def test_root_span_selected_and_child_dropped_by_sss_when_dropping_policy_is_act } ], ) + @pytest.mark.parametrize("agent_env", [{"TEST_AGENT_VERSION": "7.65.0"}]) def test_child_span_selected_and_root_dropped_by_sss_when_dropping_policy_is_active017( - self, test_agent: TestAgentAPI, test_library: APMLibrary + self, library_env: dict[str, str], agent_env: dict[str, str], test_agent: TestAgentAPI, test_library: APMLibrary ): """Single spans selected by SSS must be kept and other spans expected to be dropped on the tracer side when dropping policy is active when tracer metrics enabled. @@ -835,8 +837,9 @@ def test_child_span_selected_and_root_dropped_by_sss_when_dropping_policy_is_act } ], ) + @pytest.mark.parametrize("agent_env", [{"TEST_AGENT_VERSION": "7.65.0"}]) def test_entire_trace_dropped_when_dropping_policy_is_active018( - self, test_agent: TestAgentAPI, test_library: APMLibrary + self, library_env: dict[str, str], agent_env: dict[str, str], test_agent: TestAgentAPI, test_library: APMLibrary ): """The entire dropped span expected to be dropped on the tracer side when dropping policy is active, which is the case when tracer metrics enabled. diff --git a/utils/_context/_scenarios/__init__.py b/utils/_context/_scenarios/__init__.py index ee326e853ca..fc67b47515e 100644 --- a/utils/_context/_scenarios/__init__.py +++ b/utils/_context/_scenarios/__init__.py @@ -81,6 +81,9 @@ class _Scenarios: "DD_TRACE_FEATURES": "discovery", "DD_TRACE_TRACER_METRICS_ENABLED": "true", # java }, + agent_env={ + "TEST_AGENT_VERSION": "7.65.0", # Required for Java tracer >= 1.54.0 client-side stats + }, doc=( "End to end testing with DD_TRACE_COMPUTE_STATS=1. This feature compute stats at tracer level, and" "may drop some of them" From 58d460bdb2a921c58fc5c673b6c61317a10fe62f Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Fri, 5 Dec 2025 10:46:19 +0100 Subject: [PATCH 2/3] fix: PR Review: parameterize via a second method --- tests/parametric/test_library_tracestats.py | 43 +++++++++++---------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/tests/parametric/test_library_tracestats.py b/tests/parametric/test_library_tracestats.py index 08bc70e70c6..cd86730aa1b 100644 --- a/tests/parametric/test_library_tracestats.py +++ b/tests/parametric/test_library_tracestats.py @@ -22,7 +22,7 @@ def _human_stats(stats: V06StatsAggr) -> str: return str(filtered_copy) -def enable_tracestats(sample_rate: float | None = None) -> tuple[pytest.MarkDecorator, pytest.MarkDecorator]: +def enable_tracestats(sample_rate: float | None = None) -> pytest.MarkDecorator: env = { "DD_TRACE_STATS_COMPUTATION_ENABLED": "1", # reference, dotnet, python, golang "DD_TRACE_TRACER_METRICS_ENABLED": "true", # java @@ -33,17 +33,20 @@ def enable_tracestats(sample_rate: float | None = None) -> tuple[pytest.MarkDeco assert 0 <= sample_rate <= 1.0 env.update({"DD_TRACE_SAMPLE_RATE": str(sample_rate)}) - # Java tracer requires agent version >= 7.65.0 for client-side stats - agent_env_config = {"TEST_AGENT_VERSION": "7.65.0"} + return parametrize("library_env", [env]) - return (parametrize("library_env", [env]), parametrize("agent_env", [agent_env_config])) + +def enable_agent_version(version: str = "7.65.0") -> pytest.MarkDecorator: + """Set the test agent version. Java tracer requires agent version >= 7.65.0 for client-side stats.""" + agent_env_config = {"TEST_AGENT_VERSION": version} + return parametrize("agent_env", [agent_env_config]) @scenarios.parametric @features.client_side_stats_supported class Test_Library_Tracestats: - @enable_tracestats()[0] - @enable_tracestats()[1] + @enable_tracestats() + @enable_agent_version() @missing_feature(context.library == "cpp", reason="cpp has not implemented stats computation yet") @missing_feature(context.library == "nodejs", reason="nodejs has not implemented stats computation yet") @missing_feature(context.library == "php", reason="php has not implemented stats computation yet") @@ -99,8 +102,8 @@ def test_metrics_msgpack_serialization_TS001( for key in ("Hostname", "Env", "Version", "Stats"): assert key in decoded_request_body, f"{key} should be in stats request" - @enable_tracestats()[0] - @enable_tracestats()[1] + @enable_tracestats() + @enable_agent_version() @missing_feature(context.library == "cpp", reason="cpp has not implemented stats computation yet") @missing_feature(context.library == "nodejs", reason="nodejs has not implemented stats computation yet") @missing_feature(context.library == "php", reason="php has not implemented stats computation yet") @@ -188,8 +191,8 @@ def test_distinct_aggregationkeys_TS003( "There should be seven stats entries in the bucket. There is one baseline entry and 6 that are unique along each of 6 dimensions." ) - @enable_tracestats()[0] - @enable_tracestats()[1] + @enable_tracestats() + @enable_agent_version() @missing_feature(context.library == "cpp", reason="cpp has not implemented stats computation yet") @missing_feature(context.library == "nodejs", reason="nodejs has not implemented stats computation yet") @missing_feature(context.library == "php", reason="php has not implemented stats computation yet") @@ -234,8 +237,8 @@ def test_measured_spans_TS004( assert op2_stats["Hits"] == 1 assert op2_stats["TopLevelHits"] == 0 - @enable_tracestats()[0] - @enable_tracestats()[1] + @enable_tracestats() + @enable_agent_version() @missing_feature(context.library == "cpp", reason="cpp has not implemented stats computation yet") @missing_feature(context.library == "nodejs", reason="nodejs has not implemented stats computation yet") @missing_feature(context.library == "php", reason="php has not implemented stats computation yet") @@ -289,8 +292,8 @@ def test_top_level_TS005( assert web_stats["TopLevelHits"] == 1 assert web_stats["Duration"] > 0 - @enable_tracestats()[0] - @enable_tracestats()[1] + @enable_tracestats() + @enable_agent_version() @missing_feature(context.library == "cpp", reason="cpp has not implemented stats computation yet") @missing_feature(context.library == "nodejs", reason="nodejs has not implemented stats computation yet") @missing_feature(context.library == "php", reason="php has not implemented stats computation yet") @@ -347,8 +350,8 @@ def test_successes_errors_recorded_separately_TS006( assert stat["OkSummary"] is not None assert stat["ErrorSummary"] is not None - @enable_tracestats(sample_rate=0.0)[0] - @enable_tracestats(sample_rate=0.0)[1] + @enable_tracestats(sample_rate=0.0) + @enable_agent_version() @missing_feature(context.library == "cpp", reason="cpp has not implemented stats computation yet") @missing_feature(context.library == "java", reason="FIXME: Undefined behavior according the java tracer core team") @missing_feature(context.library == "nodejs", reason="nodejs has not implemented stats computation yet") @@ -377,8 +380,8 @@ def test_sample_rate_0_TS007( assert web_stats["TopLevelHits"] == 1 assert web_stats["Hits"] == 1 - @enable_tracestats()[0] - @enable_tracestats()[1] + @enable_tracestats() + @enable_agent_version() @missing_feature(reason="relative error test is broken") def test_relative_error_TS008( self, library_env: dict[str, str], agent_env: dict[str, str], test_agent: TestAgentAPI, test_library: APMLibrary @@ -425,8 +428,8 @@ def test_relative_error_TS008( rel=0.01, ), f"Quantile mismatch for quantile {quantile!r}" - @enable_tracestats()[0] - @enable_tracestats()[1] + @enable_tracestats() + @enable_agent_version() @missing_feature(context.library == "cpp", reason="cpp has not implemented stats computation yet") @missing_feature(context.library == "nodejs", reason="nodejs has not implemented stats computation yet") @missing_feature(context.library == "php", reason="php has not implemented stats computation yet") From 3ceeb74c921956afb97f58918d01fd2f7d506c0f Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Fri, 5 Dec 2025 10:48:43 +0100 Subject: [PATCH 3/3] fix: PR Review: revert trace_stats_computation changes --- utils/_context/_scenarios/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/utils/_context/_scenarios/__init__.py b/utils/_context/_scenarios/__init__.py index fc67b47515e..ee326e853ca 100644 --- a/utils/_context/_scenarios/__init__.py +++ b/utils/_context/_scenarios/__init__.py @@ -81,9 +81,6 @@ class _Scenarios: "DD_TRACE_FEATURES": "discovery", "DD_TRACE_TRACER_METRICS_ENABLED": "true", # java }, - agent_env={ - "TEST_AGENT_VERSION": "7.65.0", # Required for Java tracer >= 1.54.0 client-side stats - }, doc=( "End to end testing with DD_TRACE_COMPUTE_STATS=1. This feature compute stats at tracer level, and" "may drop some of them"