Skip to content

UI profiling: idle samples are attributed to stack 0 in convertToContinuousProfile #25757

Closed

Description

@igor-goldny

Is there an existing issue for this?

I searched the repo for "idle", "stackId" and "UI profiling" issues and found none.

SDK and version

@sentry/browser 10.76.2. The same code is on develop in packages/browser/src/profiling/utils.ts.

Description

convertToContinuousProfile writes a JS Self-Profiling sample that has no stackId as stack_id: sample.stackId ?? 0. A sample without a stackId means that the thread was idle. Stack 0 is a real stack: the first stack that the profiler recorded in the chunk. So the SDK reports every idle sample in the chunk as time spent in the frames of stack 0.

The legacy converter, convertJSSelfProfileToSampledFormat, handles the same case correctly. It adds one empty stack and points every idle sample at it.

Impact

With profileLifecycle: 'trace', a root span often starts the profiler from inside a timer or event callback. Stack 0 is then rooted at the SDK's own sentryWrapped wrapper (helpers.ts), plus the callback that started the span. A parentless http.client root span that waits 2 s on the network then shows in the flame graph as about 2 s of main-thread time in sentryWrapped. In one of our production chunks, the wrapper frame was in 429 of 430 samples, and no other frame was in more than 1. Because of this, a third-party telemetry request looked as if it blocked the main thread.

Steps to reproduce

We found this by reading the code and checking production profiles. We did not run the steps below in isolation.

  1. Call Sentry.init with browserProfilingIntegration(), profileLifecycle: 'trace', profileSessionSampleRate: 1 and tracesSampleRate: 1. Use Chrome, and serve the page with Document-Policy: js-profiling.
  2. From a setTimeout callback, start a root span and keep it open for 2 s while the page is idle. For example, send an XHR to a slow endpoint while span streaming is on.
  3. Open the continuous profile for that window.

Expected result

Idle samples have an empty stack, as in the legacy format. The flame graph shows the thread as idle.

Actual result

Idle samples point to stack 0. The flame graph shows the callback's stack as busy for the whole window.

Suggested fix

Do what the legacy converter does. On the first idle sample, add one empty stack, and point every idle sample at its index instead of at 0.

Activity

  1. linear-code commented on Oct 27, 2026

    @linear-code
  2. self-assigned this
    on Oct 27, 2026
  3. added a commit that references this issue on Oct 28, 2026
    7def1fe
  4. msonnb commented on Oct 28, 2026

    @msonnb
    Member

    thanks for reporting, the fix will be included in our next release! I see you're on 10.76.2, is it feasible for you to upgrade to v11 of the SDK? We generally don't backport all fixes to older versions anymore, but can make exceptions in certain cases.

  5. github-actions commented on Oct 28, 2026

    @github-actions
    Contributor

    A PR closing this issue has just been released 🚀

    This issue was referenced by PR #25764, which was included in the 11.10.0 release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions