Skip to content

Commit 32efa23

Browse files
alwxclaudelucas-zimerman
authored
feat(core): Aggregate TurboModule call counts and latency per (module, method, kind) (#6377)
* feat(core): Aggregate TurboModule call counts and latency per (module, method, kind) Adds a small fixed-bucket histogram + counters per `(module, method, kind)` fed by the existing `wrapTurboModule` instrumentation. Aggregates flush on transaction finish (synthetic `turbo_modules.aggregate` child span + headline measurements on the root span) and on a lazy timer (info-level event for long-running sessions without transactions). Closes #6164. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(core): Trim TurboModule aggregator tests to the basics Drop edge-case tests in favor of one happy-path check per surface. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(core): Break TurboModule aggregator self-flush loop The periodic flush's own `captureEvent` travels through the wrapped `RNSentry.captureEnvelope`, which records back into the aggregator and, because the map was just drained, re-armed the flush timer forever in otherwise-idle sessions. Suppress the empty→non-empty callback around the flush's `captureEvent`, deferring the release to the next macrotask so the async record fired from the transport's `.then()` also lands inside the suppression window. * fix(core): Stop aggregating TurboModule calls when stats disabled `enableAggregateStats: false` skipped flush setup and processEvent but `wrapTurboModule` still recorded every call into the process-wide map, which had no drain path in that mode — so counters grew for the app's lifetime. Aggregator now exposes a master `setAggregateRecordingEnabled` switch that both no-ops future records and evicts existing entries. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(core): Drain aggregator noise after periodic flush suppression The suppression window in `flushPeriodicAggregate` prevented the flush's own transport call from re-arming the timer, but left the recorded self-noise sitting in the aggregate map. The next real user call then saw `wasEmpty=false` and never fired `onFirstRecordAfterEmpty`, so the periodic timer stayed silent for the rest of the session (until a transaction drain cleared the leftover). Drain the aggregator when releasing the suppression scope. Trades a sub-millisecond window of possibly-real user records for a working lazy-timer re-arm on every subsequent call. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(core): Default-ignore RNSentry in TurboModule aggregate The previous suppression + drain machinery worked around the flush's own `captureEvent → RNSentry.captureEnvelope` self-noise by drop-listing records around the send. That approach had two follow-on bugs: the blanket drain also discarded real user calls that raced with the flush window, and the async transport `.then()` sometimes fired outside the suppression scope entirely. Move the fix upstream: default `ignoreTurboModules` to `['RNSentry']`. Self-noise never enters the aggregator, so no suppression/drain gymnastics are needed. Users who want RNSentry stats can pass `ignoreTurboModules: []`. Also document the intentional data-loss trade-off when a transaction is dropped by `beforeSendTransaction` (bounded, self-healing on the next transaction or periodic flush). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore(core): Drop unused isTurboModuleIgnored helper Nothing in the package reads it — `recordTurboModuleCall` checks the ignored set inline. Removing dead API surface. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: LucasZF <lucas-zimerman1@hotmail.com>
1 parent 63b8285 commit 32efa23

11 files changed

Lines changed: 940 additions & 14 deletions

File tree

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,23 @@
1010

1111
### Features
1212

13+
- Aggregate TurboModule call counts + latency per `(module, method, kind)` and flush them on transaction finish and on a lazy periodic timer ([#6377](https://github.com/getsentry/sentry-react-native/pull/6377))
14+
15+
Counters land on the finishing transaction as a synthetic `turbo_modules.aggregate` child span (per-call breakdown in span attributes) plus headline measurements on the root span (`turbo_modules.call_count`, `turbo_modules.error_count`, `turbo_modules.total_ms`, `turbo_modules.top_module_ms`). Long-running sessions without transactions emit a periodic info-level event (default every 30s, only when there's data).
16+
17+
```ts
18+
Sentry.init({
19+
integrations: [
20+
Sentry.turboModuleContextIntegration({
21+
// optional knobs (defaults shown):
22+
enableAggregateStats: true,
23+
aggregateFlushIntervalMs: 30_000,
24+
ignoreTurboModules: ['RNSentry'],
25+
}),
26+
],
27+
});
28+
```
29+
1330
- Add `Sentry.reportFullyDisplayed()` imperative API for signaling Time to Full Display ([#6419](https://github.com/getsentry/sentry-react-native/pull/6419))
1431
- Add `enableHistoricalTombstoneReporting` option to report historical tombstones from Android's `ApplicationExitInfo` ([#6450](https://github.com/getsentry/sentry-react-native/pull/6450))
1532

packages/core/etc/sentry-react-native.api.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -856,17 +856,23 @@ export { TransactionEvent }
856856
// @public
857857
export interface TurboModuleCall {
858858
callId: number;
859-
kind: 'sync' | 'async';
859+
kind: TurboModuleCallKind;
860860
method: string;
861861
name: string;
862862
startedAtMs: number;
863863
}
864864

865+
// @public
866+
export type TurboModuleCallKind = 'sync' | 'async';
867+
865868
// @public
866869
export const turboModuleContextIntegration: (options?: TurboModuleContextOptions) => Integration;
867870

868871
// @public (undocumented)
869872
export interface TurboModuleContextOptions {
873+
aggregateFlushIntervalMs?: number;
874+
enableAggregateStats?: boolean;
875+
ignoreTurboModules?: ReadonlyArray<string>;
870876
modules?: Array<{
871877
name: string;
872878
module: object | null | undefined;

packages/core/src/js/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,4 +172,4 @@ export {
172172
pushTurboModuleCall,
173173
wrapTurboModule,
174174
} from './turbomodule';
175-
export type { TurboModuleCall } from './turbomodule';
175+
export type { TurboModuleCall, TurboModuleCallKind } from './turbomodule';

0 commit comments

Comments
 (0)