diff --git a/docs/understand/weblogs/end-to-end_weblog.md b/docs/understand/weblogs/end-to-end_weblog.md index 967040e6cdf..23fa0d6127b 100644 --- a/docs/understand/weblogs/end-to-end_weblog.md +++ b/docs/understand/weblogs/end-to-end_weblog.md @@ -972,6 +972,16 @@ These endpoints are used for the `Dynamic Instrumentation` tests. Executes the same probe line `loops` times during one request. It is used to verify per-probe and global Dynamic Instrumentation rate limits. +#### GET /debugger/correlation + +Calls `Correlation`, `CorrelationMiddle`, and `CorrelationLeaf` in one request, pausing between the probed method exits. Used to check that snapshot sampling emits a whole probe chain together. + +#### GET /debugger/correlation/loop/ + +Runs a loop `count` times in one request, sleeping one second per iteration. The loop body and the line right +after the loop are probed. Used to check that a probe emits at most one snapshot per trace, without starving a +sibling probe in the same trace. + #### GET /debugger/snapshot/capture-timeout Creates a collection containing `collectionSize` object chains. Each chain has a leaf wrapped by diff --git a/manifests/dotnet.yml b/manifests/dotnet.yml index cbd41d62906..6ca0e3edece 100644 --- a/manifests/dotnet.yml +++ b/manifests/dotnet.yml @@ -764,6 +764,7 @@ manifest: weblog: [uds, poc] - declaration: bug (DEBUG-4746) excluded_weblog: [uds, poc] + tests/debugger/test_debugger_probe_budgets.py::Test_Debugger_Probe_Budgets::test_per_trace_budget: missing_feature (per-trace snapshot budget of 1 not implemented) tests/debugger/test_debugger_probe_budgets.py::Test_Debugger_Probe_Budgets::test_span_probe_expression_budgets: irrelevant (Dotnet emits evaluation errors as span tags, not error snapshots to the debugger endpoint) tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots: v2.53.0 tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots::test_default_max_length: @@ -786,6 +787,8 @@ manifest: - declaration: bug (DEBUG-5101) component_version: '<3.45.0' tests/debugger/test_debugger_probe_status.py::Test_Debugger_Method_Probe_Statuses: v2.53.0 + tests/debugger/test_debugger_snapshot_correlation.py::Test_Debugger_Coordinated_Sampling: v3.55.0 # DEBUG-5828: per-trace coordinated snapshot sampling (#9266) + tests/debugger/test_debugger_snapshot_correlation.py::Test_Debugger_Coordinated_Sampling_Line_Probe: irrelevant (Line-probe variant is only used by Node.js) tests/debugger/test_debugger_symdb.py::Test_Debugger_SymDb: v2.53.0 tests/debugger/test_debugger_symdb.py::Test_Debugger_SymDb::test_event_metadata: v3.44.0 tests/debugger/test_debugger_symdb.py::Test_Debugger_SymDb::test_symdb_upload: # Modified by easy win activation script diff --git a/manifests/golang.yml b/manifests/golang.yml index d4b4e0e1934..eba92300bab 100644 --- a/manifests/golang.yml +++ b/manifests/golang.yml @@ -1667,6 +1667,7 @@ manifest: tests/debugger/test_debugger_pii.py::Test_Debugger_PII_Redaction: missing_feature (feature not implemented) tests/debugger/test_debugger_pii.py::Test_Debugger_PII_Redaction_Excluded_Identifiers: missing_feature (feature not implemented) tests/debugger/test_debugger_probe_budgets.py::Test_Debugger_Probe_Budgets: v2.2.3 + tests/debugger/test_debugger_probe_budgets.py::Test_Debugger_Probe_Budgets::test_per_trace_budget: missing_feature (per-trace snapshot budget of 1 not implemented) tests/debugger/test_debugger_probe_budgets.py::Test_Debugger_Probe_Budgets::test_span_probe_expression_budgets: missing_feature (span decoration probes not yet implemented for Go) tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots: v2.2.3 tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots::test_default_max_collection_size: missing_feature (no /debugger/snapshot/limits endpoint) @@ -1703,6 +1704,8 @@ manifest: tests/debugger/test_debugger_probe_status.py::Test_Debugger_Method_Probe_Statuses::test_metric_status: missing_feature (Not yet implemented) tests/debugger/test_debugger_probe_status.py::Test_Debugger_Method_Probe_Statuses::test_span_decoration_method_status: missing_feature (Not yet implemented) tests/debugger/test_debugger_probe_status.py::Test_Debugger_Method_Probe_Statuses::test_span_method_status: missing_feature (Not yet implemented) + tests/debugger/test_debugger_snapshot_correlation.py::Test_Debugger_Coordinated_Sampling: missing_feature (no per-trace coordinated sampling; probabilistic per-trace rate not implemented) + tests/debugger/test_debugger_snapshot_correlation.py::Test_Debugger_Coordinated_Sampling_Line_Probe: irrelevant (Line-probe variant is only used by Node.js) tests/debugger/test_debugger_symdb.py::Test_Debugger_SymDb: v2.2.3 # TODO(andrei): un-skip when datadog-agent v7.79.0 is released (Go's symdb upload comes from the agent, not from dd-trace-go) tests/debugger/test_debugger_symdb.py::Test_Debugger_SymDb::test_event_metadata: # TODO: a lower version might be supported diff --git a/manifests/java.yml b/manifests/java.yml index 6f7fcc2d46f..f10cc7b81ac 100644 --- a/manifests/java.yml +++ b/manifests/java.yml @@ -3316,6 +3316,7 @@ manifest: spring-boot-undertow: v1.38.0 spring-boot-wildfly: v1.38.0 uds-spring-boot: v1.38.0 + tests/debugger/test_debugger_probe_budgets.py::Test_Debugger_Probe_Budgets::test_per_trace_budget: incomplete_test_app (no /debugger/correlation/loop endpoint) tests/debugger/test_debugger_probe_budgets.py::Test_Debugger_Probe_Budgets::test_span_probe_expression_budgets: bug (DEBUG-3797) tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots: - weblog_declaration: @@ -3396,6 +3397,8 @@ manifest: spring-boot-undertow: v1.38.0 spring-boot-wildfly: v1.38.0 uds-spring-boot: v1.38.0 + tests/debugger/test_debugger_snapshot_correlation.py::Test_Debugger_Coordinated_Sampling: incomplete_test_app (no /debugger/correlation endpoint) + tests/debugger/test_debugger_snapshot_correlation.py::Test_Debugger_Coordinated_Sampling_Line_Probe: irrelevant (Line-probe variant is only used by Node.js) tests/debugger/test_debugger_symdb.py::Test_Debugger_SymDb: - weblog_declaration: "*": missing_feature diff --git a/manifests/nodejs.yml b/manifests/nodejs.yml index 94b6334410c..6e857dd851b 100644 --- a/manifests/nodejs.yml +++ b/manifests/nodejs.yml @@ -110,6 +110,7 @@ refs: - &ref_6_17_0 '>=6.17.0 || ^5.128.0' - &ref_6_20_0 '>=6.20.0 || ^5.131.0' - &ref_6_20_1 '>=6.20.1 || ^5.131.1' + - &ref_6_21_0 '>=6.21.0 || ^5.132.0' manifest: tests/ai_guard/test_ai_guard_sdk.py::Test_AIGuardEvent_Tag: - weblog_declaration: @@ -1788,6 +1789,7 @@ manifest: fastify: *ref_5_46_0 uds-express4: *ref_5_46_0 tests/debugger/test_debugger_probe_budgets.py::Test_Debugger_Probe_Budgets::test_log_line_capture_expression_budgets: '>=6.16.0 || ^5.127.0' + tests/debugger/test_debugger_probe_budgets.py::Test_Debugger_Probe_Budgets::test_per_trace_budget: *ref_6_21_0 tests/debugger/test_debugger_probe_budgets.py::Test_Debugger_Probe_Budgets::test_span_probe_expression_budgets: missing_feature (Method probes are not supported by Node.js) tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots: - weblog_declaration: @@ -1846,6 +1848,11 @@ manifest: tests/debugger/test_debugger_probe_status.py::Test_Debugger_Line_Probe_Statuses::test_metric_line_status: missing_feature (Not yet implemented) tests/debugger/test_debugger_probe_status.py::Test_Debugger_Line_Probe_Statuses::test_span_decoration_line_status: missing_feature (Not yet implemented) tests/debugger/test_debugger_probe_status.py::Test_Debugger_Method_Probe_Statuses: missing_feature + tests/debugger/test_debugger_snapshot_correlation.py::Test_Debugger_Coordinated_Sampling: irrelevant (Method-probe variant is not used by Node.js) + tests/debugger/test_debugger_snapshot_correlation.py::Test_Debugger_Coordinated_Sampling_Line_Probe: + - weblog_declaration: + "*": *ref_6_21_0 + nextjs: irrelevant tests/debugger/test_debugger_symdb.py::Test_Debugger_SymDb: missing_feature (feature not implented) tests/debugger/test_debugger_telemetry.py::Test_Debugger_Telemetry: - weblog_declaration: diff --git a/manifests/php.yml b/manifests/php.yml index c99643f4dc8..e77a7ef3e13 100644 --- a/manifests/php.yml +++ b/manifests/php.yml @@ -1190,6 +1190,7 @@ manifest: laravel11x: incomplete_test_app symfony7x: incomplete_test_app tests/debugger/test_debugger_probe_budgets.py::Test_Debugger_Probe_Budgets::test_log_line_capture_expression_budgets: missing_feature + tests/debugger/test_debugger_probe_budgets.py::Test_Debugger_Probe_Budgets::test_per_trace_budget: missing_feature (per-trace snapshot budget of 1 not implemented) tests/debugger/test_debugger_probe_budgets.py::Test_Debugger_Probe_Budgets::test_span_probe_expression_budgets: irrelevant tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots: missing_feature tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots::test_process_tags_snapshot: missing_feature (Not yet implemented) @@ -1219,6 +1220,8 @@ manifest: apache-mod-8.0: v1.8.3 - declaration: flaky (DEBUG-3814) component_version: '>1.8.3' + tests/debugger/test_debugger_snapshot_correlation.py::Test_Debugger_Coordinated_Sampling: missing_feature (no per-trace coordinated sampling; probabilistic per-trace rate not implemented) + tests/debugger/test_debugger_snapshot_correlation.py::Test_Debugger_Coordinated_Sampling_Line_Probe: irrelevant (Line-probe variant is only used by Node.js) tests/debugger/test_debugger_symdb.py::Test_Debugger_SymDb: missing_feature tests/debugger/test_debugger_telemetry.py::Test_Debugger_Telemetry: - weblog_declaration: diff --git a/manifests/python.yml b/manifests/python.yml index 80f14f5e4aa..b4509f429a4 100644 --- a/manifests/python.yml +++ b/manifests/python.yml @@ -2110,6 +2110,7 @@ manifest: tests/debugger/test_debugger_probe_budgets.py::Test_Debugger_Probe_Budgets::test_log_line_capture_expression_budgets: - declaration: bug (DEBUG-5743) component_version: <4.12.0 + tests/debugger/test_debugger_probe_budgets.py::Test_Debugger_Probe_Budgets::test_per_trace_budget: missing_feature (per-trace snapshot budget of 1 not implemented; tracer uses a time-based budget) tests/debugger/test_debugger_probe_budgets.py::Test_Debugger_Probe_Budgets::test_span_probe_expression_budgets: irrelevant (Python emits evaluation errors as span tags, not error snapshots to the debugger endpoint) tests/debugger/test_debugger_probe_snapshot.py: - weblog_declaration: @@ -2169,6 +2170,8 @@ manifest: component_version: 2.16.0 - declaration: bug (DEBUG-3127) component_version: 2.16.1 + tests/debugger/test_debugger_snapshot_correlation.py::Test_Debugger_Coordinated_Sampling: missing_feature (no per-trace coordinated sampling; probabilistic per-trace rate not implemented) + tests/debugger/test_debugger_snapshot_correlation.py::Test_Debugger_Coordinated_Sampling_Line_Probe: irrelevant (Line-probe variant is only used by Node.js) tests/debugger/test_debugger_symdb.py::Test_Debugger_SymDb: - weblog_declaration: "*": missing_feature diff --git a/manifests/ruby.yml b/manifests/ruby.yml index 5a7cf90a6ae..2001e82eddf 100644 --- a/manifests/ruby.yml +++ b/manifests/ruby.yml @@ -1998,6 +1998,7 @@ manifest: rails80: missing_feature (feature not implemented) uds-rails: missing_feature (feature not implemented) tests/debugger/test_debugger_probe_budgets.py::Test_Debugger_Probe_Budgets::test_log_line_budgets: missing_feature (Not yet implemented) + tests/debugger/test_debugger_probe_budgets.py::Test_Debugger_Probe_Budgets::test_per_trace_budget: missing_feature (per-trace snapshot budget of 1 not implemented) tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots: - weblog_declaration: "*": irrelevant @@ -2062,6 +2063,8 @@ manifest: tests/debugger/test_debugger_probe_status.py::Test_Debugger_Method_Probe_Statuses::test_metric_status: missing_feature (Not yet implemented) tests/debugger/test_debugger_probe_status.py::Test_Debugger_Method_Probe_Statuses::test_span_decoration_method_status: missing_feature (Not yet implemented) tests/debugger/test_debugger_probe_status.py::Test_Debugger_Method_Probe_Statuses::test_span_method_status: missing_feature (Not yet implemented) + tests/debugger/test_debugger_snapshot_correlation.py::Test_Debugger_Coordinated_Sampling: missing_feature (no per-trace coordinated sampling; probabilistic per-trace rate not implemented) + tests/debugger/test_debugger_snapshot_correlation.py::Test_Debugger_Coordinated_Sampling_Line_Probe: irrelevant (Line-probe variant is only used by Node.js) tests/debugger/test_debugger_symdb.py::Test_Debugger_SymDb: - weblog_declaration: "*": irrelevant diff --git a/mirror_images.lock.yaml b/mirror_images.lock.yaml index fc975ace23a..8562942aaf5 100644 --- a/mirror_images.lock.yaml +++ b/mirror_images.lock.yaml @@ -110,26 +110,26 @@ images: digest: sha256:c2d8a505dfd121933b113e0717f8fbe2e8352f4533d99c85cc5456d9aaf77bde target: registry.ddbuild.io/system-tests/mirror/datadog/system-tests:django-py3.13.base-8fa5fb488bac tag: django-py3.13.base-8fa5fb488bac - datadog/system-tests:express4-typescript.base-be5138693a38: - digest: sha256:6e66aa1b5da64a0d04a108c78f35c3f9f26a941f8233160c5eb78f08d0d3834d - target: registry.ddbuild.io/system-tests/mirror/datadog/system-tests:express4-typescript.base-be5138693a38 - tag: express4-typescript.base-be5138693a38 - datadog/system-tests:express4.base-7a16285d66a9: - digest: sha256:3b3365bb8748df3ff993459149705c08a127589ccba72a1a5691f8905b742db2 - target: registry.ddbuild.io/system-tests/mirror/datadog/system-tests:express4.base-7a16285d66a9 - tag: express4.base-7a16285d66a9 - datadog/system-tests:express5.base-fb385bd8cf2f: - digest: sha256:bd622fa20feba4e54e5c4aebe3e069b9f9b5ffb8a3d016c66749640e44c943fd - target: registry.ddbuild.io/system-tests/mirror/datadog/system-tests:express5.base-fb385bd8cf2f - tag: express5.base-fb385bd8cf2f + datadog/system-tests:express4-typescript.base-070fd1ba6efd: + digest: sha256:c805fcf6a950f68d3b1fe25102dfbe4a3175374857475ad417154e1e53ebc959 + target: registry.ddbuild.io/system-tests/mirror/datadog/system-tests:express4-typescript.base-070fd1ba6efd + tag: express4-typescript.base-070fd1ba6efd + datadog/system-tests:express4.base-527c2a306057: + digest: sha256:5960fa4fcfdd56fee4754847c6400c78b6d1d4cc3f6093954ea9fa959ef6cd24 + target: registry.ddbuild.io/system-tests/mirror/datadog/system-tests:express4.base-527c2a306057 + tag: express4.base-527c2a306057 + datadog/system-tests:express5.base-5bff656d2059: + digest: sha256:56f6df4d24495b11e13e122f117d0938a3b86fc9b14c4d30e94bfb88e2999946 + target: registry.ddbuild.io/system-tests/mirror/datadog/system-tests:express5.base-5bff656d2059 + tag: express5.base-5bff656d2059 datadog/system-tests:fastapi.base-7f1b67ecf0c2: digest: sha256:6ee0374d82b4cb777189b4e5f9a6ceb8697ee92be2ab455691445b1c86e2df4a target: registry.ddbuild.io/system-tests/mirror/datadog/system-tests:fastapi.base-7f1b67ecf0c2 tag: fastapi.base-7f1b67ecf0c2 - datadog/system-tests:fastify.base-8f4f6cf42bfb: - digest: sha256:4ebcb8a7ec9cf2152633cec3d907d90ce50c076420e98e15e2322f9bb25682d8 - target: registry.ddbuild.io/system-tests/mirror/datadog/system-tests:fastify.base-8f4f6cf42bfb - tag: fastify.base-8f4f6cf42bfb + datadog/system-tests:fastify.base-d8786a519f91: + digest: sha256:e7006ea8fb96c22809b44721f6aefaaa249f2d49de5cfaeff9ad5df290c13526 + target: registry.ddbuild.io/system-tests/mirror/datadog/system-tests:fastify.base-d8786a519f91 + tag: fastify.base-d8786a519f91 datadog/system-tests:flask-poc.base-ac207107ea25: digest: sha256:1df044be7f3766d2f8208420b010d485bf270f98f955344997029ac732b3fca4 target: registry.ddbuild.io/system-tests/mirror/datadog/system-tests:flask-poc.base-ac207107ea25 diff --git a/mirror_images.yaml b/mirror_images.yaml index a422e046b5f..c1f3b879448 100644 --- a/mirror_images.yaml +++ b/mirror_images.yaml @@ -35,11 +35,11 @@ - "datadog/system-tests:apache-mod-8.2.base-5771d89e2b55" - "datadog/system-tests:django-poc.base-1d15b551f35b" - "datadog/system-tests:django-py3.13.base-8fa5fb488bac" -- "datadog/system-tests:express4-typescript.base-be5138693a38" -- "datadog/system-tests:express4.base-7a16285d66a9" -- "datadog/system-tests:express5.base-fb385bd8cf2f" +- "datadog/system-tests:express4-typescript.base-070fd1ba6efd" +- "datadog/system-tests:express4.base-527c2a306057" +- "datadog/system-tests:express5.base-5bff656d2059" - "datadog/system-tests:fastapi.base-7f1b67ecf0c2" -- "datadog/system-tests:fastify.base-8f4f6cf42bfb" +- "datadog/system-tests:fastify.base-d8786a519f91" - "datadog/system-tests:flask-poc.base-ac207107ea25" - "datadog/system-tests:golang_buddy-v2" - "datadog/system-tests:java-lambda-runtime.base-e9a494d2bc57" diff --git a/tests/debugger/test_debugger_probe_budgets.py b/tests/debugger/test_debugger_probe_budgets.py index e6977660247..f0a4db16212 100644 --- a/tests/debugger/test_debugger_probe_budgets.py +++ b/tests/debugger/test_debugger_probe_budgets.py @@ -168,3 +168,35 @@ def test_span_probe_expression_budgets(self): f"{self.total_request_time:.1f}s), got {total_error_entries}. " f"Span probe expression error logging should be rate-limited." ) + + def setup_per_trace_budget(self): + self.initialize_weblog_remote_config() + + language = self.get_tracer()["language"] + + probes = [] + for method in ("CorrelationLoopBody", "CorrelationLoopSibling"): + probe = debugger.read_probes("probe_snapshot_log_correlation_loop")[0] + probe["id"] = debugger.generate_probe_id("log") + self._rewrite_where_for_lines([probe], self.method_and_language_to_line_number(method, language)) + probes.append(probe) + + self.set_probes(probes) + self.send_rc_probes() + self.wait_for_all_probes(statuses=["INSTALLED"]) + + # The loop sleeps one second per iteration, so a time-based budget would emit more than one + # snapshot while a trace-scoped budget emits exactly one. + self.send_weblog_request("/debugger/correlation/loop/3") + + self.wait_for_all_probes(statuses=["EMITTING"]) + self.wait_for_all_snapshots() + + def test_per_trace_budget(self): + """A probe in a loop emits one snapshot per trace and does not starve a sibling probe.""" + self._assert() + + # A single request is a single trace, so every probe must have emitted exactly once. + for probe_id in self.probe_ids: + count = len(self.probe_snapshots.get(probe_id, [])) + assert count == 1, f"probe {probe_id} emitted {count} snapshots in one trace, expected exactly 1" diff --git a/tests/debugger/test_debugger_snapshot_correlation.py b/tests/debugger/test_debugger_snapshot_correlation.py new file mode 100644 index 00000000000..4b7b38bef49 --- /dev/null +++ b/tests/debugger/test_debugger_snapshot_correlation.py @@ -0,0 +1,100 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the the Apache License Version 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2021 Datadog, Inc. + +import tests.debugger.utils as debugger + +from utils import context, scenarios, features, rfc, slow + +_RFC = "https://docs.google.com/document/d/17BQ1cEcJuumpMWMtBItIx9x_akG99dzjBA1NoaBFuBE/edit?tab=t.4l0zjxovcnz3#heading=h.kejjaf501thi" + + +class _DebuggerCoordinatedSamplingTest(debugger.BaseDebuggerTest): + """The sampling decision is made once per trace, so a trace emits its whole probe chain or none of it. + + As the RFC specifies, the first snapshot probe hit in a trace decides for the whole trace. If it + drops, every probe in the trace drops. If it emits, every probe in the trace may emit until it + reaches its cap of one snapshot per trace, even where its own per-probe rate would have skipped it. + + Correlation rides on the dd.trace_id already in the snapshot envelope. The probes get different + per-probe rates, so independent per-probe sampling emits partial chains, which is what makes the + assertion discriminating. + """ + + _INVOCATIONS = 30 + # The leaf samples every request on its own. A request takes about 800ms, so without coordination + # the probes limited to one snapshot per second are skipped in about every other request while the + # leaf is not. With line probes on the return lines, the leaf is also the first probe hit, so it + # makes the trace's decision. + _SNAPSHOTS_PER_SECOND = {"CorrelationLeaf": 5, "CorrelationMiddle": 1, "Correlation": 1} + + def _prepare_probe(self, probe: dict) -> None: + pass + + def setup_coordinated_per_trace_sampling(self): + self.initialize_weblog_remote_config() + + probes = debugger.read_probes("probe_snapshot_log_correlation") + for probe in probes: + probe["id"] = debugger.generate_probe_id("log") + probe["sampling"] = {"snapshotsPerSecond": self._SNAPSHOTS_PER_SECOND[probe["where"]["methodName"]]} + self._prepare_probe(probe) + self.set_probes(probes) + + self.send_rc_probes() + if not self.wait_for_all_probes(statuses=["INSTALLED"], timeout=60): + self.setup_failures.append("Probes did not reach INSTALLED status") + return + + # Each request is one trace, so it is one sampling unit. + for i in range(self._INVOCATIONS): + self.send_weblog_request("/debugger/correlation", reset=(i == 0)) + + self.wait_for_all_probes(statuses=["EMITTING"]) + self.wait_for_all_snapshots() + + def test_coordinated_per_trace_sampling(self): + """Every trace emits either all of its probes or none of them.""" + self.collect() + + self.assert_setup_ok() + self.assert_rc_state_not_error() + self.assert_all_weblog_responses_ok() + + chain = set(self.probe_ids) + + probes_by_trace: dict[str, set[str]] = {} + for probe_id in self.probe_ids: + for snapshot in self.probe_snapshots.get(probe_id, []): + # Tracers send either a nested dd object or a flat dd.trace_id key. + trace_id = snapshot.get("dd", {}).get("trace_id") or snapshot.get("dd.trace_id") + assert trace_id, f"snapshot for probe {probe_id} is missing dd.trace_id on the wire" + probes_by_trace.setdefault(trace_id, set()).add(probe_id) + + full = [trace_id for trace_id, probes in probes_by_trace.items() if probes == chain] + partial = [trace_id for trace_id, probes in probes_by_trace.items() if probes != chain] + + assert not partial, ( + f"partial probe chains were emitted, the sampling decision is not made once per trace: {partial}" + ) + assert full, "no trace emitted the full probe chain" + + +@rfc(_RFC) +@features.debugger_snapshot_correlation +@scenarios.debugger_probes_snapshot +@slow +class Test_Debugger_Coordinated_Sampling(_DebuggerCoordinatedSamplingTest): + """Probes the methods of the correlation endpoint.""" + + +@rfc(_RFC) +@features.debugger_snapshot_correlation +@scenarios.debugger_probes_snapshot +@slow +class Test_Debugger_Coordinated_Sampling_Line_Probe(_DebuggerCoordinatedSamplingTest): + """Probes the line each method of the correlation endpoint returns on, for tracers without method probes.""" + + def _prepare_probe(self, probe: dict) -> None: + lines = self.method_and_language_to_line_number(probe["where"]["methodName"], context.library.name) + self._rewrite_where_for_lines([probe], lines) diff --git a/tests/debugger/utils.py b/tests/debugger/utils.py index 059a8347664..50db8eaf538 100644 --- a/tests/debugger/utils.py +++ b/tests/debugger/utils.py @@ -190,7 +190,7 @@ def initialize_weblog_remote_config(self) -> None: def method_and_language_to_line_number(self, method: str, language: str) -> list: """method_and_language_to_line_number returns the respective line number given the method and language""" definitions: dict[str, dict[str, list[int]]] = { - "Budgets": {"java": [140], "dotnet": [138], "python": [144], "golang": [117], "nodejs": [163]}, + "Budgets": {"java": [140], "dotnet": [138], "python": [144], "golang": [117], "nodejs": [165]}, "LogProbe": {"nodejs": [20]}, "Pii": {"java": [66], "dotnet": [66], "python": [66], "ruby": [66], "nodejs": [64]}, "Expression": {"java": [73], "dotnet": [76], "python": [74], "ruby": [82], "nodejs": [82], "golang": [71]}, @@ -212,7 +212,13 @@ def method_and_language_to_line_number(self, method: str, language: str) -> list "CollectionOperations": {"java": [116], "dotnet": [116], "python": [125], "ruby": [162], "nodejs": [120]}, "Nulls": {"java": [132], "dotnet": [129], "python": [138], "ruby": [192], "nodejs": [126]}, "SnapshotLimits": {"java": [155], "python": [174], "nodejs": [136], "ruby": [233], "dotnet": [152]}, - "CaptureTimeout": {"java": [174], "nodejs": [157], "dotnet": [173], "python": [183]}, + "CaptureTimeout": {"java": [174], "nodejs": [159], "dotnet": [173], "python": [183]}, + # Lines the probed methods of the correlation endpoint return on, for tracers without method probes + "Correlation": {"nodejs": [178]}, + "CorrelationMiddle": {"nodejs": [184]}, + "CorrelationLeaf": {"nodejs": [188]}, + "CorrelationLoopBody": {"golang": [144], "nodejs": [194]}, + "CorrelationLoopSibling": {"golang": [147], "nodejs": [197]}, } return definitions.get(method, {}).get(language, []) @@ -355,6 +361,8 @@ def __get_probe_type(probe_id: str): "20": "main.(*DebuggerController).logProbe", "71": "main.(*DebuggerController).expression", "117": "main.(*DebuggerController).budgetStep", + "144": "main.(*DebuggerController).correlationLoop", + "147": "main.(*DebuggerController).correlationLoop", } line = str(probe["where"]["lines"][0]) if line in golang_line_to_method: diff --git a/tests/debugger/utils/probes/probe_snapshot_log_correlation.json b/tests/debugger/utils/probes/probe_snapshot_log_correlation.json new file mode 100644 index 00000000000..df0803049b4 --- /dev/null +++ b/tests/debugger/utils/probes/probe_snapshot_log_correlation.json @@ -0,0 +1,38 @@ +[ + { + "language": "", + "type": "", + "id": "", + "version": 0, + "where": { + "typeName": "ACTUAL_TYPE_NAME", + "methodName": "Correlation", + "sourceFile": null + }, + "captureSnapshot": true + }, + { + "language": "", + "type": "", + "id": "", + "version": 0, + "where": { + "typeName": "ACTUAL_TYPE_NAME", + "methodName": "CorrelationMiddle", + "sourceFile": null + }, + "captureSnapshot": true + }, + { + "language": "", + "type": "", + "id": "", + "version": 0, + "where": { + "typeName": "ACTUAL_TYPE_NAME", + "methodName": "CorrelationLeaf", + "sourceFile": null + }, + "captureSnapshot": true + } +] diff --git a/tests/debugger/utils/probes/probe_snapshot_log_correlation_loop.json b/tests/debugger/utils/probes/probe_snapshot_log_correlation_loop.json new file mode 100644 index 00000000000..a547c04ed5f --- /dev/null +++ b/tests/debugger/utils/probes/probe_snapshot_log_correlation_loop.json @@ -0,0 +1,15 @@ +[ + { + "language": "", + "type": "", + "id": "", + "version": 0, + "where": { + "typeName": null, + "sourceFile": "ACTUAL_SOURCE_FILE", + "lines": [] + }, + "captureSnapshot": true, + "tags": [] + } +] diff --git a/utils/_features.py b/utils/_features.py index 4a5ce48125b..ea347ab3a9e 100644 --- a/utils/_features.py +++ b/utils/_features.py @@ -2335,6 +2335,19 @@ def debugger_probe_budgets(test_object): """ return _mark_test_object(test_object, feature_id=368, owner=_Owner.debugger) + @staticmethod + def debugger_snapshot_correlation(test_object): + """Coordinated per-trace snapshot sampling (Live Debugger) + + The sampling decision is made once per trace and applies to every probe in that trace, + with snapshots correlated by the dd.trace_id already carried in the snapshot envelope (no + snapshot payload change). Runtime-id-in-envelope and the execution-context generation + token are separate follow-on work and are not part of this feature. + + https://feature-parity.us1.prod.dog/#/?feature=564 + """ + return _mark_test_object(test_object, feature_id=564, owner=_Owner.debugger) + @staticmethod def debugger_method_probe(test_object): """Method-level Probes diff --git a/utils/build/docker/base-images.lock.json b/utils/build/docker/base-images.lock.json index d2f79d4f9ca..578a921c155 100644 --- a/utils/build/docker/base-images.lock.json +++ b/utils/build/docker/base-images.lock.json @@ -2,10 +2,10 @@ "version": 1, "images": { "system_tests_base_java_lambda_java_lambda_runtime": "datadog/system-tests:java-lambda-runtime.base-e9a494d2bc57", - "system_tests_base_nodejs_express4": "datadog/system-tests:express4.base-7a16285d66a9", - "system_tests_base_nodejs_express4_typescript": "datadog/system-tests:express4-typescript.base-be5138693a38", - "system_tests_base_nodejs_express5": "datadog/system-tests:express5.base-fb385bd8cf2f", - "system_tests_base_nodejs_fastify": "datadog/system-tests:fastify.base-8f4f6cf42bfb", + "system_tests_base_nodejs_express4": "datadog/system-tests:express4.base-527c2a306057", + "system_tests_base_nodejs_express4_typescript": "datadog/system-tests:express4-typescript.base-070fd1ba6efd", + "system_tests_base_nodejs_express5": "datadog/system-tests:express5.base-5bff656d2059", + "system_tests_base_nodejs_fastify": "datadog/system-tests:fastify.base-d8786a519f91", "system_tests_base_nodejs_lambda_nodejs_lambda_runtime": "datadog/system-tests:nodejs-lambda-runtime.base-93b9660a8ce9", "system_tests_base_nodejs_nextjs": "datadog/system-tests:nextjs.base-5c42165eff3e", "system_tests_base_php_apache_mod_7_0": "datadog/system-tests:apache-mod-7.0.base-817e138f4813", diff --git a/utils/build/docker/dotnet/weblog/Controllers/DebuggerController.cs b/utils/build/docker/dotnet/weblog/Controllers/DebuggerController.cs index 818e978ebb9..d159e702521 100644 --- a/utils/build/docker/dotnet/weblog/Controllers/DebuggerController.cs +++ b/utils/build/docker/dotnet/weblog/Controllers/DebuggerController.cs @@ -182,5 +182,29 @@ private static Dictionary CreatePiiUser() ["name"] = "NON_SENSITIVE_VALUE", }; } + + [HttpGet("correlation")] + [Consumes("application/json", "application/xml")] + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + public IActionResult Correlation() + { + int result = CorrelationMiddle(); + System.Threading.Thread.Sleep(400); // space the probed call sites in time + return Content($"Correlation {result}"); + } + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private int CorrelationMiddle() + { + int result = CorrelationLeaf(); + System.Threading.Thread.Sleep(400); // space the probed call sites in time + return result; + } + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private int CorrelationLeaf() + { + return 3; + } } } diff --git a/utils/build/docker/golang/app/chi/debugger.go b/utils/build/docker/golang/app/chi/debugger.go index 37a36a8fb80..f02170c58cc 100644 --- a/utils/build/docker/golang/app/chi/debugger.go +++ b/utils/build/docker/golang/app/chi/debugger.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "runtime" + "time" ) // The `debugger` feature allows attachment to specific lines of code. @@ -13,7 +14,6 @@ import ( type DebuggerController struct{} -// Dummy line // Dummy line func (d *DebuggerController) logProbe(w http.ResponseWriter, r *http.Request) { // Dummy line @@ -116,3 +116,35 @@ func (d *DebuggerController) budgets(w http.ResponseWriter, r *http.Request, loo func (d *DebuggerController) budgetStep(i, loops int) int { return i + loops // loops is referenced so the capture-expression probe can read it at this line. } + +//go:noinline +func (d *DebuggerController) correlation(w http.ResponseWriter, r *http.Request) { + result := d.correlationMiddle() + time.Sleep(400 * time.Millisecond) // space the probed call sites in time + runtime.KeepAlive(result) + w.Write([]byte(fmt.Sprintf("Correlation %d", result))) +} + +//go:noinline +func (d *DebuggerController) correlationMiddle() int { + result := d.correlationLeaf() + time.Sleep(400 * time.Millisecond) // space the probed call sites in time + return result +} + +//go:noinline +func (d *DebuggerController) correlationLeaf() int { + return 3 +} + +//go:noinline +func (d *DebuggerController) correlationLoop(w http.ResponseWriter, r *http.Request, loops int) { + total := 0 + for i := 0; i < loops; i++ { + total += i // correlation loop body line probe + time.Sleep(time.Second) + } + afterLoop := total // correlation loop sibling line probe + runtime.KeepAlive(afterLoop) + w.Write([]byte(fmt.Sprintf("Loop %d", afterLoop))) +} diff --git a/utils/build/docker/golang/app/chi/main.go b/utils/build/docker/golang/app/chi/main.go index b85532021b5..e078d9a465e 100644 --- a/utils/build/docker/golang/app/chi/main.go +++ b/utils/build/docker/golang/app/chi/main.go @@ -428,6 +428,11 @@ func main() { loops, _ := strconv.Atoi(chi.RouteContext(r.Context()).URLParam("count")) d.budgets(w, r, loops) }) + mux.HandleFunc("/debugger/correlation", d.correlation) + mux.HandleFunc("/debugger/correlation/loop/{count}", func(w http.ResponseWriter, r *http.Request) { + loops, _ := strconv.Atoi(chi.RouteContext(r.Context()).URLParam("count")) + d.correlationLoop(w, r, loops) + }) srv := &http.Server{ Addr: ":7777", diff --git a/utils/build/docker/golang/app/echo/debugger.go b/utils/build/docker/golang/app/echo/debugger.go index 37a36a8fb80..f02170c58cc 100644 --- a/utils/build/docker/golang/app/echo/debugger.go +++ b/utils/build/docker/golang/app/echo/debugger.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "runtime" + "time" ) // The `debugger` feature allows attachment to specific lines of code. @@ -13,7 +14,6 @@ import ( type DebuggerController struct{} -// Dummy line // Dummy line func (d *DebuggerController) logProbe(w http.ResponseWriter, r *http.Request) { // Dummy line @@ -116,3 +116,35 @@ func (d *DebuggerController) budgets(w http.ResponseWriter, r *http.Request, loo func (d *DebuggerController) budgetStep(i, loops int) int { return i + loops // loops is referenced so the capture-expression probe can read it at this line. } + +//go:noinline +func (d *DebuggerController) correlation(w http.ResponseWriter, r *http.Request) { + result := d.correlationMiddle() + time.Sleep(400 * time.Millisecond) // space the probed call sites in time + runtime.KeepAlive(result) + w.Write([]byte(fmt.Sprintf("Correlation %d", result))) +} + +//go:noinline +func (d *DebuggerController) correlationMiddle() int { + result := d.correlationLeaf() + time.Sleep(400 * time.Millisecond) // space the probed call sites in time + return result +} + +//go:noinline +func (d *DebuggerController) correlationLeaf() int { + return 3 +} + +//go:noinline +func (d *DebuggerController) correlationLoop(w http.ResponseWriter, r *http.Request, loops int) { + total := 0 + for i := 0; i < loops; i++ { + total += i // correlation loop body line probe + time.Sleep(time.Second) + } + afterLoop := total // correlation loop sibling line probe + runtime.KeepAlive(afterLoop) + w.Write([]byte(fmt.Sprintf("Loop %d", afterLoop))) +} diff --git a/utils/build/docker/golang/app/echo/main.go b/utils/build/docker/golang/app/echo/main.go index 631076f9a44..dae11906908 100644 --- a/utils/build/docker/golang/app/echo/main.go +++ b/utils/build/docker/golang/app/echo/main.go @@ -399,6 +399,12 @@ func main() { d.budgets(c.Response().Writer, c.Request(), loops) return nil }) + r.Any("/debugger/correlation", echoHandleFunc(d.correlation)) + r.Any("/debugger/correlation/loop/:count", func(c echo.Context) error { + loops, _ := strconv.Atoi(c.Param("count")) + d.correlationLoop(c.Response().Writer, c.Request(), loops) + return nil + }) common.InitDatadog() go grpc.ListenAndServe() diff --git a/utils/build/docker/golang/app/gin/debugger.go b/utils/build/docker/golang/app/gin/debugger.go index 37a36a8fb80..f02170c58cc 100644 --- a/utils/build/docker/golang/app/gin/debugger.go +++ b/utils/build/docker/golang/app/gin/debugger.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "runtime" + "time" ) // The `debugger` feature allows attachment to specific lines of code. @@ -13,7 +14,6 @@ import ( type DebuggerController struct{} -// Dummy line // Dummy line func (d *DebuggerController) logProbe(w http.ResponseWriter, r *http.Request) { // Dummy line @@ -116,3 +116,35 @@ func (d *DebuggerController) budgets(w http.ResponseWriter, r *http.Request, loo func (d *DebuggerController) budgetStep(i, loops int) int { return i + loops // loops is referenced so the capture-expression probe can read it at this line. } + +//go:noinline +func (d *DebuggerController) correlation(w http.ResponseWriter, r *http.Request) { + result := d.correlationMiddle() + time.Sleep(400 * time.Millisecond) // space the probed call sites in time + runtime.KeepAlive(result) + w.Write([]byte(fmt.Sprintf("Correlation %d", result))) +} + +//go:noinline +func (d *DebuggerController) correlationMiddle() int { + result := d.correlationLeaf() + time.Sleep(400 * time.Millisecond) // space the probed call sites in time + return result +} + +//go:noinline +func (d *DebuggerController) correlationLeaf() int { + return 3 +} + +//go:noinline +func (d *DebuggerController) correlationLoop(w http.ResponseWriter, r *http.Request, loops int) { + total := 0 + for i := 0; i < loops; i++ { + total += i // correlation loop body line probe + time.Sleep(time.Second) + } + afterLoop := total // correlation loop sibling line probe + runtime.KeepAlive(afterLoop) + w.Write([]byte(fmt.Sprintf("Loop %d", afterLoop))) +} diff --git a/utils/build/docker/golang/app/gin/main.go b/utils/build/docker/golang/app/gin/main.go index 1b8b3e4a913..9fe8822faed 100644 --- a/utils/build/docker/golang/app/gin/main.go +++ b/utils/build/docker/golang/app/gin/main.go @@ -384,6 +384,11 @@ func main() { loops, _ := strconv.Atoi(ctx.Param("count")) d.budgets(ctx.Writer, ctx.Request, loops) }) + r.Any("/debugger/correlation", ginHandleFunc(d.correlation)) + r.Any("/debugger/correlation/loop/:count", func(ctx *gin.Context) { + loops, _ := strconv.Atoi(ctx.Param("count")) + d.correlationLoop(ctx.Writer, ctx.Request, loops) + }) srv := &http.Server{ Addr: ":7777", diff --git a/utils/build/docker/golang/app/net-http-orchestrion/debugger.go b/utils/build/docker/golang/app/net-http-orchestrion/debugger.go index 37a36a8fb80..f02170c58cc 100644 --- a/utils/build/docker/golang/app/net-http-orchestrion/debugger.go +++ b/utils/build/docker/golang/app/net-http-orchestrion/debugger.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "runtime" + "time" ) // The `debugger` feature allows attachment to specific lines of code. @@ -13,7 +14,6 @@ import ( type DebuggerController struct{} -// Dummy line // Dummy line func (d *DebuggerController) logProbe(w http.ResponseWriter, r *http.Request) { // Dummy line @@ -116,3 +116,35 @@ func (d *DebuggerController) budgets(w http.ResponseWriter, r *http.Request, loo func (d *DebuggerController) budgetStep(i, loops int) int { return i + loops // loops is referenced so the capture-expression probe can read it at this line. } + +//go:noinline +func (d *DebuggerController) correlation(w http.ResponseWriter, r *http.Request) { + result := d.correlationMiddle() + time.Sleep(400 * time.Millisecond) // space the probed call sites in time + runtime.KeepAlive(result) + w.Write([]byte(fmt.Sprintf("Correlation %d", result))) +} + +//go:noinline +func (d *DebuggerController) correlationMiddle() int { + result := d.correlationLeaf() + time.Sleep(400 * time.Millisecond) // space the probed call sites in time + return result +} + +//go:noinline +func (d *DebuggerController) correlationLeaf() int { + return 3 +} + +//go:noinline +func (d *DebuggerController) correlationLoop(w http.ResponseWriter, r *http.Request, loops int) { + total := 0 + for i := 0; i < loops; i++ { + total += i // correlation loop body line probe + time.Sleep(time.Second) + } + afterLoop := total // correlation loop sibling line probe + runtime.KeepAlive(afterLoop) + w.Write([]byte(fmt.Sprintf("Loop %d", afterLoop))) +} diff --git a/utils/build/docker/golang/app/net-http-orchestrion/main.go b/utils/build/docker/golang/app/net-http-orchestrion/main.go index b9cda1a1f41..1689e75c17d 100644 --- a/utils/build/docker/golang/app/net-http-orchestrion/main.go +++ b/utils/build/docker/golang/app/net-http-orchestrion/main.go @@ -646,6 +646,11 @@ func main() { loops, _ := strconv.Atoi(r.PathValue("count")) d.budgets(w, r, loops) }) + mux.HandleFunc("/debugger/correlation", d.correlation) + mux.HandleFunc("/debugger/correlation/loop/{count}", func(w http.ResponseWriter, r *http.Request) { + loops, _ := strconv.Atoi(r.PathValue("count")) + d.correlationLoop(w, r, loops) + }) srv := &http.Server{ Addr: ":7777", diff --git a/utils/build/docker/golang/app/net-http/debugger.go b/utils/build/docker/golang/app/net-http/debugger.go index 37a36a8fb80..f02170c58cc 100644 --- a/utils/build/docker/golang/app/net-http/debugger.go +++ b/utils/build/docker/golang/app/net-http/debugger.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "runtime" + "time" ) // The `debugger` feature allows attachment to specific lines of code. @@ -13,7 +14,6 @@ import ( type DebuggerController struct{} -// Dummy line // Dummy line func (d *DebuggerController) logProbe(w http.ResponseWriter, r *http.Request) { // Dummy line @@ -116,3 +116,35 @@ func (d *DebuggerController) budgets(w http.ResponseWriter, r *http.Request, loo func (d *DebuggerController) budgetStep(i, loops int) int { return i + loops // loops is referenced so the capture-expression probe can read it at this line. } + +//go:noinline +func (d *DebuggerController) correlation(w http.ResponseWriter, r *http.Request) { + result := d.correlationMiddle() + time.Sleep(400 * time.Millisecond) // space the probed call sites in time + runtime.KeepAlive(result) + w.Write([]byte(fmt.Sprintf("Correlation %d", result))) +} + +//go:noinline +func (d *DebuggerController) correlationMiddle() int { + result := d.correlationLeaf() + time.Sleep(400 * time.Millisecond) // space the probed call sites in time + return result +} + +//go:noinline +func (d *DebuggerController) correlationLeaf() int { + return 3 +} + +//go:noinline +func (d *DebuggerController) correlationLoop(w http.ResponseWriter, r *http.Request, loops int) { + total := 0 + for i := 0; i < loops; i++ { + total += i // correlation loop body line probe + time.Sleep(time.Second) + } + afterLoop := total // correlation loop sibling line probe + runtime.KeepAlive(afterLoop) + w.Write([]byte(fmt.Sprintf("Loop %d", afterLoop))) +} diff --git a/utils/build/docker/golang/app/net-http/main.go b/utils/build/docker/golang/app/net-http/main.go index dafa5e66da1..1f54502fe63 100644 --- a/utils/build/docker/golang/app/net-http/main.go +++ b/utils/build/docker/golang/app/net-http/main.go @@ -797,6 +797,11 @@ func main() { loops, _ := strconv.Atoi(r.PathValue("count")) d.budgets(w, r, loops) }) + mux.HandleFunc("/debugger/correlation", d.correlation) + mux.HandleFunc("/debugger/correlation/loop/{count}", func(w http.ResponseWriter, r *http.Request) { + loops, _ := strconv.Atoi(r.PathValue("count")) + d.correlationLoop(w, r, loops) + }) srv := &http.Server{ Addr: ":7777", diff --git a/utils/build/docker/nodejs/express/debugger/index.js b/utils/build/docker/nodejs/express/debugger/index.js index e3a68bf4dbf..ec4f7028b03 100644 --- a/utils/build/docker/nodejs/express/debugger/index.js +++ b/utils/build/docker/nodejs/express/debugger/index.js @@ -143,6 +143,8 @@ module.exports = { }) app.get('/debugger/budgets/:loops', budgets) + app.get('/debugger/correlation', correlationHandler) + app.get('/debugger/correlation/loop/:count', correlationLoopHandler) } } @@ -154,13 +156,47 @@ function captureTimeoutFixture (collectionSize, nestingDepth) { } return nested }) - return 'Capture timeout probe' // This needs to be line 157 + return 'Capture timeout probe' // This needs to be line 159 } function budgets (request, reply) { const loops = Number(request.params.loops) for (let iteration = 0; iteration < loops; iteration++) { - const currentIteration = iteration // This needs to be line 163 + const currentIteration = iteration // This needs to be line 165 } return reply.send('Budgets') } + +async function correlationHandler (req, res) { + await correlation() + res.send('Correlation') +} + +async function correlation () { + const value = await correlationMiddle() + await sleep(400) + return value // This needs to be line 178 +} + +async function correlationMiddle () { + const value = correlationLeaf() + await sleep(400) + return value // This needs to be line 184 +} + +function correlationLeaf () { + return 3 // This needs to be line 188 +} + +async function correlationLoopHandler (req, res) { + const count = Number(req.params.count) + for (let i = 0; i < count; i++) { + const iteration = i // This needs to be line 194 + await sleep(1000) + } + res.send('Loop') // This needs to be line 197 +} + +function sleep (ms) { + return new Promise((resolve) => setTimeout(resolve, ms)) +} diff --git a/utils/build/docker/nodejs/express4-typescript/debugger/index.ts b/utils/build/docker/nodejs/express4-typescript/debugger/index.ts index 8b7076ac94c..eca43368392 100644 --- a/utils/build/docker/nodejs/express4-typescript/debugger/index.ts +++ b/utils/build/docker/nodejs/express4-typescript/debugger/index.ts @@ -143,6 +143,8 @@ export function initRoutes (app: Express) { }) app.get('/debugger/budgets/:loops', budgets) + app.get('/debugger/correlation', correlationHandler) + app.get('/debugger/correlation/loop/:count', correlationLoopHandler) } function captureTimeoutFixture (collectionSize: number, nestingDepth: number): string { @@ -154,13 +156,47 @@ function captureTimeoutFixture (collectionSize: number, nestingDepth: number): s return nested }) - return 'Capture timeout probe' // This needs to be line 157 + return 'Capture timeout probe' // This needs to be line 159 } function budgets (request: Request, reply: Response): Response { const loops = Number(request.params.loops) for (let iteration = 0; iteration < loops; iteration++) { - const currentIteration = iteration // This needs to be line 163 + const currentIteration = iteration // This needs to be line 165 } return reply.send('Budgets') } + +async function correlationHandler (req: Request, res: Response): Promise { + await correlation() + res.send('Correlation') +} + +async function correlation (): Promise { + const value = await correlationMiddle() + await sleep(400) + return value // This needs to be line 178 +} + +async function correlationMiddle (): Promise { + const value = correlationLeaf() + await sleep(400) + return value // This needs to be line 184 +} + +function correlationLeaf (): number { + return 3 // This needs to be line 188 +} + +async function correlationLoopHandler (req: Request, res: Response): Promise { + const count = Number(req.params.count) + for (let i = 0; i < count; i++) { + const iteration = i // This needs to be line 194 + await sleep(1000) + } + res.send('Loop') // This needs to be line 197 +} + +function sleep (ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)) +} diff --git a/utils/build/docker/nodejs/fastify/debugger/index.js b/utils/build/docker/nodejs/fastify/debugger/index.js index 33edf1f2335..5caf0e0c839 100644 --- a/utils/build/docker/nodejs/fastify/debugger/index.js +++ b/utils/build/docker/nodejs/fastify/debugger/index.js @@ -143,6 +143,8 @@ module.exports = { }) fastify.get('/debugger/budgets/:loops', budgets) + fastify.get('/debugger/correlation', correlationHandler) + fastify.get('/debugger/correlation/loop/:count', correlationLoopHandler) } } @@ -154,13 +156,47 @@ function captureTimeoutFixture (collectionSize, nestingDepth) { } return nested }) - return 'Capture timeout probe' // This needs to be line 157 + return 'Capture timeout probe' // This needs to be line 159 } function budgets (request, reply) { const loops = Number(request.params.loops) for (let iteration = 0; iteration < loops; iteration++) { - const currentIteration = iteration // This needs to be line 163 + const currentIteration = iteration // This needs to be line 165 } return reply.send('Budgets') } + +async function correlationHandler (request, reply) { + await correlation() + return 'Correlation' +} + +async function correlation () { + const value = await correlationMiddle() + await sleep(400) + return value // This needs to be line 178 +} + +async function correlationMiddle () { + const value = correlationLeaf() + await sleep(400) + return value // This needs to be line 184 +} + +function correlationLeaf () { + return 3 // This needs to be line 188 +} + +async function correlationLoopHandler (request, reply) { + const count = Number(request.params.count) + for (let i = 0; i < count; i++) { + const iteration = i // This needs to be line 194 + await sleep(1000) + } + return 'Loop' // This needs to be line 197 +} + +function sleep (ms) { + return new Promise((resolve) => setTimeout(resolve, ms)) +}