Skip to content

Conversation

@s1gr1d
Copy link
Member

@s1gr1d s1gr1d commented Nov 12, 2025

Adds the manual mode for profiling and browser integration tests.

  • adds deprecation note for old option
  • adds some JSDoc comments to public-facing API to make the difference between Node and UI profiling better visible.

Closes #17279

@github-actions
Copy link
Contributor

github-actions bot commented Nov 12, 2025

size-limit report 📦

Path Size % Change Change
@sentry/browser 24.78 kB - -
@sentry/browser - with treeshaking flags 23.27 kB - -
@sentry/browser (incl. Tracing) 41.51 kB - -
@sentry/browser (incl. Tracing, Profiling) 46.1 kB +0.59% +269 B 🔺
@sentry/browser (incl. Tracing, Replay) 79.92 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 69.65 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 84.6 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 96.85 kB - -
@sentry/browser (incl. Feedback) 41.45 kB - -
@sentry/browser (incl. sendFeedback) 29.46 kB - -
@sentry/browser (incl. FeedbackAsync) 34.4 kB - -
@sentry/react 26.49 kB - -
@sentry/react (incl. Tracing) 43.51 kB - -
@sentry/vue 29.22 kB - -
@sentry/vue (incl. Tracing) 43.31 kB - -
@sentry/svelte 24.79 kB - -
CDN Bundle 27.14 kB +0.2% +52 B 🔺
CDN Bundle (incl. Tracing) 42.14 kB +0.16% +64 B 🔺
CDN Bundle (incl. Tracing, Replay) 78.67 kB +0.11% +79 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) 84.13 kB +0.07% +53 B 🔺
CDN Bundle - uncompressed 79.71 kB +0.32% +249 B 🔺
CDN Bundle (incl. Tracing) - uncompressed 125.08 kB +0.2% +249 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed 241.11 kB +0.11% +249 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 253.87 kB +0.1% +249 B 🔺
@sentry/nextjs (client) 45.93 kB - -
@sentry/sveltekit (client) 41.87 kB - -
@sentry/node-core 51.15 kB -0.01% -1 B 🔽
@sentry/node 159.46 kB -0.01% -1 B 🔽
@sentry/node - without tracing 93.02 kB -0.01% -1 B 🔽
@sentry/aws-serverless 106.78 kB -0.01% -1 B 🔽

View base workflow run

@github-actions
Copy link
Contributor

github-actions bot commented Nov 14, 2025

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.

Scenario Requests/s % of Baseline Prev. Requests/s Change %
GET Baseline 8,859 - 8,762 +1%
GET With Sentry 1,799 20% 1,719 +5%
GET With Sentry (error only) 6,226 70% 6,107 +2%
POST Baseline 1,223 - 1,194 +2%
POST With Sentry 614 50% 593 +4%
POST With Sentry (error only) 1,082 88% 1,073 +1%
MYSQL Baseline 3,355 - 3,366 -0%
MYSQL With Sentry 522 16% 470 +11%
MYSQL With Sentry (error only) 2,732 81% 2,717 +1%

View base workflow run

this._sessionSampled = sessionSampled;
this._lifecycleMode = lifecycleMode;

client.on('spanStart', span => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The client.on calls (spanStart, spanEnd) are now added in the _setupTraceLifecycleListeners function.

s1gr1d added a commit that referenced this pull request Nov 17, 2025
This PR was factored out of another PR to make reviewing easier. The
other PR: #18189

Moved the `spanStart` and `spanEnd` listeners into an extra function
(`_setupTraceLifecycleListeners`) to be able to only call it depending
on the lifecycle (used in another PR).

Part of #17279
# Conflicts:
#	packages/browser/src/profiling/UIProfiler.ts
@s1gr1d s1gr1d marked this pull request as ready for review November 17, 2025 13:29
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is similar to the Node profiling one: packages/core/src/profiling.ts (can be seen at the end of this diff page)

// Trace: Profile context is kept as long as there is an active root span
if (this._lifecycleMode === 'manual') {
getGlobalScope().setContext('profile', {});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Profiling state leaks after trace completion.

In trace mode, when _endProfiling is called after the last root span ends, the profile context is not cleared. The condition if (this._lifecycleMode === 'manual') prevents clearing the context in trace mode, but when there are no active root spans (which is when _endProfiling is called from spanEnd), the context should be cleared to prevent subsequent transactions from being incorrectly marked as profiled. The profile context persists even though profiling has stopped, causing spans created after profiling ends to incorrectly appear as profiled.

Fix in Cursor Fix in Web

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The profiler context will stay the same for the whole profiling session as the profiler cannot be just stopped like in manual profiling, where there might be spans that are not profiled anymore.

Copy link
Member

@Lms24 Lms24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! Had one thought about the public API <> integration interaction but otherwise LGTM!

Comment on lines +36 to +37
// In manual mode we start and stop once -> expect exactly one chunk
const profileChunkEnvelopes = await getMultipleSentryEnvelopeRequests<ProfileChunkEnvelope>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l: the code suggests we get two envelopes/chunks. Should we update/remove the comment?

const _browserProfilingIntegration = (() => {
return {
name: INTEGRATION_NAME,
_profiler: new UIProfiler(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l/m: In case the CDN bundle tests fail, this is likely the reason why: _profiler is mangled by our terser config (makeTerserPlugin()) because it is not excluded from the ignore list of private properties.

We can either opt out of mangling this field, make it "public" (i.e. remove the leading underscore), or add client hooks instead of exposing any additional field. I'd personally prefer using client hooks because it makes the binding very loose and also removes the need for the isProfilingIntegrationWithProfiler type predicate. Happy to leave this up to you though!

const laterRootSpan = laterActiveSpan && getRootSpan(laterActiveSpan);
if (laterRootSpan) {
traceLifecycleProfiler.notifyRootSpanActive(laterRootSpan);
profiler.notifyRootSpanActive(laterRootSpan);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Profiler hooks registered without initialization for invalid lifecycle

The profiler hooks startUIProfiler and stopUIProfiler are registered before checking if the lifecycle mode is valid. If profileLifecycle is set to an invalid value (not 'manual' or 'trace'), the hooks get registered but profiler.initialize() is never called. This causes the profiler to be in an uninitialized state when users call uiProfiler.startProfiler() or uiProfiler.stopProfiler(), leading to undefined behavior since _lifecycleMode, _sessionSampled, and other fields remain undefined.

Fix in Cursor Fix in Web

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.

[Browser]: Implement new Profiling API spec (UI Profiling)

3 participants