Fix Safari dormant overhead in Live Debugger instrumentation by watson · Pull Request #438 · DataDog/build-plugins · GitHub
Skip to content

Fix Safari dormant overhead in Live Debugger instrumentation#438

Draft
watson wants to merge 1 commit into
watson/DEBUG-5787/add-benchfrom
watson/DEBUG-5793/fix-safari
Draft

Fix Safari dormant overhead in Live Debugger instrumentation#438
watson wants to merge 1 commit into
watson/DEBUG-5787/add-benchfrom
watson/DEBUG-5793/fix-safari

Conversation

@watson

@watson watson commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

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 the if (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.

watson commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

@datadog-official

datadog-official Bot commented Jun 24, 2026

Copy link
Copy Markdown

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 937be82 | Docs | Datadog PR Page | Give us feedback!

@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown

Live Debugger Runtime Benchmark

SDK-loaded dormant-probe runtime overhead, measured against an uninstrumented bundle in the same browser session.

Browser Workload Quality Per-call overhead upper
chrome Hot clean <= 2.59 ns
chrome Tiny clean <= 0.01 ns
firefox Hot clean <= 10.02 ns
firefox Tiny clean <= 0.01 ns
safari Hot clean <= 5.71 ns
safari Tiny clean <= 1.60 ns
Full diagnostics
browser  workload  quality  per-call overhead upper  overhead upper          95% CI        A/A diag        block CI  acf(1)   baseline  instrumented                         samples
-------  --------  -------  -----------------------  --------------  --------------  --------------  --------------  ------  ---------  ------------  ------------------------------
chrome   Hot       clean                 <= 2.59 ns        <= 5.21%   2.54..2.58 ns  -0.02..0.02 ns   2.54..2.59 ns    0.04  50.858 ms     53.467 ms   102 (trim 20%, outliers 4.9%)
chrome   Tiny      clean                 <= 0.01 ns        <= 0.12%  -0.02..0.01 ns  -0.00..0.03 ns  -0.02..0.01 ns    0.03  76.842 ms     76.990 ms  102 (trim 20%, outliers 13.7%)
firefox  Hot       clean                <= 10.02 ns       <= 39.92%  9.99..10.02 ns  -0.00..0.01 ns  9.99..10.02 ns   -0.30  38.540 ms     53.940 ms   102 (trim 20%, outliers 2.9%)
firefox  Tiny      clean                 <= 0.01 ns        <= 0.08%   0.00..0.01 ns  -0.00..0.00 ns   0.00..0.01 ns    0.07  97.860 ms     97.910 ms  102 (trim 20%, outliers 11.8%)
safari   Hot       clean                 <= 5.71 ns       <= 22.85%   5.70..5.71 ns   0.00..0.01 ns   5.70..5.71 ns    0.05  51.200 ms     62.900 ms   102 (trim 20%, outliers 4.9%)
safari   Tiny      clean                 <= 1.60 ns       <= 42.75%   1.59..1.60 ns  -0.00..0.00 ns   1.59..1.60 ns   -0.08  37.520 ms     53.570 ms   102 (trim 20%, outliers 7.8%)

Raw samples are in the live-debugger-runtime-bench-results artifact.

@watson watson force-pushed the watson/DEBUG-5793/fix-safari branch from ce8320e to 52ed20d Compare June 25, 2026 14:01
@watson watson force-pushed the watson/DEBUG-5787/add-bench branch 2 times, most recently from d80cc4f to ba9d257 Compare June 26, 2026 11:49
@watson watson force-pushed the watson/DEBUG-5793/fix-safari branch from 52ed20d to 01561a1 Compare June 26, 2026 11:49
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.
@watson watson force-pushed the watson/DEBUG-5793/fix-safari branch from 01561a1 to 937be82 Compare July 1, 2026 13:03
@watson watson force-pushed the watson/DEBUG-5787/add-bench branch from ba9d257 to 0168305 Compare July 1, 2026 13:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant