feat(core): Wire TurboModulePerfLogger on iOS and Android#6307
Conversation
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
Semver Impact of This PR⚪ None (no version bump detected) 📋 Changelog PreviewThis is how your changes will appear in the changelog.
Plus 3 more 🤖 This preview updates automatically when you update the PR. |
Instructions and example for changelogPlease add an entry to Example: ## Unreleased
### Features
- Wire TurboModulePerfLogger on iOS and Android ([#6307](https://github.com/getsentry/sentry-react-native/pull/6307))If none of the above apply, you can opt out of this check by adding |
| /// as a C-linkage symbol so the JNI side can call it from `JNI_OnLoad` | ||
| /// without dragging the C++ ABI through the JNI boundary). | ||
| class SentryTurboModulePerfController { | ||
| public: |
There was a problem hiding this comment.
No tests for SentryTurboModulePerfController or RNSentryTurboModulePerfTracker
The new SentryTurboModulePerfController (idempotent install, enable/disable flag, swappable sink) and RNSentryTurboModulePerfTracker (lazy-link error handling, nativeUnavailable guard) have no accompanying tests, leaving the core wiring logic uncovered.
Evidence
packages/core/android/src/test/java/io/sentry/react/contains onlyRNSentryFramesDelayTest.javaandRNSentryUriValidationTest.java— no test forRNSentryTurboModulePerfTracker.- No C++ test file matching
*TurboModule*,*PerfLogger*, or*PerfController*was found anywhere in the repo. RNSentryTurboModulePerfTracker.setEnabled()has a non-trivialnativeUnavailablelatch and swallowsUnsatisfiedLinkError; this branch logic has no unit coverage.SentryTurboModulePerfController::install()idempotency viacompare_exchange_strongand thesetSink/sinkconcurrent access pattern are also untested.
Also found at 3 additional locations
packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java:198-201packages/core/cpp/SentryTurboModulePerfLogger.cpp:191packages/core/ios/RNSentry.mm:83
Identified by Warden code-review · 9WC-W6L
📢 Type of change
📜 Description
Install a Sentry-owned
facebook::react::NativeModulePerfLoggeron 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
ISentryTurboModulePerfSinkimplementation and plug into the hook this PR exposes.How it's split
Shared C++ (
packages/core/cpp/):SentryTurboModulePerfSink.h— pluggable sink interface for follow-up features.SentryTurboModulePerfLogger.h/.cpp— aSentryTurboModulePerfControllersingleton owns the installed logger and an atomicenabledflag. When disabled, every callback hits one atomic load and returns. When enabled, callbacks are forwarded to whichever sink is currently installed. The forwardingNativeModulePerfLoggersubclass lives inside an anonymous namespace and is generated via macros to keep the ~30 callbacks readable.iOS:
RNSentryTurboModulePerfLoggerInstaller's+loadcallsSentry_InstallTurboModulePerfLogger()so the perf logger is in place beforeRCTBridge/RCTHostinstantiate any module. (RNSentry's own+loadis reserved byRCT_EXPORT_MODULE().)cpp/**/*.{h,cpp}added to the podspec sources; files are guarded withRCT_NEW_ARCH_ENABLEDso Old Arch builds compile to empty TUs.Android (New Architecture only):
libsentry-tm-perf-logger.soshared library built via CMake. ExposesJNI_OnLoad(which installs the perf logger) andJava_io_sentry_react_RNSentryTurboModulePerfTracker_nativeSetEnabled(runtime gate).reactnativeprefab. The missing<reactperflogger/NativeModulePerfLogger.h>header (which the prefab transitively pulls in but doesn't ship) is plugged by pointing CMake at the source tree —REACT_NATIVE_DIRis resolved at gradle config time vianode --print require.resolve('react-native/package.json'), mirroring howreact-native-reanimateddoes it. Honours an explicitREACT_NATIVE_NODE_MODULES_DIRoverride.RNSentryPackage's static initializerSystem.loadLibrarys the perf-logger lib — host apps do NOT need to touch their ownOnLoad.cpp. A guardedtry { … } catch (UnsatisfiedLinkError)keeps Old Architecture (and any host that strips the lib) working as before.Runtime gate
New
enableTurboModuleTrackingoption onSentry.init, defaultfalsefor 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 throughinitNativeSdkon both platforms.💡 Motivation and Context
Closes #6162.
Today the SDK auto-wraps only the
RNSentryTurboModule viaturboModuleContextIntegration({ modules: […] }). Every other TurboModule (RN's built-ins, third-party) requires the user to manually register it, and JSI HostObjects don't always expose methods to JS — which means many native calls slip past the JS-side wrapper entirely.A native perf logger gives universal coverage of every TurboModule in the app, including ones the user has never heard of, for free.
Project: Turbo Modules instrumentation improvements · Issue: RN-638.
💚 How did you test it?
samples/react-native, New Architecture, Hermes, RN 0.86):xcodebuildbuild succeeds;+loadfires; perf logger is installed before any module instantiates.samples/react-native, New Architecture, RN 0.86): both:sentry_react-native:assembleReleaseand:app:assembleDebugbuild cleanly.libsentry-tm-perf-logger.sois built for all four ABIs (arm64-v8a,armeabi-v7a,x86,x86_64) and exports the expected symbols:falseby default and the foundation has no behaviour change beyond the install of the (otherwise inert) logger. End-to-end behavioural validation will come with the follow-up sink PRs.📝 Checklist
sendDefaultPIIis enabled🔮 Next steps
This is the foundation for the Turbo Modules project. Follow-up issues plug in
ISentryTurboModulePerfSinkimplementations:turbo_module.name/turbo_module.methodof the call that was in flight.duration,status,module.methoddata.