Skip to content

Commit 42e54f9

Browse files
authored
Add timeout to debugger captures (#4003)
* Add timeout to debugger captures Signed-off-by: Bob Weinand <bob.weinand@datadoghq.com> * Fix alpine build Signed-off-by: Bob Weinand <bob.weinand@datadoghq.com> * Windows timers have less resolution, increase amount Signed-off-by: Bob Weinand <bob.weinand@datadoghq.com> * Address code review Signed-off-by: Bob Weinand <bob.weinand@datadoghq.com> --------- Signed-off-by: Bob Weinand <bob.weinand@datadoghq.com>
1 parent acd5a0d commit 42e54f9

15 files changed

Lines changed: 335 additions & 3 deletions

ext/compatibility.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,8 @@ static inline zend_string *zend_ini_str(const char *name, size_t name_length, bo
686686
return return_value;
687687
}
688688

689+
#define tsrm_is_managed_thread() (tsrm_get_ls_cache() != NULL)
690+
689691
#define zend_zval_value_name zend_zval_type_name
690692

691693
#define Z_PARAM_ZVAL_OR_NULL(dest) Z_PARAM_ZVAL_EX(dest, 1, 0)

ext/crashtracking_frames.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ static ddog_CharSlice dd_validate_zstr(zend_string *str) {
2424
}
2525

2626
static void dd_frames_callback(void (*emit_frame)(const ddog_crasht_RuntimeStackFrame *)) {
27+
#ifdef ZTS
28+
if (!tsrm_is_managed_thread()) {
29+
return;
30+
}
31+
#endif
32+
2733
zend_execute_data *call;
2834
#if PHP_VERSION_ID >= 80400
2935
zend_execute_data *last_call = NULL;

ext/remote_config.c

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
#include "threads.h"
88
#include <tracer/tracer_api.h>
99
#ifndef _WIN32
10-
#include <signal.h>
10+
#include <sys/time.h>
11+
#include <tracer/ddtrace_globals.h>
1112
#endif
1213

1314
#if PHP_VERSION_ID < 70100
@@ -65,6 +66,50 @@ void datadog_check_for_new_config_now(void) {
6566
static void dd_sigvtalarm_handler(int signal, siginfo_t *siginfo, void *ctx) {
6667
UNUSED(signal, siginfo, ctx);
6768
datadog_set_all_thread_vm_interrupt();
69+
70+
#if defined(__linux__) && defined(ZTS)
71+
if (!tsrm_is_managed_thread()) {
72+
return;
73+
}
74+
#endif
75+
76+
uint64_t now_ns = 0;
77+
#if !defined(__linux__) && defined(ZTS)
78+
// On macOS ZTS, setitimer is per-process; the signal may land on any thread - iterate all threads to check for expirations
79+
uint64_t next_deadline = ~0ull;
80+
tsrm_mutex_lock(datadog_threads_mutex);
81+
void *TSRMLS_CACHE;
82+
ZEND_HASH_FOREACH_PTR(&datadog_tls_bases, TSRMLS_CACHE) {
83+
#endif
84+
// On Linux the signal gets delivered to the thread that set the timer, so we don't need to iterate all threads
85+
uint64_t deadline = DDTRACE_G(capture_deadline_ns);
86+
if (deadline) {
87+
if (!now_ns) {
88+
struct timespec now;
89+
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &now);
90+
now_ns = (uint64_t)now.tv_sec * 1000000000ULL + (uint64_t)now.tv_nsec;
91+
}
92+
if (now_ns >= deadline) {
93+
DDTRACE_G(debugger_capture_timed_out) = 1;
94+
}
95+
#if !defined(__linux__) && defined(ZTS)
96+
else {
97+
next_deadline = MIN(deadline, next_deadline);
98+
}
99+
#endif
100+
}
101+
#if !defined(__linux__) && defined(ZTS)
102+
} ZEND_HASH_FOREACH_END();
103+
if (next_deadline != ~0ull) { // re-arm the timer, for ZTS concurrency
104+
uint64_t usec = (next_deadline - now_ns) / 1000ull;
105+
struct itimerval it = {
106+
.it_value = { .tv_sec = usec / 1000000, .tv_usec = usec % 1000000 },
107+
.it_interval = { 0, 0 },
108+
};
109+
setitimer(ITIMER_VIRTUAL, &it, NULL);
110+
}
111+
tsrm_mutex_unlock(datadog_threads_mutex);
112+
#endif
68113
}
69114
#endif
70115

metadata/supported-configurations.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,13 @@
340340
"default": "http://localhost:8125"
341341
}
342342
],
343+
"DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS": [
344+
{
345+
"implementation": "A",
346+
"type": "int",
347+
"default": "15"
348+
}
349+
],
343350
"DD_DYNAMIC_INSTRUMENTATION_ENABLED": [
344351
{
345352
"implementation": "A",

tests/ext/live-debugger/debugger_log_probe.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ DD_TRACE_AGENT_PORT=80
88
DD_TRACE_GENERATE_ROOT_SPAN=0
99
DD_DYNAMIC_INSTRUMENTATION_ENABLED=1
1010
DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS=0.1
11+
DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS=5000
1112
--INI--
1213
datadog.trace.agent_test_session_token=live-debugger/log_probe
1314
--FILE--
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
--TEST--
2+
Live debugger log probe capture timeout with large data structure
3+
--SKIPIF--
4+
<?php include __DIR__ . '/../includes/skipif_no_dev_env.inc'; ?>
5+
--ENV--
6+
DD_AGENT_HOST=request-replayer
7+
DD_TRACE_AGENT_PORT=80
8+
DD_TRACE_GENERATE_ROOT_SPAN=0
9+
DD_DYNAMIC_INSTRUMENTATION_ENABLED=1
10+
DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS=0.1
11+
DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS=1
12+
--INI--
13+
datadog.trace.agent_test_session_token=live-debugger/log_probe_capture_timeout
14+
--FILE--
15+
<?php
16+
17+
require __DIR__ . "/live_debugger.inc";
18+
19+
reset_request_replayer();
20+
21+
function large_capture($huge_array) {}
22+
23+
await_probe_installation(function() {
24+
build_log_probe([
25+
"where" => ["methodName" => "large_capture"],
26+
"captureSnapshot" => true,
27+
"segments" => [["str" => "capture timeout test"]],
28+
]);
29+
\DDTrace\start_span();
30+
});
31+
32+
// 3-level array: 10 outer x 100 mid x 100 inner strings (100-char each)
33+
// ~100,000 capture operations: reliably exceeds the 1ms CPU-time timeout
34+
$data = [];
35+
for ($i = 0; $i < 99; $i++) {
36+
$data[] = array_fill(0, 10, array_fill(0, 100, str_repeat('x', 100)));
37+
}
38+
$last = array_fill(0, 99, str_repeat('x', 100));
39+
$last[] = 'LAST_SENTINEL';
40+
$data[] = $last;
41+
42+
large_capture($data);
43+
44+
$dlr = new DebuggerLogReplayer;
45+
$log = $dlr->waitForDebuggerDataAndReplay();
46+
$captures = json_decode($log["body"], true)[0]["debugger"]["snapshot"]["captures"];
47+
$captures_json = json_encode($captures);
48+
49+
// Snapshot was delivered with some captured data
50+
var_dump(!empty($captures));
51+
52+
// Timeout reason must appear somewhere in the captured data
53+
var_dump(strpos($captures_json, '"timeout"') !== false);
54+
55+
// The last element must NOT have been captured before the timeout fired
56+
var_dump(strpos($captures_json, 'LAST_SENTINEL') === false);
57+
58+
?>
59+
--CLEAN--
60+
<?php
61+
require __DIR__ . "/live_debugger.inc";
62+
reset_request_replayer();
63+
?>
64+
--EXPECT--
65+
bool(true)
66+
bool(true)
67+
bool(true)

tests/ext/live-debugger/debugger_span_decoration_probe.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ DD_TRACE_AGENT_PORT=80
88
DD_TRACE_GENERATE_ROOT_SPAN=0
99
DD_DYNAMIC_INSTRUMENTATION_ENABLED=1
1010
DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS=0.1
11+
DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS=5000
1112
--INI--
1213
datadog.trace.agent_test_session_token=live-debugger/span_decoration_probe
1314
--FILE--

tests/ext/live-debugger/exception-replay_001.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ DD_TRACE_AGENT_PORT=80
99
DD_TRACE_GENERATE_ROOT_SPAN=0
1010
DD_EXCEPTION_REPLAY_ENABLED=1
1111
DD_EXCEPTION_REPLAY_CAPTURE_INTERVAL_SECONDS=1
12+
DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS=5000
1213
--INI--
1314
datadog.trace.agent_test_session_token=live-debugger/exception-replay_001
1415
--FILE--

tests/ext/live-debugger/exception-replay_002.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ DD_TRACE_AGENT_PORT=80
88
DD_TRACE_GENERATE_ROOT_SPAN=0
99
DD_EXCEPTION_REPLAY_ENABLED=1
1010
DD_EXCEPTION_REPLAY_CAPTURE_INTERVAL_SECONDS=1
11+
DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS=5000
1112
--INI--
1213
datadog.trace.agent_test_session_token=live-debugger/exception-replay_002
1314
--FILE--

tests/ext/live-debugger/exception-replay_non_regression_2989_mysqli.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ DD_TRACE_AGENT_PORT=80
1010
DD_TRACE_GENERATE_ROOT_SPAN=0
1111
DD_EXCEPTION_REPLAY_ENABLED=1
1212
DD_EXCEPTION_REPLAY_CAPTURE_INTERVAL_SECONDS=1
13+
DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS=5000
1314
--INI--
1415
datadog.trace.agent_test_session_token=live-debugger/non_regression_2989_mysqli
1516
--FILE--

0 commit comments

Comments
 (0)