Skip to content

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

Open

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
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