Skip to content

feat(profiling): Send Hermes samples as UI profile chunks on Android - #6966

Open
alwx wants to merge 5 commits into
mainfrom
alwx/feature/6894
Open

alwx wants to merge 5 commits into
mainfrom
alwx/feature/6894

Conversation

@alwx

@alwx alwx commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

📢 Type of change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring

📜 Description

On Android 15 (API 35) and newer, uiProfilingIntegration now runs the Hermes sampler during the native profiler session. Every 60 seconds, and when the session stops, it sends the samples as a javascript profile chunk with the native profiler_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 (trace lifecycle). 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

  • I added tests to verify changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • All tests passing.
  • Public API changes reviewed by another Mobile SDK team member or implemented according to the develop docs spec.
  • No breaking changes.

🔮 Next steps

Add iOS when sentry-cocoa exposes the current profiler id.

alwx added 2 commits October 29, 2026 14:27
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.
@github-actions

Copy link
Copy Markdown
Contributor

Semver Impact of This PR

⚪ None (no version bump detected)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


  • feat(profiling): Send Hermes samples as UI profile chunks on Android by alwx in #6966

🤖 This preview updates automatically when you update the PR.

@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor
Fails
🚫 Pull request is not ready for merge, please add the "ready-to-merge" label to the pull request
Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against d02c5d7

Comment thread packages/core/src/js/profiling/uiProfilingIntegration.ts
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.
@alwx
alwx marked this pull request as ready for review October 29, 2026 14:41
@alwx
alwx requested review from a team and lucas-zimerman as code owners October 29, 2026 14:41
@alwx
alwx requested review from antonis and buenaflor and removed request for a team October 29, 2026 14:41

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

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

Comment thread packages/core/src/js/profiling/uiProfilingIntegration.ts Outdated
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.
Comment on lines 275 to 280
client.on('close', () => {
clearStopTimeout();
collectHermesChunk();
session = undefined;
resolveAppStartSession?.(null);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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() then sendProfileChunk() → client.sendEnvelope(envelope).
  • ReactNativeClient.close() awaits super.close() before NATIVE.closeNativeSdk(); core Client.close() flushes, sets enabled = false, then emits close.
  • ReactNativeClient.sendEnvelope() only sends when this._isEnabled() is true; otherwise it logs Transport disabled and returns.
  • Normal stopNativeProfiler() still sends while the client is enabled; only the close path loses the chunk.

Identified by Warden · find-bugs · RM2-25L

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Adopt Perfetto format for Android 17 profiling

1 participant