Repository navigation
Conversation
UI profiling on Android showed only native frames, so the profile of a transaction had no JavaScript functions. The integration now runs the Hermes sampler while the native profiler session runs. Every 60 seconds, and when the session stops, it sends the samples as a JavaScript profile_chunk with the native profiler_id. Sentry merges all chunks of a profiler_id into one profile. The feature is limited to Android API 35 and newer. Older devices use the legacy profiler, whose chunks vroom cannot merge with sample-format chunks (it returns HTTP 400). iOS needs the profiler id from sentry-cocoa first. The native Perfetto profiler samples the JS thread under its OS thread id too. Hermes uses the same id, so the Hermes samples get their own thread id (2^32) and spans point to it. Hermes timestamps use a monotonic clock, so each chunk is anchored to the Unix time when the sampler started. The sampler starts only after the native SDK returns a profiler id. It does not run when the session is not sampled or when transaction profiling (profilesSampleRate) uses the sampler. NATIVE.stopProfiling no longer warns about a missing native profile when only Hermes was started.
Replace the PR number placeholder in the CHANGELOG entry with #6966.
Semver Impact of This PR⚪ None (no version bump detected) 📋 Changelog PreviewThis is how your changes will appear in the changelog.
🤖 This preview updates automatically when you update the PR. |
|
The close handler collected the current Hermes chunk but kept the profile session. If NATIVE.startProfiler() resolved after close, the profiler id passed the session check and started the Hermes sampler and its 60 second restart timer again. The close handler now clears the session, so a late profiler id is ignored.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 13c7c2d. Configure here.
The UI profiling integration turned off Hermes chunks whenever profilesSampleRate was set, also when it was 0. At rate 0 the transaction profiler never starts the Hermes sampler, and the React Native SDK does not pass profilesSampleRate to sentry-java, so the native UI profiler still runs. Hermes chunks are now turned off only for a positive profilesSampleRate.
| client.on('close', () => { | ||
| clearStopTimeout(); | ||
| collectHermesChunk(); | ||
| session = undefined; | ||
| resolveAppStartSession?.(null); | ||
| }); |
There was a problem hiding this comment.
Final Hermes chunk send is dropped on client close
collectHermesChunk() on close calls client.sendEnvelope() after Client.close() sets enabled: false, so the final Hermes chunk is discarded. Send via the transport/NATIVE before native teardown, or stop Hermes without relying on the disabled client send path.
Evidence
- Close handler calls
collectHermesChunk()thensendProfileChunk()→client.sendEnvelope(envelope). ReactNativeClient.close()awaitssuper.close()beforeNATIVE.closeNativeSdk(); coreClient.close()flushes, setsenabled = false, then emitsclose.ReactNativeClient.sendEnvelope()only sends whenthis._isEnabled()is true; otherwise it logsTransport disabledand returns.- Normal
stopNativeProfiler()still sends while the client is enabled; only the close path loses the chunk.
Identified by Warden · find-bugs · RM2-25L
There was a problem hiding this comment.
Confirmed: Client.close() sets enabled = false before it emits close, so the chunk was dropped. Fixed in d02c5d7. The close handler now only stops the Hermes sampler and does not build or send a chunk. There is no hook before the client is disabled, and apps rarely call Sentry.close(), so the SDK does not try to send the last partial chunk. Added a test.
There was a problem hiding this comment.
Fix attempt detected (commit d02c5d7)
The close handler now stops the Hermes sampler but discards the returned profile instead of sending it, so the final chunk is still lost.
The original issue appears unresolved. Please review and try again.
Evaluated by Warden
@sentry/core emits the close hook after it disables the client, so sendEnvelope drops anything sent from there. The close handler built and sent the last Hermes chunk anyway, which only wasted a conversion. It now stops the Hermes sampler without building a chunk. Stopping and sending are split into stopHermesSampler and collectHermesChunk for this.
The last Hermes chunk was lost when the client closed, because @sentry/core disables the client before it emits close. Client.close() flushes first, and flush emits its hook while the client is still enabled and before the transport drains. The integration now sends the current chunk and restarts the sampler on flush, so close sends the last chunk, and Sentry.flush() and the fatal error handler send the JS samples collected so far. The close handler only stops the sampler.
antonis
left a comment
There was a problem hiding this comment.
Thank you for your work on this @alwx 🚀
Overall LGTM. I've also run a few tests on an emulator and I think the result is the expected (Sentry link).
The CI looks stuck at this point. Merging the latest main or closing/opening the PR should unstuck it 🤞
Also looping in @markushi who has more context on the Perfetto Android implementation in case he has any feedback.

📢 Type of change
📜 Description
On Android 15 (API 35) and newer,
uiProfilingIntegrationnow runs the Hermes sampler during the native profiler session. Every 60 seconds, and when the session stops, it sends the samples as ajavascriptprofile chunk with the nativeprofiler_id, so Sentry merges JS and native frames into one profile. The Hermes samples use their own thread id (2^32), and spans point to it, because the native Perfetto profiler samples the JS thread under the same OS thread id.💡 Motivation and Context
Closes #6894. UI profiles on Android showed only native frames. The feature is limited to API 35+, because older devices send legacy Android chunks, and vroom cannot merge them with sample-format chunks. iOS needs the profiler id from sentry-cocoa first.
💚 How did you test it?
New Jest tests for the chunk builder, the integration, and the wrapper. I ran the sample app on an API 36 emulator with
profilingOptions(tracelifecycle). Perfetto and the Hermes sampler ran together, chunks were sent every 60 seconds, and both chunks passed Relay's sample v2 parse and normalize rules. The merged profile in the Sentry UI still needs a check.📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps
Add iOS when sentry-cocoa exposes the current profiler id.