Skip to content

Commit 69931fa

Browse files
Fix: App start time span no longer created if too long (#3299)
1 parent ae673b2 commit 69931fa

File tree

3 files changed

+62
-16
lines changed

3 files changed

+62
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353

5454
### Fixes
5555

56+
- App start time span no longer created if too long ([#3299](https://github.com/getsentry/sentry-react-native/pull/3299))
5657
- Change log output to show what paths are considered when collecting modules ([#3316](https://github.com/getsentry/sentry-react-native/pull/3316))
5758
- `Sentry.wrap` doesn't enforce any keys on the wrapped component props ([#3332](https://github.com/getsentry/sentry-react-native/pull/3332))
5859
- Ignore defaults when warning about duplicate definition of trace propagation targets ([#3327](https://github.com/getsentry/sentry-react-native/pull/3327))

src/js/tracing/reactnativetracing.ts

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,17 @@ export class ReactNativeTracing implements Integration {
350350
return this._inflightInteractionTransaction;
351351
}
352352

353+
/**
354+
* Returns the App Start Duration in Milliseconds. Also returns undefined if not able do
355+
* define the duration.
356+
*/
357+
private _getAppStartDurationMilliseconds(appStart: NativeAppStartResponse): number | undefined {
358+
if (!this._appStartFinishTimestamp) {
359+
return undefined;
360+
}
361+
return this._appStartFinishTimestamp * 1000 - appStart.appStartTime;
362+
}
363+
353364
/**
354365
* Instruments the app start measurements on the first route transaction.
355366
* Starts a route transaction if there isn't routing instrumentation.
@@ -372,12 +383,9 @@ export class ReactNativeTracing implements Integration {
372383
if (this.options.routingInstrumentation) {
373384
this._awaitingAppStartData = appStart;
374385
} else {
375-
const appStartTimeSeconds = appStart.appStartTime / 1000;
376-
377386
const idleTransaction = this._createRouteTransaction({
378387
name: 'App Start',
379388
op: UI_LOAD,
380-
startTimestamp: appStartTimeSeconds,
381389
});
382390

383391
if (idleTransaction) {
@@ -390,13 +398,23 @@ export class ReactNativeTracing implements Integration {
390398
* Adds app start measurements and starts a child span on a transaction.
391399
*/
392400
private _addAppStartData(transaction: IdleTransaction, appStart: NativeAppStartResponse): void {
393-
if (!this._appStartFinishTimestamp) {
401+
const appStartDurationMilliseconds = this._getAppStartDurationMilliseconds(appStart);
402+
if (!appStartDurationMilliseconds) {
394403
logger.warn('App start was never finished.');
395404
return;
396405
}
397406

407+
// we filter out app start more than 60s.
408+
// this could be due to many different reasons.
409+
// we've seen app starts with hours, days and even months.
410+
if (appStartDurationMilliseconds >= ReactNativeTracing._maxAppStart) {
411+
return;
412+
}
413+
398414
const appStartTimeSeconds = appStart.appStartTime / 1000;
399415

416+
transaction.startTimestamp = appStartTimeSeconds;
417+
400418
const op = appStart.isColdStart ? APP_START_COLD_OP : APP_START_WARM_OP;
401419
transaction.startChild({
402420
description: appStart.isColdStart ? 'Cold App Start' : 'Warm App Start',
@@ -405,15 +423,6 @@ export class ReactNativeTracing implements Integration {
405423
endTimestamp: this._appStartFinishTimestamp,
406424
});
407425

408-
const appStartDurationMilliseconds = this._appStartFinishTimestamp * 1000 - appStart.appStartTime;
409-
410-
// we filter out app start more than 60s.
411-
// this could be due to many different reasons.
412-
// we've seen app starts with hours, days and even months.
413-
if (appStartDurationMilliseconds >= ReactNativeTracing._maxAppStart) {
414-
return;
415-
}
416-
417426
const measurement = appStart.isColdStart ? APP_START_COLD : APP_START_WARM;
418427
transaction.setMeasurement(measurement, appStartDurationMilliseconds, 'millisecond');
419428
}
@@ -484,9 +493,7 @@ export class ReactNativeTracing implements Integration {
484493

485494
idleTransaction.registerBeforeFinishCallback(transaction => {
486495
if (this.options.enableAppStartTracking && this._awaitingAppStartData) {
487-
transaction.startTimestamp = this._awaitingAppStartData.appStartTime / 1000;
488-
transaction.op = 'ui.load';
489-
496+
transaction.op = UI_LOAD;
490497
this._addAppStartData(transaction, this._awaitingAppStartData);
491498

492499
this._awaitingAppStartData = undefined;

test/tracing/reactnativetracing.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ import {
104104
APP_START_WARM as APP_START_WARM_OP,
105105
UI_LOAD,
106106
} from '../../src/js/tracing';
107+
import { APP_START_WARM as APP_SPAN_START_WARM } from '../../src/js/tracing/ops';
107108
import { ReactNativeTracing } from '../../src/js/tracing/reactnativetracing';
108109
import { getTimeOriginMilliseconds } from '../../src/js/tracing/utils';
109110
import { NATIVE } from '../../src/js/wrapper';
@@ -386,6 +387,43 @@ describe('ReactNativeTracing', () => {
386387
}
387388
});
388389

390+
it('Does not add app start span if more than 60s', async () => {
391+
const integration = new ReactNativeTracing();
392+
393+
const timeOriginMilliseconds = Date.now();
394+
const appStartTimeMilliseconds = timeOriginMilliseconds - 65000;
395+
const mockAppStartResponse: NativeAppStartResponse = {
396+
isColdStart: false,
397+
appStartTime: appStartTimeMilliseconds,
398+
didFetchAppStart: false,
399+
};
400+
401+
mockFunction(getTimeOriginMilliseconds).mockReturnValue(timeOriginMilliseconds);
402+
mockFunction(NATIVE.fetchNativeAppStart).mockResolvedValue(mockAppStartResponse);
403+
404+
const mockHub = getMockHub();
405+
integration.setupOnce(addGlobalEventProcessor, () => mockHub);
406+
407+
await jest.advanceTimersByTimeAsync(500);
408+
409+
const transaction = mockHub.getScope()?.getTransaction();
410+
411+
expect(transaction).toBeDefined();
412+
413+
if (transaction) {
414+
expect(
415+
// @ts-expect-error access private for test
416+
transaction.spanRecorder,
417+
).toBeDefined();
418+
419+
expect(
420+
// @ts-expect-error access private for test
421+
transaction.spanRecorder.spans.some(span => span.op == APP_SPAN_START_WARM),
422+
).toBe(false);
423+
expect(transaction.startTimestamp).toBeGreaterThanOrEqual(timeOriginMilliseconds / 1000);
424+
}
425+
});
426+
389427
it('Does not create app start transaction if didFetchAppStart == true', async () => {
390428
const integration = new ReactNativeTracing();
391429

0 commit comments

Comments
 (0)