Skip to content

feat(core): Wire TurboModulePerfLogger on iOS and Android#6307

Open
alwx wants to merge 4 commits into
mainfrom
alwx/feature/turbo-module-perf-logger
Open

feat(core): Wire TurboModulePerfLogger on iOS and Android#6307
alwx wants to merge 4 commits into
mainfrom
alwx/feature/turbo-module-perf-logger

Conversation

@alwx

@alwx alwx commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

📢 Type of change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring

📜 Description

Install a Sentry-owned facebook::react::NativeModulePerfLogger on both platforms so the SDK observes every TurboModule lifecycle event:

  • moduleDataCreate{Start,End}, moduleCreate{Start,CacheHit,Construct*,SetUp*,End,Fail}
  • moduleJSRequireBeginning*, moduleJSRequireEnding*
  • syncMethodCall{Start,ArgConversion*,Execution*,ReturnConversion*,End,Fail}
  • asyncMethodCall{Start,ArgConversion*,Dispatch,End,Fail}
  • asyncMethodCallBatchPreprocess{Start,End}
  • asyncMethodCallExecution{Start,ArgConversion*,End,Fail}

This is the foundation that the next three issues in the Turbo Modules instrumentation project build on: JS↔Native crash attribution, per-Turbo-Module spans, and aggregated per-module stats. Each will ship its own ISentryTurboModulePerfSink implementation and plug into the hook this PR exposes.

New enableTurboModuleTracking option on Sentry.init, default false for this first release so the foundation lands without behavioural change. The native logger is always installed (we never want to miss early lifecycle events); the flag only decides whether forwarded callbacks reach the sink. The option is plumbed through initNativeSdk on both platforms.

Sentry.init({
  dsn: "___PUBLIC_DSN___",
  enableTurboModuleTracking: true, // off by default — will activate the follow-up sinks once they ship
});

💡 Motivation and Context

Closes #6162.

💚 How did you test it?

📝 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.
    • Docs deferred until a sink ships and the option becomes user-visible.
  • I updated the wizard if needed.
  • All tests passing
  • No breaking changes

🔮 Next steps

This is the foundation for the Turbo Modules project. Follow-up issues plug in ISentryTurboModulePerfSink implementations:

  1. JS↔Native crash attribution — annotate native crashes with turbo_module.name / turbo_module.method of the call that was in flight.
  2. Per-Turbo-Module spans — open a span around each native call with duration, status, module.method data.
  3. Aggregated per-module stats — counters / duration histograms per module/method, attached to transactions.

Install a Sentry-owned `facebook::react::NativeModulePerfLogger` on
both platforms so the SDK observes every TurboModule lifecycle event \u2014
`moduleDataCreate*`, `moduleCreate*`, sync/async method call
`start`/`end`/`fail`, async dispatch and execution `start`/`end`/`fail`
\u2014 for follow-up features (crash attribution, per-module spans,
aggregated stats) to plug into.

The implementation is split into:

- **Shared C++** (`packages/core/cpp/`): a single
  `SentryTurboModulePerfController` singleton owns the installed logger
  and an atomic `enabled` flag. When disabled, every callback hits one
  atomic load and returns. When enabled, callbacks are forwarded to a
  swappable `ISentryTurboModulePerfSink` \u2014 follow-up issues ship the
  sinks; this PR just exposes the hook.

- **iOS**: the perf logger is installed from a dedicated installer
  class's `+load` so it fires before `RCTBridge` / `RCTHost` create
  their first TurboModule. (`RNSentry`'s own `+load` is reserved by
  `RCT_EXPORT_MODULE()`.) The cpp/ directory is added to the podspec
  sources; files are guarded with `RCT_NEW_ARCH_ENABLED` so Old Arch
  builds compile to empty TUs.

- **Android**: a new `libsentry-tm-perf-logger.so` shared library is
  built via CMake under New Architecture only and exposes `JNI_OnLoad`
  + a tiny `nativeSetEnabled` JNI hook. It links against React
  Native's `reactnative` prefab; the missing
  `<reactperflogger/NativeModulePerfLogger.h>` header is plugged by
  pointing the include path at the source tree (mirroring how
  react-native-reanimated resolves react-native via the standard
  `REACT_NATIVE_NODE_MODULES_DIR` / `require.resolve` fallback).
  `RNSentryPackage`'s static initializer `System.loadLibrary`s the
  perf-logger lib \u2014 host apps do NOT need to touch their own
  `OnLoad.cpp`. A guarded `try { \u2026 } catch (UnsatisfiedLinkError)`
  keeps Old Architecture (and any host that strips the lib) working
  as before.

Runtime gate: new `enableTurboModuleTracking` option on `Sentry.init`,
default `false` for this first release so the foundation lands without
behavioral change. The native logger is always installed (we never want
to miss early lifecycle events), the flag only decides whether
forwarded callbacks reach the Sentry sink. The option is plumbed
through `initNativeSdk` on both platforms.

Foundation only \u2014 no sink is installed in this PR. Follow-up issues
ship the actual instrumentation.

Closes #6162
@github-actions

github-actions Bot commented Jun 17, 2026

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(core): Wire TurboModulePerfLogger on iOS and Android by alwx in #6307
  • fix(android): Prevent breadcrumb loss from numeric timestamp ClassCastException by antonis in #6308
  • chore(deps): update Android SDK to v8.44.0 by github-actions in #6310
  • chore(deps): update CLI to v3.5.1 by github-actions in #6305
  • chore(deps): update JavaScript SDK to v10.58.0 by github-actions in #6296
  • chore: Fix lint issues by antonis in #6300
  • chore: Bump sample and perf test apps to React Native 0.86.0 by antonis in #6287
  • fix(deps): bump form-data from 4.0.5 to 4.0.6 by antonis in #6297
  • fix(ci): Handle @sentry-internal/* package renames in JS updater by antonis in #6295
  • Record network request/response bodies in Session Replay by alwx in #6288
  • chore(deps): bump tar from 7.5.11 to 7.5.16 by dependabot in #6293
  • fix(ci): Update renamed @sentry-internal/* packages in JS updater script by antonis in #6294
  • chore(deps): bump launch-editor from 2.11.1 to 2.14.1 by dependabot in #6291
  • chore(deps-dev): bump @babel/core from 7.26.7 to 7.29.6 by dependabot in #6292
  • fix(deps): Resolve shell-quote to >=1.8.4 (Dependabot RNSentryModule.captureEvent is ignoring environment #547) by antonis in #6286
  • fix(ci): Support version catalog in android SDK version check by antonis in #6280
  • test(e2e): Bump E2E tests to React Native 0.86.0 by antonis in #6268
  • feat(android): Add nativeStackAndroid support to NativeLinkedErrors by lucas-zimerman in #6278
  • chore(deps): bump ruby/setup-ruby from 1.310.0 to 1.313.0 by dependabot in #6282
  • chore(deps): update Maestro to v2.6.1 by github-actions in #6277
  • chore(deps): bump gradle/actions from 6.1.0 to 6.2.0 by dependabot in #6284
  • chore(deps): bump getsentry/craft from 2.26.8 to 2.26.10 by dependabot in #6283
  • chore(deps): bump getsentry/craft/.github/workflows/changelog-preview.yml from 2.26.8 to 2.26.10 by dependabot in #6281
  • chore(deps): update Sentry Android Gradle Plugin to v6.11.0 by github-actions in #6275

Plus 5 more


🤖 This preview updates automatically when you update the PR.

@github-actions

github-actions Bot commented Jun 17, 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
Warnings
⚠️

⚠️ Android SDK Version Mismatch

Component Version
sentry-android in build.gradle 8.44.0
sentry-android bundled by gradle plugin 6.11.0 8.43.2

If a user enables AGP autoInstallation (default: true), this mismatch causes:

IllegalStateException: Sentry SDK has detected a mix of versions

Our docs instruct React Native users to set autoInstallation.enabled = false, so users following the guide are unaffected. Consider either updating packages/core/android/build.gradle to 8.43.2 or waiting for a gradle plugin release that bundles 8.44.0.

Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against 995956f

Comment thread packages/core/cpp/SentryTurboModulePerfLogger.h
@alwx

alwx commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

@cursor review

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3b618c5. Configure here.

Address Warden's medium-severity finding on PR #6307: the new
`SentryTurboModulePerfController` and `RNSentryTurboModulePerfTracker`
shipped without unit coverage. Add focused tests that exercise the
state machines independently of React Native's runtime.

- **iOS** (`RNSentryCocoaTester/.../RNSentryTurboModulePerfControllerTests.mm`):
  default `isEnabled() == false`, `setEnabled` toggle, the C-linkage
  `Sentry_SetTurboModuleTrackingEnabled` entry point matches the typed
  setter, `setSink`/`sink` round-trips including `nullptr` detach,
  and `Sentry_InstallTurboModulePerfLogger` idempotency under repeated
  calls. End-to-end forwarding through `facebook::react::TurboModulePerfLogger`
  is intentionally not covered here \u2014 it requires `+load` ordering and
  process-wide singletons that the follow-up sink PRs will integration-test.

- **Android** (`RNSentryAndroidTester/.../RNSentryTurboModulePerfTrackerTest.kt`):
  the JVM-side latch around the JNI symbol. In the test JVM the
  underlying `.so` is not loaded, so the first `setEnabled` call must
  catch `UnsatisfiedLinkError` and flip `nativeUnavailable`; subsequent
  calls must short-circuit. Uses Robolectric so the `android.util.Log.i`
  call inside the catch branch resolves instead of throwing the
  not-mocked stub. A small `@TestOnly` window on the tracker exposes
  the latch state to assertions.

Also fix the changelog entry to reference the PR (#6307) rather than
the issue (#6162) so danger stops nagging.
@alwx alwx marked this pull request as ready for review June 18, 2026 08:49
Comment thread packages/core/cpp/SentryTurboModulePerfLogger.cpp
@alwx alwx enabled auto-merge (squash) June 18, 2026 09:40
@alwx alwx disabled auto-merge June 18, 2026 09:40

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

Reviewed by Cursor Bugbot for commit 995956f. Configure here.

id enableTurboModuleTracking = [options objectForKey:@"enableTurboModuleTracking"];
if ([enableTurboModuleTracking isKindOfClass:[NSNumber class]]) {
Sentry_SetTurboModuleTrackingEnabled([(NSNumber *)enableTurboModuleTracking boolValue] ? 1 : 0);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Late tracking enable misses startup

Medium Severity

With enableTurboModuleTracking set to true, the native forwarder stays disabled until initNativeSdk runs. TurboModule perf callbacks during earlier bridge startup only hit the installed logger’s isEnabled() fast path and are dropped, so startup module create/require/sync activity is never forwarded for that init.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 995956f. Configure here.

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.

This looks valid 👍

Comment on lines +55 to +57
buildFeatures {
prefab true
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: In build.gradle, two separate buildFeatures blocks exist. The second block overwrites the first when both are active, potentially disabling BuildConfig generation on AGP 8+ and causing build failures.
Severity: HIGH

Suggested Fix

Merge the two buildFeatures blocks in build.gradle into a single block to ensure both buildConfig = true (for AGP 8+) and prefab = true (for New Architecture) are applied correctly. This prevents one configuration from overwriting the other.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: packages/core/android/build.gradle#L55-L57

Potential issue: The `build.gradle` file defines two separate `buildFeatures` blocks
within the same `android` scope. When both an Android Gradle Plugin (AGP) version of 8
or higher is used and the new architecture is enabled, both blocks are active. However,
Gradle's DSL behavior causes the second `buildFeatures { prefab = true }` block to
overwrite the first `buildFeatures { buildConfig = true }` block. This removes the
explicit `buildConfig = true` setting, which is likely required for AGP 8+ to generate
the `BuildConfig` class. Consequently, the `buildConfigField` for
`IS_NEW_ARCHITECTURE_ENABLED` may fail, leading to a build error or a runtime failure in
`RNSentryPackage.java` which depends on this field.

Also affects:

  • packages/core/android/src/main/java/io/sentry/react/RNSentryPackage.java:22~22

Did we get this right? 👍 / 👎 to inform future reviews.

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.

Formatting this should fix the lint issue

@antonis antonis left a comment

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.

Thank you for you work on this @alwx 🙇
Did a 1st pass and didn't notice anything off other what has been caught by the agents and the lint check. Let's also add the ready-to-merge since there are many changes on the native side that need to be validated.

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.

Wire TurboModulePerfLogger on iOS and Android

2 participants