From 65c15df95558aff29b8ee228939948c41589a37e Mon Sep 17 00:00:00 2001 From: Nazim Saouli Date: Tue, 2 Jul 2024 16:10:15 +0200 Subject: [PATCH] poc merging long tasks and long animation frames --- .../src/browser/performanceCollection.ts | 39 +++++ .../src/domain/longTask/longTaskCollection.ts | 134 +++++++++++++++--- 2 files changed, 157 insertions(+), 16 deletions(-) diff --git a/packages/rum-core/src/browser/performanceCollection.ts b/packages/rum-core/src/browser/performanceCollection.ts index 0c97e19878..53af1e9093 100644 --- a/packages/rum-core/src/browser/performanceCollection.ts +++ b/packages/rum-core/src/browser/performanceCollection.ts @@ -44,6 +44,7 @@ export enum RumPerformanceEntryType { NAVIGATION = 'navigation', PAINT = 'paint', RESOURCE = 'resource', + LONG_ANIMATION_FRAME = 'long-animation-frame', } export interface RumPerformanceResourceTiming { @@ -135,6 +136,42 @@ export interface RumLayoutShiftTiming { }> } +// Documentation https://developer.chrome.com/docs/web-platform/long-animation-frames#better-attribution +export type RumPerformanceScriptTiming = { + duration: Duration + entryType: 'script' + executionStart: RelativeTime + forcedStyleAndLayoutDuration: Duration + invoker: string // e.g. "https://static.datadoghq.com/static/c/93085/chunk-bc4db53278fd4c77a637.min.js" + invokerType: + | 'user-callback' + | 'event-listener' + | 'resolve-promise' + | 'reject-promise' + | 'classic-script' + | 'module-script' + name: 'script' + pauseDuration: Duration + sourceCharPosition: number + sourceFunctionName: string + sourceURL: string + startTime: RelativeTime + window: Window + windowAttribution: string +} + +export interface RumPerformanceLongAnimationFrameTiming { + blockingDuration: Duration + duration: Duration + entryType: RumPerformanceEntryType.LONG_ANIMATION_FRAME + firstUIEventTimestamp: RelativeTime + name: 'long-animation-frame' + renderStart: RelativeTime + scripts: PerformanceTiming[] + startTime: RelativeTime + styleAndLayoutStart: RelativeTime +} + export type RumPerformanceEntry = | RumPerformanceResourceTiming | RumPerformanceLongTaskTiming @@ -144,6 +181,7 @@ export type RumPerformanceEntry = | RumFirstInputTiming | RumPerformanceEventTiming | RumLayoutShiftTiming + | RumPerformanceLongAnimationFrameTiming function supportPerformanceObject() { return window.performance !== undefined && 'getEntries' in performance @@ -185,6 +223,7 @@ export function startPerformanceCollection(lifeCycle: LifeCycle, configuration: RumPerformanceEntryType.FIRST_INPUT, RumPerformanceEntryType.LAYOUT_SHIFT, RumPerformanceEntryType.EVENT, + RumPerformanceEntryType.LONG_ANIMATION_FRAME, ] try { diff --git a/packages/rum-core/src/domain/longTask/longTaskCollection.ts b/packages/rum-core/src/domain/longTask/longTaskCollection.ts index d4c281c744..8a82c998d9 100644 --- a/packages/rum-core/src/domain/longTask/longTaskCollection.ts +++ b/packages/rum-core/src/domain/longTask/longTaskCollection.ts @@ -4,34 +4,136 @@ import { RumEventType } from '../../rawRumEvent.types' import type { LifeCycle } from '../lifeCycle' import { LifeCycleEventType } from '../lifeCycle' import { RumPerformanceEntryType } from '../../browser/performanceCollection' +import type { RumPerformanceScriptTiming } from '../../browser/performanceCollection' import type { RumConfiguration } from '../configuration' export function startLongTaskCollection(lifeCycle: LifeCycle, configuration: RumConfiguration) { lifeCycle.subscribe(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, (entries) => { for (const entry of entries) { - if (entry.entryType !== RumPerformanceEntryType.LONG_TASK) { + if ( + // entry.entryType !== RumPerformanceEntryType.LONG_TASK && + entry.entryType !== RumPerformanceEntryType.LONG_ANIMATION_FRAME + ) { break } if (!configuration.trackLongTasks) { break } const startClocks = relativeToClocks(entry.startTime) - const rawRumEvent: RawRumLongTaskEvent = { - date: startClocks.timeStamp, - long_task: { - id: generateUUID(), - duration: toServerDuration(entry.duration), - }, - type: RumEventType.LONG_TASK, - _dd: { - discarded: false, - }, + let longTaskData = { + id: generateUUID(), + duration: toServerDuration(entry.duration), + } + + if (entry.entryType === RumPerformanceEntryType.LONG_ANIMATION_FRAME) { + const { blockingDuration, firstUIEventTimestamp, renderStart, startTime, styleAndLayoutStart } = entry + + const enrichedScriptsPromises = entry.scripts.map((script) => { + const { + name, + duration, + entryType, + executionStart, + forcedStyleAndLayoutDuration, + invoker, + invokerType, + pauseDuration, + sourceCharPosition, + sourceFunctionName, + sourceURL, + startTime, + windowAttribution, + } = script.toJSON() as RumPerformanceScriptTiming + + return fetch(sourceURL, { cache: 'force-cache' }) + .then((response) => response.text()) + .then((sourceContent) => { + let totalCharCount = 0 + let currentLine = 1 + let currentCol = 1 + + for (let i = 0; i < sourceContent.length; i++) { + if (sourceContent[i] === '\n') { + currentLine++ + currentCol = 1 + } else { + currentCol++ + } + totalCharCount++ + + if (totalCharCount === sourceCharPosition) { + break + } + } + + return { + name, + duration: toServerDuration(duration), + entryType, + executionStart, + forcedStyleAndLayoutDuration: toServerDuration(forcedStyleAndLayoutDuration), + invoker, + invokerType, + pauseDuration: toServerDuration(pauseDuration), + sourceCharPosition, + sourceFunctionName, + sourceURL, + sourceLine: currentLine, + sourceCol: currentCol, + startTime, + windowAttribution, + } + }) + }) + + // longTaskData = Object.assign(longTaskData, { + // blockingDuration: toServerDuration(blockingDuration), + // firstUIEventTimestamp, + // renderStart, + // startTime, + // styleAndLayoutStart, + // scripts: enrichedScripts, + // }) + Promise.all(enrichedScriptsPromises) + .then((enrichedScripts) => { + longTaskData = Object.assign(longTaskData, { + blockingDuration: toServerDuration(blockingDuration), + firstUIEventTimestamp, + renderStart, + startTime, + styleAndLayoutStart, + scripts: enrichedScripts, + }) + const rawRumEvent: RawRumLongTaskEvent = { + date: startClocks.timeStamp, + long_task: longTaskData, + type: RumEventType.LONG_TASK, + _dd: { + discarded: false, + }, + } + lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + rawRumEvent, + startTime: startClocks.relative, + domainContext: { performanceEntry: entry }, + }) + }) + .catch(() => {}) + } else { + const rawRumEvent: RawRumLongTaskEvent = { + date: startClocks.timeStamp, + long_task: longTaskData, + type: RumEventType.LONG_TASK, + _dd: { + discarded: false, + }, + } + lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + rawRumEvent, + startTime: startClocks.relative, + domainContext: { performanceEntry: entry }, + }) } - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { - rawRumEvent, - startTime: startClocks.relative, - domainContext: { performanceEntry: entry }, - }) } }) }