diff --git a/packages/rum-core/src/domain/view/viewCollection.spec.ts b/packages/rum-core/src/domain/view/viewCollection.spec.ts index a536155d10..309f249195 100644 --- a/packages/rum-core/src/domain/view/viewCollection.spec.ts +++ b/packages/rum-core/src/domain/view/viewCollection.spec.ts @@ -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, @@ -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, diff --git a/packages/rum-core/src/domain/view/viewCollection.ts b/packages/rum-core/src/domain/view/viewCollection.ts index fc822e8505..8a44b63f57 100644 --- a/packages/rum-core/src/domain/view/viewCollection.ts +++ b/packages/rum-core/src/domain/view/viewCollection.ts @@ -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), diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackInteractionToNextPaint.spec.ts b/packages/rum-core/src/domain/view/viewMetrics/trackInteractionToNextPaint.spec.ts index 83b4cf67c1..606ebbc72b 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackInteractionToNextPaint.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackInteractionToNextPaint.spec.ts @@ -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) }) @@ -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, }) }) @@ -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, }) }) @@ -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, }) }) @@ -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, }) }) diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackInteractionToNextPaint.ts b/packages/rum-core/src/domain/view/viewMetrics/trackInteractionToNextPaint.ts index dd0e20299e..f44206c5cd 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackInteractionToNextPaint.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackInteractionToNextPaint.ts @@ -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). @@ -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) { for (const entry of entries) { @@ -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( @@ -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 { diff --git a/packages/rum-core/src/rawRumEvent.types.ts b/packages/rum-core/src/rawRumEvent.types.ts index 482e3d46fe..f1fb2ce01a 100644 --- a/packages/rum-core/src/rawRumEvent.types.ts +++ b/packages/rum-core/src/rawRumEvent.types.ts @@ -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 diff --git a/packages/rum-core/src/rumEvent.types.ts b/packages/rum-core/src/rumEvent.types.ts index c9750326a2..19611d7dd3 100644 --- a/packages/rum-core/src/rumEvent.types.ts +++ b/packages/rum-core/src/rumEvent.types.ts @@ -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 */