Skip to content

Commit f7a4d29

Browse files
fix(performance): UIKit can't end after JS execution starts (#3884)
1 parent 785b737 commit f7a4d29

File tree

3 files changed

+111
-1
lines changed

3 files changed

+111
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
### Features
66

7-
- Add native application start spans ([#3855](https://github.com/getsentry/sentry-react-native/pull/3855))
7+
- Add native application start spans ([#3855](https://github.com/getsentry/sentry-react-native/pull/3855), [#3884](https://github.com/getsentry/sentry-react-native/pull/3884))
88
- This doesn't change the app start measurement length, but add child spans (more detail) into the existing app start span
99
- Added JS Bundle Execution start information to the application start measurements ([#3857](https://github.com/getsentry/sentry-react-native/pull/3857))
1010

src/js/tracing/reactnativetracing.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,10 @@ export class ReactNativeTracing implements Integration {
554554
*/
555555
private _addNativeSpansTo(appStartSpan: Span, nativeSpans: NativeAppStartResponse['spans']): void {
556556
nativeSpans.forEach(span => {
557+
if (span.description === 'UIKit init') {
558+
return this._createUIKitSpan(appStartSpan, span);
559+
}
560+
557561
appStartSpan.startChild({
558562
op: appStartSpan.op,
559563
description: span.description,
@@ -563,6 +567,33 @@ export class ReactNativeTracing implements Integration {
563567
});
564568
}
565569

570+
/**
571+
* UIKit init is measured by the native layers till the native SDK start
572+
* RN initializes the native SDK later, the end timestamp would be wrong
573+
*/
574+
private _createUIKitSpan(parentSpan: Span, nativeUIKitSpan: NativeAppStartResponse['spans'][number]): void {
575+
const bundleStart = getBundleStartTimestampMs();
576+
577+
// If UIKit init ends after the bundle start, the native SDK was auto-initialized
578+
// and so the end timestamp is incorrect.
579+
// The timestamps can't equal, as RN initializes after UIKit.
580+
if (bundleStart && bundleStart < nativeUIKitSpan.end_timestamp_ms) {
581+
parentSpan.startChild({
582+
op: parentSpan.op,
583+
description: 'UIKit Init to JS Exec Start',
584+
startTimestamp: nativeUIKitSpan.start_timestamp_ms / 1000,
585+
endTimestamp: bundleStart / 1000,
586+
});
587+
} else {
588+
parentSpan.startChild({
589+
op: parentSpan.op,
590+
description: 'UIKit Init',
591+
startTimestamp: nativeUIKitSpan.start_timestamp_ms / 1000,
592+
endTimestamp: nativeUIKitSpan.end_timestamp_ms / 1000,
593+
});
594+
}
595+
}
596+
566597
/** To be called when the route changes, but BEFORE the components of the new route mount. */
567598
private _onRouteWillChange(context: TransactionContext): TransactionType | undefined {
568599
return this._createRouteTransaction(context);

test/tracing/reactnativetracing.test.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,82 @@ describe('ReactNativeTracing', () => {
478478
}),
479479
);
480480
});
481+
482+
it('adds ui kit init full length as a child of the main app start span', async () => {
483+
const integration = new ReactNativeTracing();
484+
485+
const timeOriginMilliseconds = Date.now();
486+
mockAppStartResponse({
487+
cold: true,
488+
enableNativeSpans: true,
489+
customNativeSpans: [
490+
{
491+
description: 'UIKit init',
492+
start_timestamp_ms: timeOriginMilliseconds - 100,
493+
end_timestamp_ms: timeOriginMilliseconds - 60,
494+
},
495+
],
496+
});
497+
mockReactNativeBundleExecutionStartTimestamp();
498+
499+
setup(integration);
500+
501+
await jest.advanceTimersByTimeAsync(500);
502+
await jest.runOnlyPendingTimersAsync();
503+
504+
const transaction = client.event;
505+
506+
const nativeSpan = transaction!.spans!.find(({ description }) => description?.startsWith('UIKit Init'));
507+
const nativeSpanJSON = spanToJSON(nativeSpan!);
508+
509+
expect(nativeSpan).toBeDefined();
510+
expect(nativeSpanJSON).toEqual(
511+
expect.objectContaining(<SpanJSON>{
512+
description: 'UIKit Init',
513+
start_timestamp: (timeOriginMilliseconds - 100) / 1000,
514+
timestamp: (timeOriginMilliseconds - 60) / 1000,
515+
}),
516+
);
517+
});
518+
519+
it('adds ui kit init start mark as a child of the main app start span', async () => {
520+
const integration = new ReactNativeTracing();
521+
522+
const timeOriginMilliseconds = Date.now();
523+
mockAppStartResponse({
524+
cold: true,
525+
enableNativeSpans: true,
526+
customNativeSpans: [
527+
{
528+
description: 'UIKit init',
529+
start_timestamp_ms: timeOriginMilliseconds - 100,
530+
end_timestamp_ms: timeOriginMilliseconds - 20, // After mocked bundle execution start
531+
},
532+
],
533+
});
534+
mockReactNativeBundleExecutionStartTimestamp();
535+
536+
setup(integration);
537+
538+
await jest.advanceTimersByTimeAsync(500);
539+
await jest.runOnlyPendingTimersAsync();
540+
541+
const transaction = client.event;
542+
543+
const nativeRuntimeInitSpan = transaction!.spans!.find(({ description }) =>
544+
description?.startsWith('UIKit Init to JS Exec Start'),
545+
);
546+
const nativeRuntimeInitSpanJSON = spanToJSON(nativeRuntimeInitSpan!);
547+
548+
expect(nativeRuntimeInitSpanJSON).toBeDefined();
549+
expect(nativeRuntimeInitSpanJSON).toEqual(
550+
expect.objectContaining(<SpanJSON>{
551+
description: 'UIKit Init to JS Exec Start',
552+
start_timestamp: (timeOriginMilliseconds - 100) / 1000,
553+
timestamp: (timeOriginMilliseconds - 50) / 1000,
554+
}),
555+
);
556+
});
481557
});
482558

483559
describe('With routing instrumentation', () => {
@@ -1068,10 +1144,12 @@ function mockAppStartResponse({
10681144
cold,
10691145
has_fetched,
10701146
enableNativeSpans,
1147+
customNativeSpans,
10711148
}: {
10721149
cold: boolean;
10731150
has_fetched?: boolean;
10741151
enableNativeSpans?: boolean;
1152+
customNativeSpans?: NativeAppStartResponse['spans'];
10751153
}) {
10761154
const timeOriginMilliseconds = Date.now();
10771155
const appStartTimeMilliseconds = timeOriginMilliseconds - 100;
@@ -1086,6 +1164,7 @@ function mockAppStartResponse({
10861164
start_timestamp_ms: timeOriginMilliseconds - 100,
10871165
end_timestamp_ms: timeOriginMilliseconds - 50,
10881166
},
1167+
...(customNativeSpans ?? []),
10891168
]
10901169
: [],
10911170
};

0 commit comments

Comments
 (0)