Skip to content

Commit b2affff

Browse files
authored
Merge 1be2f64 into 7d3c3cb
2 parents 7d3c3cb + 1be2f64 commit b2affff

File tree

4 files changed

+287
-16
lines changed

4 files changed

+287
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
- Use engine-specific promise rejection tracking ([#4826](https://github.com/getsentry/sentry-react-native/pull/4826))
4545
- Fixes Feedback Widget accessibility issue on iOS ([#4739](https://github.com/getsentry/sentry-react-native/pull/4739))
4646
- Measuring TTID or TTFD could cause a crash when `parentSpanId` was removed ([#4881](https://github.com/getsentry/sentry-react-native/pull/4881))
47+
- Report slow and frozen frames as app start span data ([#4865](https://github.com/getsentry/sentry-react-native/pull/4865))
4748

4849
### Dependencies
4950

packages/core/src/js/tracing/integrations/appStart.ts

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
APP_START_COLD as APP_START_COLD_MEASUREMENT,
1717
APP_START_WARM as APP_START_WARM_MEASUREMENT,
1818
} from '../../measurements';
19-
import type { NativeAppStartResponse } from '../../NativeRNSentry';
19+
import type { NativeAppStartResponse, NativeFramesResponse } from '../../NativeRNSentry';
2020
import type { ReactNativeClientOptions } from '../../options';
2121
import { convertSpanToTransaction, isRootSpan, setEndTimeValue } from '../../utils/span';
2222
import { NATIVE } from '../../wrapper';
@@ -49,7 +49,12 @@ const MAX_APP_START_AGE_MS = 60_000;
4949
/** App Start transaction name */
5050
const APP_START_TX_NAME = 'App Start';
5151

52-
let recordedAppStartEndTimestampMs: number | undefined = undefined;
52+
interface AppStartEndData {
53+
timestampMs: number;
54+
endFrames: NativeFramesResponse | null;
55+
}
56+
57+
let appStartEndData: AppStartEndData | undefined = undefined;
5358
let isRecordedAppStartEndTimestampMsManual = false;
5459

5560
let rootComponentCreationTimestampMs: number | undefined = undefined;
@@ -76,7 +81,24 @@ export async function _captureAppStart({ isManual }: { isManual: boolean }): Pro
7681
}
7782

7883
isRecordedAppStartEndTimestampMsManual = isManual;
79-
_setAppStartEndTimestampMs(timestampInSeconds() * 1000);
84+
85+
const timestampMs = timestampInSeconds() * 1000;
86+
let endFrames: NativeFramesResponse | null = null;
87+
88+
if (NATIVE.enableNative) {
89+
try {
90+
endFrames = await NATIVE.fetchNativeFrames();
91+
logger.debug('[AppStart] Captured end frames for app start.', endFrames);
92+
} catch (error) {
93+
logger.debug('[AppStart] Failed to capture end frames for app start.', error);
94+
}
95+
}
96+
97+
_setAppStartEndData({
98+
timestampMs,
99+
endFrames,
100+
});
101+
80102
await client.getIntegrationByName<AppStartIntegration>(INTEGRATION_NAME)?.captureStandaloneAppStart();
81103
}
82104

@@ -85,8 +107,7 @@ export async function _captureAppStart({ isManual }: { isManual: boolean }): Pro
85107
* Used automatically by `Sentry.wrap` and `Sentry.ReactNativeProfiler`.
86108
*/
87109
export function setRootComponentCreationTimestampMs(timestampMs: number): void {
88-
recordedAppStartEndTimestampMs &&
89-
logger.warn('Setting Root component creation timestamp after app start end is set.');
110+
appStartEndData?.timestampMs && logger.warn('Setting Root component creation timestamp after app start end is set.');
90111
rootComponentCreationTimestampMs && logger.warn('Overwriting already set root component creation timestamp.');
91112
rootComponentCreationTimestampMs = timestampMs;
92113
isRootComponentCreationTimestampMsManual = true;
@@ -107,9 +128,9 @@ export function _setRootComponentCreationTimestampMs(timestampMs: number): void
107128
*
108129
* @private
109130
*/
110-
export const _setAppStartEndTimestampMs = (timestampMs: number): void => {
111-
recordedAppStartEndTimestampMs && logger.warn('Overwriting already set app start.');
112-
recordedAppStartEndTimestampMs = timestampMs;
131+
export const _setAppStartEndData = (data: AppStartEndData): void => {
132+
appStartEndData && logger.warn('Overwriting already set app start end data.');
133+
appStartEndData = data;
113134
};
114135

115136
/**
@@ -121,6 +142,29 @@ export function _clearRootComponentCreationTimestampMs(): void {
121142
rootComponentCreationTimestampMs = undefined;
122143
}
123144

145+
/**
146+
* Attaches frame data to a span's data object.
147+
*/
148+
function attachFrameDataToSpan(span: SpanJSON, frames: NativeFramesResponse): void {
149+
if (frames.totalFrames <= 0 && frames.slowFrames <= 0 && frames.totalFrames <= 0) {
150+
logger.warn(`[AppStart] Detected zero slow or frozen frames. Not adding measurements to spanId (${span.span_id}).`);
151+
return;
152+
}
153+
span.data = span.data || {};
154+
span.data['frames.total'] = frames.totalFrames;
155+
span.data['frames.slow'] = frames.slowFrames;
156+
span.data['frames.frozen'] = frames.frozenFrames;
157+
158+
logger.debug('[AppStart] Attached frame data to span.', {
159+
spanId: span.span_id,
160+
frameData: {
161+
total: frames.totalFrames,
162+
slow: frames.slowFrames,
163+
frozen: frames.frozenFrames,
164+
},
165+
});
166+
}
167+
124168
/**
125169
* Adds AppStart spans from the native layer to the transaction event.
126170
*/
@@ -220,6 +264,21 @@ export const appStartIntegration = ({
220264

221265
logger.debug('[AppStart] App start tracking standalone root span (transaction).');
222266

267+
if (!appStartEndData?.endFrames && NATIVE.enableNative) {
268+
try {
269+
const endFrames = await NATIVE.fetchNativeFrames();
270+
logger.debug('[AppStart] Captured end frames for standalone app start.', endFrames);
271+
272+
const currentTimestamp = appStartEndData?.timestampMs || timestampInSeconds() * 1000;
273+
_setAppStartEndData({
274+
timestampMs: currentTimestamp,
275+
endFrames,
276+
});
277+
} catch (error) {
278+
logger.debug('[AppStart] Failed to capture frames for standalone app start.', error);
279+
}
280+
}
281+
223282
const span = startInactiveSpan({
224283
forceTransaction: true,
225284
name: APP_START_TX_NAME,
@@ -288,10 +347,10 @@ export const appStartIntegration = ({
288347
return;
289348
}
290349

291-
const appStartEndTimestampMs = recordedAppStartEndTimestampMs || getBundleStartTimestampMs();
350+
const appStartEndTimestampMs = appStartEndData?.timestampMs || getBundleStartTimestampMs();
292351
if (!appStartEndTimestampMs) {
293352
logger.warn(
294-
'[AppStart] Javascript failed to record app start end. `setAppStartEndTimestampMs` was not called nor could the bundle start be found.',
353+
'[AppStart] Javascript failed to record app start end. `_setAppStartEndData` was not called nor could the bundle start be found.',
295354
);
296355
return;
297356
}
@@ -368,6 +427,11 @@ export const appStartIntegration = ({
368427
parent_span_id: event.contexts.trace.span_id,
369428
origin,
370429
});
430+
431+
if (appStartEndData?.endFrames) {
432+
attachFrameDataToSpan(appStartSpanJSON, appStartEndData.endFrames);
433+
}
434+
371435
const jsExecutionSpanJSON = createJSExecutionStartSpan(appStartSpanJSON, rootComponentCreationTimestampMs);
372436

373437
const appStartSpans = [

0 commit comments

Comments
 (0)