Unsampled pageload span is profiled when profileLifecycle is 'trace' (browser UI profiling)
维护者通常 1 天内回复
评估
- 难度
- 2/5
- 预计耗时
- 1-3 小时
- 新手友好度
- 82/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 活跃
- 技术栈
- javascript, typescript
- 领域
- frontend, performance
调研方向
从 packages/browser/src/profiling/UIProfiler.ts 开始,将 notifyRootSpanActive() 与 _setupTraceLifecycleListeners() 中的 spanStart 监听器进行比较。检查现有的 profiling 测试是否覆盖了设置时已经处于活动状态且未采样的根 span。完成标准是:该情况不会启动 profiler,也不会发送 profile 数据块;而已采样的根 span 仍会启动 profiling。
由索引模型根据 Issue 内容生成。
描述
Is there an existing issue for this?
- I have checked for existing issues https://github.com/getsentry/sentry-javascript/issues
- I have reviewed the documentation https://docs.sentry.io/
- I am using the latest SDK release https://github.com/getsentry/sentry-javascript/releases
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/browser
SDK Version
@sentry/browser 11.8.0 (also reproduced on 11.4.0; UIProfiler.ts on develop is unchanged as of 2026-10-19)
Framework Version
No response
Link to Sentry event
No response
Reproduction Example/SDK Setup
import * as Sentry from '@sentry/browser';
Sentry.init({
dsn: '<dsn>',
integrations: [Sentry.browserTracingIntegration(), Sentry.browserProfilingIntegration()],
tracesSampleRate: 0, // pageload span NOT sampled -> expect NO profiling
profileSessionSampleRate: 1,
profileLifecycle: 'trace',
});
The page is served with Document-Policy: js-profiling.
Steps to Reproduce
- Initialize the SDK with
profileLifecycle: 'trace',profileSessionSampleRate: 1andtracesSampleRate: 0(see SDK setup above). - Load the page. The pageload span is not sampled (
isRecording() === false,traceFlags === 0). - Observe that
new Profiler(...)is called once and aprofile_chunkenvelope is sent when the pageload span ends.
Expected Result
The pageload span is not sampled, so trace lifecycle profiling should not start. This is how root spans started after setup are already handled.
Actual Result
The profiler starts for the unsampled pageload span and a profile_chunk is sent.
I checked this in Chromium with a window.Profiler stub that counts constructor calls and an in-memory transport:
tracesSampleRate: 0,profileSessionSampleRate: 1: profiler started once,profile_chunksenttracesSampleRate: 0,profileSessionSampleRate: 0: profiler not started (control)tracesSampleRate: 1,profileSessionSampleRate: 1: profiler started once,profile_chunkand the pageload span sent (expected)tracesSampleRate: 0,profileSessionSampleRate: 1, pageload instrumentation off, an unsampled root span started afterinit: profiler not started
The last case shows that only the root span that is already active at setup skips the sampling check.
Additional Context
Root cause (source walkthrough)
In packages/browser/src/profiling/integration.ts (develop):
- After
profiler.initialize(client), setup callsprofiler.notifyRootSpanActive(rootSpan)for the active root span. The comment says "If there is an active, sampled root span already, notify the profiler". - Setup calls it again in a
setTimeout(0). This is the path that picks up the pageload span thatbrowserTracingIntegrationstarts inafterAllSetup.
In packages/browser/src/profiling/UIProfiler.ts:
- The
spanStartlistener registered by_setupTraceLifecycleListeners()returns early when!span.isRecording()("Only count sampled root spans"). notifyRootSpanActive()only checks the lifecycle mode and_sessionSampled. It then registers the span and calls_beginProfiling()without checkingrootSpan.isRecording().
So an unsampled root span that is already active at setup starts the profiler. The unsampled span still emits spanEnd, so the profile runs for the length of the pageload.
#17619, which added the trace lifecycle, describes it as "Starts when the first sampled root span starts".
Impact
With profileSessionSampleRate: 1, unsampled pageload spans can still start UI profiling in supported browsers. Reducing tracesSampleRate, even to 0, does not prevent this startup path. This can add unintended profiling overhead and profile usage, without a sampled pageload span to correlate with the resulting chunks.
Related
#22927 was the same kind of gap in the Node trace lifecycle, there for the session sampling decision.
Suggested fix
Return early in notifyRootSpanActive() when !rootSpan.isRecording(), matching the spanStart listener. Happy to open a PR.
- 主要语言
- TypeScript
- 星标
- 8.8k
- 派生
- 1.9k
- 平均合并
- 1 天 12 小时
- 30 天内合并 PR
- 598
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
getsentry/sentry-javascript 的其他 Issue
-
Hapi: `route.method.toUpperCase is not a function` for routes registered with an array of methods可能已有人在做 @itisrmk 于 2 天前认领。 未关闭Bug Hapi Waiting for: Product Owner
难度 2/5 1-3 小时 新手友好度 88/100
getsentry/sentry-javascript#25481 · 1 条评论 ·
维护者通常 1 天内回复
-
node:http client span never ends when the response is destroyed without an error (res.destroy())可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭Bug Node.js Waiting for: Product Owner
难度 2/5 1-3 小时 新手友好度 78/100
getsentry/sentry-javascript#25469 · 1 条评论 ·
维护者通常 1 天内回复
-
Core Feature: Typescript Meta: Breaking
难度 2/5 1-3 小时 新手友好度 65/100
getsentry/sentry-javascript#25286 · 2 条评论 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 80/100
getsentry/sentry-javascript#24860 · 1 条评论 ·
维护者通常 1 天内回复
-
javascript
难度 2/5 1-3 小时 新手友好度 75/100
getsentry/sentry-javascript#24200 · 2 条评论 ·
维护者通常 1 天内回复
查看 getsentry/sentry-javascript 的全部 Issue
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 72/100
simkimsia/netlify-axi#19 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 72/100
subhranshudash13-dotcom/Contribo#74 ·
维护者通常 1 天内回复
-
enhancement frontend good first issue Stellar Wave trivial
难度 2/5 1-3 小时 新手友好度 76/100
Godgift-care/sci-healthcare-frontend#166 · 1 条评论 ·
维护者通常 1 天内回复
-
backend good first issue security Stellar Wave trivial
难度 2/5 1-3 小时 新手友好度 82/100
Godgift-care/sci-healthcare-backend#171 · 1 条评论 ·
维护者通常 1 天内回复