Skip to content

✨ Add input, process and render durations to INP #3048

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion packages/rum-core/src/domain/view/viewCollection.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,13 @@ const VIEW: ViewEvent = {
commonViewMetrics: {
loadingTime: 20 as Duration,
cumulativeLayoutShift: { value: 1, time: 100 as Duration },
interactionToNextPaint: { value: 10 as Duration, time: 100 as Duration },
interactionToNextPaint: {
value: 10 as Duration,
time: 100 as Duration,
input: 2 as Duration,
process: 5 as Duration,
render: 3 as Duration,
},
scroll: {
maxDepth: 2000,
maxScrollHeight: 3000,
Expand Down Expand Up @@ -154,7 +160,11 @@ describe('viewCollection', () => {
first_input_target_selector: undefined,
interaction_to_next_paint: (10 * 1e6) as ServerDuration,
interaction_to_next_paint_target_selector: undefined,
interaction_to_next_paint_type: undefined,
interaction_to_next_paint_time: (100 * 1e6) as ServerDuration,
interaction_to_next_paint_input: (2 * 1e6) as ServerDuration,
interaction_to_next_paint_process: (5 * 1e6) as ServerDuration,
interaction_to_next_paint_render: (3 * 1e6) as ServerDuration,
is_active: false,
name: undefined,
largest_contentful_paint: (10 * 1e6) as ServerDuration,
Expand Down
4 changes: 4 additions & 0 deletions packages/rum-core/src/domain/view/viewCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ function processViewUpdate(
interaction_to_next_paint: toServerDuration(view.commonViewMetrics.interactionToNextPaint?.value),
interaction_to_next_paint_time: toServerDuration(view.commonViewMetrics.interactionToNextPaint?.time),
interaction_to_next_paint_target_selector: view.commonViewMetrics.interactionToNextPaint?.targetSelector,
interaction_to_next_paint_type: view.commonViewMetrics.interactionToNextPaint?.type,
interaction_to_next_paint_input: toServerDuration(view.commonViewMetrics.interactionToNextPaint?.input),
interaction_to_next_paint_process: toServerDuration(view.commonViewMetrics.interactionToNextPaint?.process),
interaction_to_next_paint_render: toServerDuration(view.commonViewMetrics.interactionToNextPaint?.render),
is_active: view.isActive,
name: view.name,
largest_contentful_paint: toServerDuration(view.initialViewMetrics.largestContentfulPaint?.value),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,20 @@ describe('trackInteractionToNextPaint', () => {
setViewEnd(10 as RelativeTime)

newInteraction({
name: 'pointerup',
interactionId: 1,
duration: 10 as Duration,
startTime: -1 as RelativeTime,
processingStart: 0 as RelativeTime,
processingEnd: 6 as RelativeTime,
})
newInteraction({
name: 'pointerup',
interactionId: 2,
duration: 10 as Duration,
startTime: 11 as RelativeTime,
processingStart: 12 as RelativeTime,
processingEnd: 16 as RelativeTime,
})
expect(getInteractionToNextPaint()).toEqual(undefined)
})
Expand All @@ -98,29 +104,43 @@ describe('trackInteractionToNextPaint', () => {
setViewEnd(10 as RelativeTime)

newInteraction({
name: 'pointerup',
interactionId: 1,
duration: 100 as Duration,
startTime: 1 as RelativeTime,
processingStart: 2 as RelativeTime,
processingEnd: 80 as RelativeTime,
})
expect(getInteractionToNextPaint()).toEqual({
value: 100 as Duration,
targetSelector: undefined,
time: 1 as RelativeTime,
type: 'pointerup',
input: 1 as Duration,
process: 78 as Duration,
render: 21 as Duration,
})
})

it('should cap INP value', () => {
startINPTracking()
newInteraction({
name: 'pointerup',
interactionId: 1,
duration: (MAX_INP_VALUE + 1) as Duration,
startTime: 1 as RelativeTime,
startTime: 10 as RelativeTime,
processingStart: 15 as RelativeTime,
processingEnd: 80 as RelativeTime,
})

expect(getInteractionToNextPaint()).toEqual({
value: MAX_INP_VALUE,
targetSelector: undefined,
time: 1 as RelativeTime,
time: 10 as RelativeTime,
type: 'pointerup',
input: 5 as Duration,
process: 65 as Duration,
render: (MAX_INP_VALUE + 10 - 80 + 1) as Duration,
})
})

Expand All @@ -130,13 +150,20 @@ describe('trackInteractionToNextPaint', () => {
newInteraction({
duration: index as Duration,
interactionId: index,
startTime: index as RelativeTime,
startTime: 1 as RelativeTime,
name: 'pointerup',
processingStart: 1 as RelativeTime,
processingEnd: index as RelativeTime,
})
}
expect(getInteractionToNextPaint()).toEqual({
value: 98 as Duration,
targetSelector: undefined,
time: 98 as RelativeTime,
time: 1 as RelativeTime,
type: 'pointerup',
input: 0 as RelativeTime,
process: 97 as RelativeTime,
render: 1 as RelativeTime,
})
})

Expand All @@ -152,11 +179,18 @@ describe('trackInteractionToNextPaint', () => {
interactionId: 1,
entryType: RumPerformanceEntryType.FIRST_INPUT,
startTime: 1 as RelativeTime,
name: 'pointerup',
processingStart: 1 as RelativeTime,
processingEnd: 5 as RelativeTime,
})
expect(getInteractionToNextPaint()).toEqual({
value: 40 as Duration,
targetSelector: undefined,
time: 1 as RelativeTime,
type: 'pointerup',
input: 0 as RelativeTime,
process: 4 as RelativeTime,
render: 36 as RelativeTime,
})
})

Expand All @@ -166,14 +200,21 @@ describe('trackInteractionToNextPaint', () => {
newInteraction({
duration: index as Duration,
interactionId: 1,
startTime: index as RelativeTime,
startTime: 1 as RelativeTime,
name: 'pointerup',
processingStart: 1 as RelativeTime,
processingEnd: index as RelativeTime,
})
}
// the p98 return 100 which shows that the entry has been updated
expect(getInteractionToNextPaint()).toEqual({
value: 100 as Duration,
targetSelector: undefined,
time: 100 as RelativeTime,
time: 1 as RelativeTime,
type: 'pointerup',
input: 0 as Duration,
process: 99 as Duration,
render: 1 as Duration,
})
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ export const MAX_INP_VALUE = (1 * ONE_MINUTE) as Duration
export interface InteractionToNextPaint {
value: Duration
targetSelector?: string
type?: string
time?: Duration
input?: Duration
process?: Duration
render?: Duration
}
/**
* Track the interaction to next paint (INP).
Expand Down Expand Up @@ -48,7 +52,11 @@ export function trackInteractionToNextPaint(
const longestInteractions = trackLongestInteractions(getViewInteractionCount)
let interactionToNextPaint = -1 as Duration
let interactionToNextPaintTargetSelector: string | undefined
let interactionToNextPaintType: string | undefined
let interactionToNextPaintStartTime: Duration | undefined
let interactionToNextPaintInputTime: Duration | undefined
let interactionToNextPaintProcessingTime: Duration | undefined
let interactionToNextPaintRenderTime: Duration | undefined

function handleEntries(entries: Array<RumPerformanceEventTiming | RumFirstInputTiming>) {
for (const entry of entries) {
Expand All @@ -65,7 +73,11 @@ export function trackInteractionToNextPaint(
const newInteraction = longestInteractions.estimateP98Interaction()
if (newInteraction && newInteraction.duration !== interactionToNextPaint) {
interactionToNextPaint = newInteraction.duration
interactionToNextPaintType = newInteraction.name
interactionToNextPaintStartTime = elapsed(viewStart, newInteraction.startTime)
interactionToNextPaintInputTime = elapsed(newInteraction.startTime, newInteraction.processingStart)
interactionToNextPaintProcessingTime = elapsed(newInteraction.processingStart, newInteraction.processingEnd)
interactionToNextPaintRenderTime = elapsed(newInteraction.processingEnd, (newInteraction.startTime + newInteraction.duration) as RelativeTime)

if (newInteraction.target && isElementNode(newInteraction.target)) {
interactionToNextPaintTargetSelector = getSelectorFromElement(
Expand Down Expand Up @@ -99,7 +111,11 @@ export function trackInteractionToNextPaint(
return {
value: Math.min(interactionToNextPaint, MAX_INP_VALUE) as Duration,
targetSelector: interactionToNextPaintTargetSelector,
type: interactionToNextPaintType,
time: interactionToNextPaintStartTime,
input: interactionToNextPaintInputTime,
process: interactionToNextPaintProcessingTime,
render: interactionToNextPaintRenderTime
}
} else if (getViewInteractionCount()) {
return {
Expand Down
4 changes: 4 additions & 0 deletions packages/rum-core/src/rawRumEvent.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,11 @@ export interface RawRumViewEvent {
first_input_target_selector?: string
interaction_to_next_paint?: ServerDuration
interaction_to_next_paint_time?: ServerDuration
interaction_to_next_paint_input?: ServerDuration
interaction_to_next_paint_process?: ServerDuration
interaction_to_next_paint_render?: ServerDuration
interaction_to_next_paint_target_selector?: string
interaction_to_next_paint_type?: string
cumulative_layout_shift?: number
cumulative_layout_shift_time?: ServerDuration
cumulative_layout_shift_target_selector?: string
Expand Down
16 changes: 16 additions & 0 deletions packages/rum-core/src/rumEvent.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -843,10 +843,26 @@ export type RumViewEvent = CommonProperties &
* Duration in ns between start of the view and start of the INP
*/
readonly interaction_to_next_paint_time?: number
/**
* Duration in ns between interaction and start of processing
*/
readonly interaction_to_next_paint_input?: number
/**
* Duration in ns between start and end of processing
*/
readonly interaction_to_next_paint_process?: number
/**
* Duration in ns between end of processing and end of the INP
*/
readonly interaction_to_next_paint_render?: number
/**
* CSS selector path of the interacted element corresponding to INP
*/
readonly interaction_to_next_paint_target_selector?: string
/**
* Interaction type corresponding to INP
*/
readonly interaction_to_next_paint_target_type?: string
/**
* Total layout shift score that occurred on the view
*/
Expand Down