{{ message }}
Fix Safari dormant overhead in Live Debugger instrumentation#438
Draft
watson wants to merge 1 commit into
Draft
Conversation
Contributor
Author
🎉 All green!🧪 All tests passed 🔗 Commit SHA: 937be82 | Docs | Datadog PR Page | Give us feedback! |
Live Debugger Runtime BenchmarkSDK-loaded dormant-probe runtime overhead, measured against an uninstrumented bundle in the same browser session. Full diagnosticsRaw samples are in the |
ce8320e to
52ed20d
Compare
d80cc4f to
ba9d257
Compare
52ed20d to
01561a1
Compare
This was referenced Jun 29, 2026
The transform hoisted each instrumented function's captured parameters into a per-call arrow
closure (`const $dd_eN = () => ({...})`). A nested closure that captures parameters forces
JavaScriptCore (Safari and all iOS browsers) to heap-allocate the function's scope on every
invocation -- even when probes are dormant and the closure is never created or called. V8 and
SpiderMonkey sink that allocation; JavaScriptCore does not, so it dominated the dormant runtime
overhead.
The runtime benchmark measured ~8.9 ns/call of dormant overhead on Safari for a tiny function
(vs ~0 on Chrome and ~1.6 on Firefox). Gating the closure behind the probe check does not help,
because the captured scope is still allocated on every call.
Instead, inline the captured-arguments object literal (`{a, b}`) directly at each probe-guarded
call site. The object now lives inside the `if (probe)` / `probe ? ` guards, so dormant calls
allocate nothing. The active path is unchanged: the same arguments object is built the same number
of times when a probe fires.
Safari per-call dormant overhead drops ~73% (8.9 -> 2.4 ns); the Hot workload drops from ~10 to
~7 ns. Chrome and Firefox are unchanged. Transform snapshots and the README/EXAMPLES output
catalog are updated to match the new shape.
01561a1 to
937be82
Compare
ba9d257 to
0168305
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


What and why?
The Live Debugger runtime benchmark surfaced a large dormant-probe overhead on Safari: a tiny instrumented function cost ~8.9 ns/call (~1,100% over baseline) versus ~0 ns on Chrome and ~1.6 ns on Firefox. The root cause is that the transform hoisted each function's captured parameters into a per-call arrow closure (
const $dd_eN = () => ({...})). A nested closure that captures parameters forces JavaScriptCore — Safari and all iOS browsers, since they're all WebKit — to heap-allocate the function's scope on every invocation, even when probes are dormant and the closure is never created or called. V8 and SpiderMonkey sink that allocation; JavaScriptCore does not, so it dominated the dormant overhead. Because every instrumented iOS app pays this cost on every call, it's worth eliminating.How?
Inline the captured-arguments object literal (
{a, b}) directly at each probe-guarded call site instead of hoisting it into a per-call closure. The object now lives inside theif (probe)/probe ?guards, so dormant calls allocate nothing; gating the closure behind the probe check was confirmed insufficient because the captured scope is still allocated. The active path is unchanged — the same arguments object is built the same number of times when a probe fires. Verified with the runtime benchmark: Safari per-call dormant overhead drops ~73% (8.9 → 2.4 ns) and the Hot workload from ~10 to ~7 ns, with no change on Chrome or Firefox. Transform unit-test snapshots and the plugin README/EXAMPLES output catalog are updated to match the new generated shape.