Skip to content

Commit 691e5de

Browse files
c298leelforstandreiborzamydeaLms24
authored
feat(replay): Replay Web Vital Breadcrumbs (#12296)
Adds CLS, FID, and INP breadcrumbs. Updates all web vital breadcrumbs to include rating Closes #11639 --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Luca Forstner <luca.forstner@sentry.io> Co-authored-by: Andrei <168741329+andreiborza@users.noreply.github.com> Co-authored-by: Francesco Novy <francesco.novy@sentry.io> Co-authored-by: Lukas Stracke <lukas.stracke@sentry.io> Co-authored-by: Yamagishi Kazutoshi <ykzts@desire.sh> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1 parent 1df9c69 commit 691e5de

File tree

11 files changed

+284
-35
lines changed

11 files changed

+284
-35
lines changed

dev-packages/browser-integration-tests/suites/replay/customEvents/test.ts

+4
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import { expect } from '@playwright/test';
22

33
import { sentryTest } from '../../../utils/fixtures';
44
import {
5+
expectedCLSPerformanceSpan,
56
expectedClickBreadcrumb,
67
expectedFCPPerformanceSpan,
8+
expectedFIDPerformanceSpan,
79
expectedFPPerformanceSpan,
810
expectedLCPPerformanceSpan,
911
expectedMemoryPerformanceSpan,
@@ -62,6 +64,8 @@ sentryTest(
6264
expect.arrayContaining([
6365
expectedNavigationPerformanceSpan,
6466
expectedLCPPerformanceSpan,
67+
expectedCLSPerformanceSpan,
68+
expectedFIDPerformanceSpan,
6569
expectedFPPerformanceSpan,
6670
expectedFCPPerformanceSpan,
6771
expectedMemoryPerformanceSpan, // two memory spans - once per flush

dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import { expect } from '@playwright/test';
22

33
import { sentryTest } from '../../../utils/fixtures';
44
import {
5+
expectedCLSPerformanceSpan,
56
expectedClickBreadcrumb,
67
expectedFCPPerformanceSpan,
8+
expectedFIDPerformanceSpan,
79
expectedFPPerformanceSpan,
810
expectedLCPPerformanceSpan,
911
expectedMemoryPerformanceSpan,
@@ -78,11 +80,13 @@ sentryTest(
7880
const collectedPerformanceSpans = [...recording0.performanceSpans, ...recording1.performanceSpans];
7981
const collectedBreadcrumbs = [...recording0.breadcrumbs, ...recording1.breadcrumbs];
8082

81-
expect(collectedPerformanceSpans.length).toEqual(6);
83+
expect(collectedPerformanceSpans.length).toEqual(8);
8284
expect(collectedPerformanceSpans).toEqual(
8385
expect.arrayContaining([
8486
expectedNavigationPerformanceSpan,
8587
expectedLCPPerformanceSpan,
88+
expectedCLSPerformanceSpan,
89+
expectedFIDPerformanceSpan,
8690
expectedFPPerformanceSpan,
8791
expectedFCPPerformanceSpan,
8892
expectedMemoryPerformanceSpan, // two memory spans - once per flush
@@ -116,11 +120,13 @@ sentryTest(
116120
const collectedPerformanceSpansAfterReload = [...recording2.performanceSpans, ...recording3.performanceSpans];
117121
const collectedBreadcrumbsAdterReload = [...recording2.breadcrumbs, ...recording3.breadcrumbs];
118122

119-
expect(collectedPerformanceSpansAfterReload.length).toEqual(6);
123+
expect(collectedPerformanceSpansAfterReload.length).toEqual(8);
120124
expect(collectedPerformanceSpansAfterReload).toEqual(
121125
expect.arrayContaining([
122126
expectedReloadPerformanceSpan,
123127
expectedLCPPerformanceSpan,
128+
expectedCLSPerformanceSpan,
129+
expectedFIDPerformanceSpan,
124130
expectedFPPerformanceSpan,
125131
expectedFCPPerformanceSpan,
126132
expectedMemoryPerformanceSpan,
@@ -188,6 +194,8 @@ sentryTest(
188194
expect.arrayContaining([
189195
expectedNavigationPerformanceSpan,
190196
expectedLCPPerformanceSpan,
197+
expectedCLSPerformanceSpan,
198+
expectedFIDPerformanceSpan,
191199
expectedFPPerformanceSpan,
192200
expectedFCPPerformanceSpan,
193201
expectedMemoryPerformanceSpan,
@@ -304,11 +312,13 @@ sentryTest(
304312
];
305313
const collectedBreadcrumbsAfterIndexNavigation = [...recording8.breadcrumbs, ...recording9.breadcrumbs];
306314

307-
expect(collectedPerformanceSpansAfterIndexNavigation.length).toEqual(6);
315+
expect(collectedPerformanceSpansAfterIndexNavigation.length).toEqual(8);
308316
expect(collectedPerformanceSpansAfterIndexNavigation).toEqual(
309317
expect.arrayContaining([
310318
expectedNavigationPerformanceSpan,
311319
expectedLCPPerformanceSpan,
320+
expectedCLSPerformanceSpan,
321+
expectedFIDPerformanceSpan,
312322
expectedFPPerformanceSpan,
313323
expectedFCPPerformanceSpan,
314324
expectedMemoryPerformanceSpan,

dev-packages/browser-integration-tests/utils/replayEventTemplates.ts

+40-1
Original file line numberDiff line numberDiff line change
@@ -121,17 +121,56 @@ export const expectedMemoryPerformanceSpan = {
121121
};
122122

123123
export const expectedLCPPerformanceSpan = {
124-
op: 'largest-contentful-paint',
124+
op: 'web-vital',
125125
description: 'largest-contentful-paint',
126126
startTimestamp: expect.any(Number),
127127
endTimestamp: expect.any(Number),
128128
data: {
129129
value: expect.any(Number),
130130
nodeId: expect.any(Number),
131+
rating: expect.any(String),
131132
size: expect.any(Number),
132133
},
133134
};
134135

136+
export const expectedCLSPerformanceSpan = {
137+
op: 'web-vital',
138+
description: 'cumulative-layout-shift',
139+
startTimestamp: expect.any(Number),
140+
endTimestamp: expect.any(Number),
141+
data: {
142+
value: expect.any(Number),
143+
rating: expect.any(String),
144+
size: expect.any(Number),
145+
},
146+
};
147+
148+
export const expectedFIDPerformanceSpan = {
149+
op: 'web-vital',
150+
description: 'first-input-delay',
151+
startTimestamp: expect.any(Number),
152+
endTimestamp: expect.any(Number),
153+
data: {
154+
value: expect.any(Number),
155+
rating: expect.any(String),
156+
size: expect.any(Number),
157+
nodeId: expect.any(Number),
158+
},
159+
};
160+
161+
export const expectedINPPerformanceSpan = {
162+
op: 'web-vital',
163+
description: 'interaction-to-next-paint',
164+
startTimestamp: expect.any(Number),
165+
endTimestamp: expect.any(Number),
166+
data: {
167+
value: expect.any(Number),
168+
rating: expect.any(String),
169+
size: expect.any(Number),
170+
nodeId: expect.any(Number),
171+
},
172+
};
173+
135174
export const expectedFCPPerformanceSpan = {
136175
op: 'paint',
137176
description: 'first-contentful-paint',

dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/fixtures/ReplayRecordingData.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ export const ReplayRecordingData = [
212212
data: {
213213
tag: 'performanceSpan',
214214
payload: {
215-
op: 'largest-contentful-paint',
215+
op: 'web-vital',
216216
description: 'largest-contentful-paint',
217217
startTimestamp: expect.any(Number),
218218
endTimestamp: expect.any(Number),

dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/fixtures/ReplayRecordingData.ts

+39-1
Original file line numberDiff line numberDiff line change
@@ -212,18 +212,56 @@ export const ReplayRecordingData = [
212212
data: {
213213
tag: 'performanceSpan',
214214
payload: {
215-
op: 'largest-contentful-paint',
215+
op: 'web-vital',
216216
description: 'largest-contentful-paint',
217217
startTimestamp: expect.any(Number),
218218
endTimestamp: expect.any(Number),
219219
data: {
220220
value: expect.any(Number),
221221
size: expect.any(Number),
222+
rating: expect.any(String),
222223
nodeId: 16,
223224
},
224225
},
225226
},
226227
},
228+
{
229+
type: 5,
230+
timestamp: expect.any(Number),
231+
data: {
232+
tag: 'performanceSpan',
233+
payload: {
234+
op: 'web-vital',
235+
description: 'cumulative-layout-shift',
236+
startTimestamp: expect.any(Number),
237+
endTimestamp: expect.any(Number),
238+
data: {
239+
value: expect.any(Number),
240+
size: expect.any(Number),
241+
rating: expect.any(String),
242+
},
243+
},
244+
},
245+
},
246+
{
247+
type: 5,
248+
timestamp: expect.any(Number),
249+
data: {
250+
tag: 'performanceSpan',
251+
payload: {
252+
op: 'web-vital',
253+
description: 'first-input-delay',
254+
startTimestamp: expect.any(Number),
255+
endTimestamp: expect.any(Number),
256+
data: {
257+
value: expect.any(Number),
258+
size: expect.any(Number),
259+
rating: expect.any(String),
260+
nodeId: 10,
261+
},
262+
},
263+
},
264+
},
227265
{
228266
type: 5,
229267
timestamp: expect.any(Number),

packages/browser-utils/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export {
44
addFidInstrumentationHandler,
55
addTtfbInstrumentationHandler,
66
addLcpInstrumentationHandler,
7+
addInpInstrumentationHandler,
78
} from './metrics/instrument';
89

910
export {

packages/replay-internal/src/coreHandlers/performanceObserver.ts

+18-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
1-
import { addLcpInstrumentationHandler, addPerformanceInstrumentationHandler } from '@sentry-internal/browser-utils';
2-
1+
import {
2+
addClsInstrumentationHandler,
3+
addFidInstrumentationHandler,
4+
addInpInstrumentationHandler,
5+
addLcpInstrumentationHandler,
6+
addPerformanceInstrumentationHandler,
7+
} from '@sentry-internal/browser-utils';
38
import type { ReplayContainer } from '../types';
4-
import { getLargestContentfulPaint } from '../util/createPerformanceEntries';
9+
import {
10+
getCumulativeLayoutShift,
11+
getFirstInputDelay,
12+
getInteractionToNextPaint,
13+
getLargestContentfulPaint,
14+
webVitalHandler,
15+
} from '../util/createPerformanceEntries';
516

617
/**
718
* Sets up a PerformanceObserver to listen to all performance entry types.
@@ -26,9 +37,10 @@ export function setupPerformanceObserver(replay: ReplayContainer): () => void {
2637
});
2738

2839
clearCallbacks.push(
29-
addLcpInstrumentationHandler(({ metric }) => {
30-
replay.replayPerformanceEntries.push(getLargestContentfulPaint(metric));
31-
}),
40+
addLcpInstrumentationHandler(webVitalHandler(getLargestContentfulPaint, replay)),
41+
addClsInstrumentationHandler(webVitalHandler(getCumulativeLayoutShift, replay)),
42+
addFidInstrumentationHandler(webVitalHandler(getFirstInputDelay, replay)),
43+
addInpInstrumentationHandler(webVitalHandler(getInteractionToNextPaint, replay)),
3244
);
3345

3446
// A callback to cleanup all handlers

packages/replay-internal/src/types/performance.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,17 @@ export type ResourceData = Pick<PerformanceResourceTiming, 'decodedBodySize' | '
9696
statusCode?: number;
9797
};
9898

99-
export interface LargestContentfulPaintData {
99+
export interface WebVitalData {
100100
/**
101101
* Render time (in ms) of the LCP
102102
*/
103103
value: number;
104104
size: number;
105+
/**
106+
* The rating as to whether the metric value is within the "good",
107+
* "needs improvement", or "poor" thresholds of the metric.
108+
*/
109+
rating: 'good' | 'needs-improvement' | 'poor';
105110
/**
106111
* The recording id of the LCP node. -1 if not found
107112
*/
@@ -111,7 +116,7 @@ export interface LargestContentfulPaintData {
111116
/**
112117
* Entries that come from window.performance
113118
*/
114-
export type AllPerformanceEntryData = PaintData | NavigationData | ResourceData | LargestContentfulPaintData;
119+
export type AllPerformanceEntryData = PaintData | NavigationData | ResourceData | WebVitalData;
115120

116121
export interface MemoryData {
117122
memory: {

packages/replay-internal/src/types/replayFrame.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import type { Breadcrumb } from '@sentry/types';
22

33
import type {
44
HistoryData,
5-
LargestContentfulPaintData,
65
MemoryData,
76
NavigationData,
87
NetworkRequestData,
98
PaintData,
109
ResourceData,
10+
WebVitalData,
1111
} from './performance';
1212
import type { ReplayEventTypeCustom } from './rrweb';
1313

@@ -162,9 +162,9 @@ interface ReplayHistoryFrame extends ReplayBaseSpanFrame {
162162
op: 'navigation.push';
163163
}
164164

165-
interface ReplayLargestContentfulPaintFrame extends ReplayBaseSpanFrame {
166-
data: LargestContentfulPaintData;
167-
op: 'largest-contentful-paint';
165+
interface ReplayWebVitalFrame extends ReplayBaseSpanFrame {
166+
data: WebVitalData;
167+
op: 'largest-contentful-paint' | 'cumulative-layout-shift' | 'first-input-delay' | 'interaction-to-next-paint';
168168
}
169169

170170
interface ReplayMemoryFrame extends ReplayBaseSpanFrame {
@@ -196,7 +196,7 @@ export type ReplaySpanFrame =
196196
| ReplayBaseSpanFrame
197197
| ReplayHistoryFrame
198198
| ReplayRequestFrame
199-
| ReplayLargestContentfulPaintFrame
199+
| ReplayWebVitalFrame
200200
| ReplayMemoryFrame
201201
| ReplayNavigationFrame
202202
| ReplayPaintFrame

0 commit comments

Comments
 (0)