From b0bc943946ccd5375561674318679137e2be5dc6 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Tue, 6 Aug 2024 15:54:37 +0200 Subject: [PATCH 1/7] [RUM-5705] Collect Long Animation Frames --- .../core/src/tools/experimentalFeatures.ts | 1 + packages/rum-core/src/boot/startRum.ts | 9 ++- .../src/browser/performanceCollection.ts | 1 + .../src/browser/performanceObservable.ts | 40 +++++++++++++ .../longAnimationFrameCollection.ts | 58 +++++++++++++++++++ .../longTask/longTaskCollection.spec.ts | 3 +- .../src/domain/longTask/longTaskCollection.ts | 3 +- packages/rum-core/src/rawRumEvent.types.ts | 48 +++++++++++++++ 8 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.ts diff --git a/packages/core/src/tools/experimentalFeatures.ts b/packages/core/src/tools/experimentalFeatures.ts index 7ffa745ea5..6d2cf81486 100644 --- a/packages/core/src/tools/experimentalFeatures.ts +++ b/packages/core/src/tools/experimentalFeatures.ts @@ -20,6 +20,7 @@ export enum ExperimentalFeature { REMOTE_CONFIGURATION = 'remote_configuration', UPDATE_VIEW_NAME = 'update_view_name', NULL_INP_TELEMETRY = 'null_inp_telemetry', + LONG_ANIMATION_FRAME = 'long_animation_frame', } const enabledExperimentalFeatures: Set = new Set() diff --git a/packages/rum-core/src/boot/startRum.ts b/packages/rum-core/src/boot/startRum.ts index 7c4a591d4c..828fcf847a 100644 --- a/packages/rum-core/src/boot/startRum.ts +++ b/packages/rum-core/src/boot/startRum.ts @@ -17,6 +17,8 @@ import { addTelemetryDebug, CustomerDataType, drainPreStartTelemetry, + isExperimentalFeatureEnabled, + ExperimentalFeature, } from '@datadog/browser-core' import { createDOMMutationObservable } from '../browser/domMutationObservable' import { startPerformanceCollection } from '../browser/performanceCollection' @@ -47,6 +49,7 @@ import type { CommonContext } from '../domain/contexts/commonContext' import { startDisplayContext } from '../domain/contexts/displayContext' import { startVitalCollection } from '../domain/vital/vitalCollection' import { startCiVisibilityContext } from '../domain/contexts/ciVisibilityContext' +import { startLongAnimationFrameCollection } from '../domain/longAnimationFrame/longAnimationFrameCollection' import type { RecorderApi } from './rumPublicApi' export type StartRum = typeof startRum @@ -165,7 +168,11 @@ export function startRum( const { stop: stopResourceCollection } = startResourceCollection(lifeCycle, configuration, pageStateHistory) cleanupTasks.push(stopResourceCollection) - startLongTaskCollection(lifeCycle, configuration) + if (isExperimentalFeatureEnabled(ExperimentalFeature.LONG_ANIMATION_FRAME)) { + startLongAnimationFrameCollection(lifeCycle, configuration) + } else { + startLongTaskCollection(lifeCycle, configuration) + } const { addError } = startErrorCollection(lifeCycle, configuration, pageStateHistory, featureFlagContexts) diff --git a/packages/rum-core/src/browser/performanceCollection.ts b/packages/rum-core/src/browser/performanceCollection.ts index ecfe878c2c..d8fddc9436 100644 --- a/packages/rum-core/src/browser/performanceCollection.ts +++ b/packages/rum-core/src/browser/performanceCollection.ts @@ -47,6 +47,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/browser/performanceObservable.ts b/packages/rum-core/src/browser/performanceObservable.ts index a374a00912..e2d87342bb 100644 --- a/packages/rum-core/src/browser/performanceObservable.ts +++ b/packages/rum-core/src/browser/performanceObservable.ts @@ -23,6 +23,7 @@ export enum RumPerformanceEntryType { LARGEST_CONTENTFUL_PAINT = 'largest-contentful-paint', LAYOUT_SHIFT = 'layout-shift', LONG_TASK = 'longtask', + LONG_ANIMATION_FRAME = 'long-animation-frame', NAVIGATION = 'navigation', PAINT = 'paint', RESOURCE = 'resource', @@ -117,9 +118,46 @@ 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: RumPerformanceScriptTiming[] + startTime: RelativeTime + styleAndLayoutStart: RelativeTime +} + export type RumPerformanceEntry = | RumPerformanceResourceTiming | RumPerformanceLongTaskTiming + | RumPerformanceLongAnimationFrameTiming | RumPerformancePaintTiming | RumPerformanceNavigationTiming | RumLargestContentfulPaintTiming @@ -134,6 +172,7 @@ export type EntryTypeToReturnType = { [RumPerformanceEntryType.LAYOUT_SHIFT]: RumLayoutShiftTiming [RumPerformanceEntryType.PAINT]: RumPerformancePaintTiming [RumPerformanceEntryType.LONG_TASK]: RumPerformanceLongTaskTiming + [RumPerformanceEntryType.LONG_ANIMATION_FRAME]: RumPerformanceLongAnimationFrameTiming [RumPerformanceEntryType.NAVIGATION]: RumPerformanceNavigationTiming [RumPerformanceEntryType.RESOURCE]: RumPerformanceResourceTiming } @@ -180,6 +219,7 @@ export function createPerformanceObservable( RumPerformanceEntryType.RESOURCE, RumPerformanceEntryType.NAVIGATION, RumPerformanceEntryType.LONG_TASK, + RumPerformanceEntryType.LONG_ANIMATION_FRAME, RumPerformanceEntryType.PAINT, ] if (includes(fallbackSupportedEntryTypes, options.type)) { diff --git a/packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.ts b/packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.ts new file mode 100644 index 0000000000..7322dfbf41 --- /dev/null +++ b/packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.ts @@ -0,0 +1,58 @@ +import { toServerDuration, relativeToClocks, generateUUID } from '@datadog/browser-core' +import type { RawRumLongAnimationFrameEvent } from '../../rawRumEvent.types' +import { RumEventType, RumLongTaskEntryType } from '../../rawRumEvent.types' +import type { LifeCycle } from '../lifeCycle' +import { LifeCycleEventType } from '../lifeCycle' +import { RumPerformanceEntryType } from '../../browser/performanceObservable' +import type { RumConfiguration } from '../configuration' + +export function startLongAnimationFrameCollection(lifeCycle: LifeCycle, configuration: RumConfiguration) { + lifeCycle.subscribe(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, (entries) => { + for (const entry of entries) { + if (entry.entryType !== RumPerformanceEntryType.LONG_ANIMATION_FRAME) { + break + } + if (!configuration.trackLongTasks) { + break + } + + const startClocks = relativeToClocks(entry.startTime) + const rawRumEvent: RawRumLongAnimationFrameEvent = { + date: startClocks.timeStamp, + long_task: { + id: generateUUID(), + entry_type: RumLongTaskEntryType.LONG_ANIMATION_FRAME, + duration: toServerDuration(entry.duration), + blocking_duration: toServerDuration(entry.blockingDuration), + style_and_layout_start: toServerDuration(entry.styleAndLayoutStart), + render_start: toServerDuration(entry.renderStart), + start_time: toServerDuration(entry.startTime), + first_ui_event_timestamp: entry.firstUIEventTimestamp, + name: 'long-animation-frame', + scripts: entry.scripts.map((script) => ({ + duration: toServerDuration(script.duration), + pause_duration: toServerDuration(script.pauseDuration), + source_url: script.sourceURL, + source_function_name: script.sourceFunctionName, + source_char_position: script.sourceCharPosition, + start_time: toServerDuration(script.startTime), + window_attribution: script.windowAttribution, + forced_style_and_layout_duration: toServerDuration(script.forcedStyleAndLayoutDuration), + invoker_type: script.invokerType, + invoker: script.invoker, + execution_start: toServerDuration(script.executionStart), + })), + }, + type: RumEventType.LONG_TASK, + _dd: { + discarded: false, + }, + } + lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + rawRumEvent, + startTime: startClocks.relative, + domainContext: { performanceEntry: entry }, + }) + } + }) +} diff --git a/packages/rum-core/src/domain/longTask/longTaskCollection.spec.ts b/packages/rum-core/src/domain/longTask/longTaskCollection.spec.ts index c831fb3944..f593d27654 100644 --- a/packages/rum-core/src/domain/longTask/longTaskCollection.spec.ts +++ b/packages/rum-core/src/domain/longTask/longTaskCollection.spec.ts @@ -2,7 +2,7 @@ import type { RelativeTime, ServerDuration } from '@datadog/browser-core' import type { RumSessionManagerMock, TestSetupBuilder } from '../../../test' import { createPerformanceEntry, createRumSessionManagerMock, mockPerformanceObserver, setup } from '../../../test' import { RumPerformanceEntryType } from '../../browser/performanceObservable' -import { RumEventType } from '../../rawRumEvent.types' +import { RumEventType, RumLongTaskEntryType } from '../../rawRumEvent.types' import { LifeCycleEventType } from '../lifeCycle' import { startLongTaskCollection } from './longTaskCollection' @@ -65,6 +65,7 @@ describe('long task collection', () => { date: jasmine.any(Number), long_task: { id: jasmine.any(String), + entry_type: RumLongTaskEntryType.LONG_TASK, duration: (100 * 1e6) as ServerDuration, }, type: RumEventType.LONG_TASK, diff --git a/packages/rum-core/src/domain/longTask/longTaskCollection.ts b/packages/rum-core/src/domain/longTask/longTaskCollection.ts index 1d38e0af66..705103d2f2 100644 --- a/packages/rum-core/src/domain/longTask/longTaskCollection.ts +++ b/packages/rum-core/src/domain/longTask/longTaskCollection.ts @@ -1,6 +1,6 @@ import { toServerDuration, relativeToClocks, generateUUID } from '@datadog/browser-core' import type { RawRumLongTaskEvent } from '../../rawRumEvent.types' -import { RumEventType } from '../../rawRumEvent.types' +import { RumEventType, RumLongTaskEntryType } from '../../rawRumEvent.types' import type { LifeCycle } from '../lifeCycle' import { LifeCycleEventType } from '../lifeCycle' import { RumPerformanceEntryType } from '../../browser/performanceObservable' @@ -20,6 +20,7 @@ export function startLongTaskCollection(lifeCycle: LifeCycle, configuration: Rum date: startClocks.timeStamp, long_task: { id: generateUUID(), + entry_type: RumLongTaskEntryType.LONG_TASK, duration: toServerDuration(entry.duration), }, type: RumEventType.LONG_TASK, diff --git a/packages/rum-core/src/rawRumEvent.types.ts b/packages/rum-core/src/rawRumEvent.types.ts index 0744cd0a04..74ff172e8d 100644 --- a/packages/rum-core/src/rawRumEvent.types.ts +++ b/packages/rum-core/src/rawRumEvent.types.ts @@ -10,6 +10,7 @@ import type { DefaultPrivacyLevel, Connectivity, Csp, + RelativeTime, } from '@datadog/browser-core' import type { PageState } from './domain/contexts/pageStateHistory' @@ -22,6 +23,11 @@ export const enum RumEventType { VITAL = 'vital', } +export const enum RumLongTaskEntryType { + LONG_TASK = 'long-task', + LONG_ANIMATION_FRAME = 'long-animation-frame', +} + export interface RawRumResourceEvent { date: TimeStamp type: RumEventType.RESOURCE @@ -170,7 +176,48 @@ export interface RawRumLongTaskEvent { type: RumEventType.LONG_TASK long_task: { id: string + entry_type: RumLongTaskEntryType.LONG_TASK + duration: ServerDuration + } + _dd: { + discarded: boolean + } +} + +export type InvokerType = + | 'user-callback' + | 'event-listener' + | 'resolve-promise' + | 'reject-promise' + | 'classic-script' + | 'module-script' + +export interface RawRumLongAnimationFrameEvent { + date: TimeStamp + type: RumEventType.LONG_TASK // LoAF are ingested as Long Task + long_task: { + id: string + entry_type: RumLongTaskEntryType.LONG_ANIMATION_FRAME duration: ServerDuration + blocking_duration: ServerDuration + first_ui_event_timestamp: RelativeTime + name: 'long-animation-frame' + render_start: ServerDuration + start_time: ServerDuration + style_and_layout_start: ServerDuration + scripts: Array<{ + duration: ServerDuration + pause_duration: ServerDuration + source_url: string + source_function_name: string + source_char_position: number + start_time: ServerDuration + window_attribution: string + forced_style_and_layout_duration: ServerDuration + invoker_type: InvokerType + invoker: string + execution_start: ServerDuration + }> } _dd: { discarded: boolean @@ -250,6 +297,7 @@ export type RawRumEvent = | RawRumResourceEvent | RawRumViewEvent | RawRumLongTaskEvent + | RawRumLongAnimationFrameEvent | RawRumActionEvent | RawRumVitalEvent From 62433712747450be7454363a8cf238b04bc099be Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Tue, 6 Aug 2024 16:17:50 +0200 Subject: [PATCH 2/7] fix fixture --- packages/rum-core/test/fixtures.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/rum-core/test/fixtures.ts b/packages/rum-core/test/fixtures.ts index 6a4246c8ea..ee36694121 100644 --- a/packages/rum-core/test/fixtures.ts +++ b/packages/rum-core/test/fixtures.ts @@ -10,7 +10,7 @@ import { } from '@datadog/browser-core' import { RumPerformanceEntryType, type EntryTypeToReturnType } from '../src/browser/performanceObservable' import type { RawRumEvent } from '../src/rawRumEvent.types' -import { VitalType, ActionType, RumEventType, ViewLoadingType } from '../src/rawRumEvent.types' +import { VitalType, ActionType, RumEventType, ViewLoadingType, RumLongTaskEntryType } from '../src/rawRumEvent.types' export function createRawRumEvent(type: RumEventType, overrides?: Context): RawRumEvent { switch (type) { @@ -51,6 +51,7 @@ export function createRawRumEvent(type: RumEventType, overrides?: Context): RawR long_task: { id: generateUUID(), duration: 0 as ServerDuration, + entry_type: RumLongTaskEntryType.LONG_TASK, }, _dd: { discarded: false, From 4d9f22a9bd8c3915c6a34b663bf0c3094a23ba1f Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Wed, 7 Aug 2024 09:17:51 +0200 Subject: [PATCH 3/7] fix Loaf types --- .../longAnimationFrameCollection.ts | 18 ++++++++---------- packages/rum-core/src/rawRumEvent.types.ts | 16 +++++++--------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.ts b/packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.ts index 7322dfbf41..4ba6436b3d 100644 --- a/packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.ts +++ b/packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.ts @@ -24,23 +24,21 @@ export function startLongAnimationFrameCollection(lifeCycle: LifeCycle, configur entry_type: RumLongTaskEntryType.LONG_ANIMATION_FRAME, duration: toServerDuration(entry.duration), blocking_duration: toServerDuration(entry.blockingDuration), - style_and_layout_start: toServerDuration(entry.styleAndLayoutStart), - render_start: toServerDuration(entry.renderStart), - start_time: toServerDuration(entry.startTime), - first_ui_event_timestamp: entry.firstUIEventTimestamp, - name: 'long-animation-frame', + first_ui_event_timestamp: relativeToClocks(entry.firstUIEventTimestamp).relative, + render_start: relativeToClocks(entry.renderStart).relative, + style_and_layout_start: relativeToClocks(entry.styleAndLayoutStart).relative, scripts: entry.scripts.map((script) => ({ duration: toServerDuration(script.duration), pause_duration: toServerDuration(script.pauseDuration), + forced_style_and_layout_duration: toServerDuration(script.forcedStyleAndLayoutDuration), + start_time: relativeToClocks(script.startTime).relative, + execution_start: relativeToClocks(script.executionStart).relative, source_url: script.sourceURL, source_function_name: script.sourceFunctionName, source_char_position: script.sourceCharPosition, - start_time: toServerDuration(script.startTime), - window_attribution: script.windowAttribution, - forced_style_and_layout_duration: toServerDuration(script.forcedStyleAndLayoutDuration), - invoker_type: script.invokerType, invoker: script.invoker, - execution_start: toServerDuration(script.executionStart), + invoker_type: script.invokerType, + window_attribution: script.windowAttribution, })), }, type: RumEventType.LONG_TASK, diff --git a/packages/rum-core/src/rawRumEvent.types.ts b/packages/rum-core/src/rawRumEvent.types.ts index 74ff172e8d..dbc7e52fdc 100644 --- a/packages/rum-core/src/rawRumEvent.types.ts +++ b/packages/rum-core/src/rawRumEvent.types.ts @@ -201,22 +201,20 @@ export interface RawRumLongAnimationFrameEvent { duration: ServerDuration blocking_duration: ServerDuration first_ui_event_timestamp: RelativeTime - name: 'long-animation-frame' - render_start: ServerDuration - start_time: ServerDuration - style_and_layout_start: ServerDuration + render_start: RelativeTime + style_and_layout_start: RelativeTime scripts: Array<{ duration: ServerDuration pause_duration: ServerDuration + forced_style_and_layout_duration: ServerDuration + start_time: RelativeTime + execution_start: RelativeTime source_url: string source_function_name: string source_char_position: number - start_time: ServerDuration - window_attribution: string - forced_style_and_layout_duration: ServerDuration - invoker_type: InvokerType invoker: string - execution_start: ServerDuration + invoker_type: InvokerType + window_attribution: string }> } _dd: { From 2dc41b4e046edcdb2e594a7f9c44bf06c99e02a1 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Wed, 7 Aug 2024 13:57:28 +0200 Subject: [PATCH 4/7] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20use=20a=20performance?= =?UTF-8?q?=20observable=20instead=20of=20the=20lifecycle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/rum-core/src/boot/startRum.ts | 5 ++++- .../src/browser/performanceCollection.ts | 1 - .../src/browser/performanceObservable.ts | 1 - .../longAnimationFrameCollection.ts | 20 ++++++++++--------- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/rum-core/src/boot/startRum.ts b/packages/rum-core/src/boot/startRum.ts index 828fcf847a..e68dd0cd32 100644 --- a/packages/rum-core/src/boot/startRum.ts +++ b/packages/rum-core/src/boot/startRum.ts @@ -169,7 +169,10 @@ export function startRum( cleanupTasks.push(stopResourceCollection) if (isExperimentalFeatureEnabled(ExperimentalFeature.LONG_ANIMATION_FRAME)) { - startLongAnimationFrameCollection(lifeCycle, configuration) + if (configuration.trackLongTasks) { + const { stop: stopLongAnimationFrameCollection } = startLongAnimationFrameCollection(lifeCycle, configuration) + cleanupTasks.push(stopLongAnimationFrameCollection) + } } else { startLongTaskCollection(lifeCycle, configuration) } diff --git a/packages/rum-core/src/browser/performanceCollection.ts b/packages/rum-core/src/browser/performanceCollection.ts index d8fddc9436..ecfe878c2c 100644 --- a/packages/rum-core/src/browser/performanceCollection.ts +++ b/packages/rum-core/src/browser/performanceCollection.ts @@ -47,7 +47,6 @@ 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/browser/performanceObservable.ts b/packages/rum-core/src/browser/performanceObservable.ts index e2d87342bb..6c73c52101 100644 --- a/packages/rum-core/src/browser/performanceObservable.ts +++ b/packages/rum-core/src/browser/performanceObservable.ts @@ -219,7 +219,6 @@ export function createPerformanceObservable( RumPerformanceEntryType.RESOURCE, RumPerformanceEntryType.NAVIGATION, RumPerformanceEntryType.LONG_TASK, - RumPerformanceEntryType.LONG_ANIMATION_FRAME, RumPerformanceEntryType.PAINT, ] if (includes(fallbackSupportedEntryTypes, options.type)) { diff --git a/packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.ts b/packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.ts index 4ba6436b3d..f16ce5220b 100644 --- a/packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.ts +++ b/packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.ts @@ -3,20 +3,17 @@ import type { RawRumLongAnimationFrameEvent } from '../../rawRumEvent.types' import { RumEventType, RumLongTaskEntryType } from '../../rawRumEvent.types' import type { LifeCycle } from '../lifeCycle' import { LifeCycleEventType } from '../lifeCycle' -import { RumPerformanceEntryType } from '../../browser/performanceObservable' +import { createPerformanceObservable, RumPerformanceEntryType } from '../../browser/performanceObservable' import type { RumConfiguration } from '../configuration' export function startLongAnimationFrameCollection(lifeCycle: LifeCycle, configuration: RumConfiguration) { - lifeCycle.subscribe(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, (entries) => { + const performanceResourceSubscription = createPerformanceObservable(configuration, { + type: RumPerformanceEntryType.LONG_ANIMATION_FRAME, + buffered: true, + }).subscribe((entries) => { for (const entry of entries) { - if (entry.entryType !== RumPerformanceEntryType.LONG_ANIMATION_FRAME) { - break - } - if (!configuration.trackLongTasks) { - break - } - const startClocks = relativeToClocks(entry.startTime) + const rawRumEvent: RawRumLongAnimationFrameEvent = { date: startClocks.timeStamp, long_task: { @@ -46,6 +43,7 @@ export function startLongAnimationFrameCollection(lifeCycle: LifeCycle, configur discarded: false, }, } + lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { rawRumEvent, startTime: startClocks.relative, @@ -53,4 +51,8 @@ export function startLongAnimationFrameCollection(lifeCycle: LifeCycle, configur }) } }) + + return { + stop: () => performanceResourceSubscription.unsubscribe(), + } } From aa095bda5a7b556baf3dabd7ee1cfe7945f58247 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Thu, 8 Aug 2024 12:51:34 +0200 Subject: [PATCH 5/7] =?UTF-8?q?=E2=9C=85=20add=20untit=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../longAnimationFrameCollection.spec.ts | 148 ++++++++++++++++++ packages/rum-core/test/fixtures.ts | 34 ++++ 2 files changed, 182 insertions(+) create mode 100644 packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.spec.ts diff --git a/packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.spec.ts b/packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.spec.ts new file mode 100644 index 0000000000..df2d8928b9 --- /dev/null +++ b/packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.spec.ts @@ -0,0 +1,148 @@ +import { type Context, type TimeStamp, type RelativeTime, type ServerDuration, combine } from '@datadog/browser-core' +import { registerCleanupTask } from '@datadog/browser-core/test' +import { createPerformanceEntry, mockPerformanceObserver, validateRumFormat } from '../../../test' +import { RumPerformanceEntryType } from '../../browser/performanceObservable' +import type { RawRumEvent, RumContext } from '../../rawRumEvent.types' +import { RumEventType, RumLongTaskEntryType } from '../../rawRumEvent.types' +import type { RawRumEventCollectedData } from '../lifeCycle' +import { LifeCycle, LifeCycleEventType } from '../lifeCycle' +import type { RumConfiguration } from '../configuration' +import { startLongAnimationFrameCollection } from './longAnimationFrameCollection' + +describe('long animation frames collection', () => { + it('should create raw rum event from long animation frame performance entry', () => { + const { notifyPerformanceEntries, rawRumEvents } = setupLongAnimationFrameCollection() + const PerformanceLongAnimationFrameTiming = createPerformanceEntry(RumPerformanceEntryType.LONG_ANIMATION_FRAME) + + notifyPerformanceEntries([PerformanceLongAnimationFrameTiming]) + + expect(rawRumEvents[0].startTime).toBe(1234 as RelativeTime) + expect(rawRumEvents[0].rawRumEvent).toEqual({ + date: jasmine.any(Number), + long_task: { + id: jasmine.any(String), + entry_type: RumLongTaskEntryType.LONG_ANIMATION_FRAME, + duration: (82 * 1e6) as ServerDuration, + blocking_duration: 0 as ServerDuration, + first_ui_event_timestamp: 0 as RelativeTime, + render_start: 1421.5 as RelativeTime, + style_and_layout_start: 1428 as RelativeTime, + scripts: [ + { + duration: (6 * 1e6) as ServerDuration, + pause_duration: 0 as ServerDuration, + forced_style_and_layout_duration: 0 as ServerDuration, + start_time: 1348 as RelativeTime, + execution_start: 1348.7 as RelativeTime, + source_url: 'http://example.com/script.js', + source_function_name: '', + source_char_position: 9876, + invoker: 'http://example.com/script.js', + invoker_type: 'classic-script', + window_attribution: 'self', + }, + ], + }, + type: RumEventType.LONG_TASK, + _dd: { + discarded: false, + }, + }) + expect(rawRumEvents[0].domainContext).toEqual({ + performanceEntry: { + name: 'long-animation-frame', + duration: 82, + entryType: 'long-animation-frame', + startTime: 1234, + renderStart: 1421.5, + styleAndLayoutStart: 1428, + firstUIEventTimestamp: 0, + blockingDuration: 0, + scripts: [ + { + name: 'script', + entryType: 'script', + startTime: 1348, + duration: 6, + invoker: 'http://example.com/script.js', + invokerType: 'classic-script', + windowAttribution: 'self', + executionStart: 1348.7, + forcedStyleAndLayoutDuration: 0, + pauseDuration: 0, + sourceURL: 'http://example.com/script.js', + sourceFunctionName: '', + sourceCharPosition: 9876, + }, + ], + toJSON: jasmine.any(Function), + }, + }) + }) +}) + +function setupLongAnimationFrameCollection() { + const lifeCycle = new LifeCycle() + const configuration = {} as RumConfiguration + + const notifyPerformanceEntries = mockPerformanceObserver().notifyPerformanceEntries + const rawRumEvents = collectAndValidateRawRumEvents(lifeCycle) + const { stop: stopLongAnimationFrameCollection } = startLongAnimationFrameCollection(lifeCycle, configuration) + + registerCleanupTask(() => { + stopLongAnimationFrameCollection() + }) + + return { + notifyPerformanceEntries, + rawRumEvents, + } +} + +// TODO: replace with packages/rum-core/test/eventFormatValidation.ts from this PR https://github.com/DataDog/browser-sdk/pull/2913 +function collectAndValidateRawRumEvents(lifeCycle: LifeCycle) { + const rawRumEvents: Array> = [] + const subscription = lifeCycle.subscribe(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, (data) => { + rawRumEvents.push(data) + validateRumEventFormat(data.rawRumEvent) + }) + registerCleanupTask(() => { + subscription.unsubscribe() + }) + + return rawRumEvents +} + +function validateRumEventFormat(rawRumEvent: RawRumEvent) { + const fakeId = '00000000-aaaa-0000-aaaa-000000000000' + const fakeContext: RumContext = { + _dd: { + format_version: 2, + drift: 0, + configuration: { + session_sample_rate: 40, + session_replay_sample_rate: 60, + }, + }, + application: { + id: fakeId, + }, + date: 0 as TimeStamp, + source: 'browser', + session: { + id: fakeId, + type: 'user', + }, + view: { + id: fakeId, + referrer: '', + url: 'fake url', + }, + connectivity: { + status: 'connected', + interfaces: ['wifi'], + effective_type: '4g', + }, + } + validateRumFormat(combine(fakeContext as RumContext & Context, rawRumEvent)) +} diff --git a/packages/rum-core/test/fixtures.ts b/packages/rum-core/test/fixtures.ts index ee36694121..2b74b77f3d 100644 --- a/packages/rum-core/test/fixtures.ts +++ b/packages/rum-core/test/fixtures.ts @@ -205,6 +205,40 @@ export function createPerformanceEntry( return { ...entry, toJSON: () => entry } } + case RumPerformanceEntryType.LONG_ANIMATION_FRAME: { + const entry = assign( + { + name: 'long-animation-frame', + entryType: RumPerformanceEntryType.LONG_ANIMATION_FRAME, + startTime: 1234 as RelativeTime, + duration: 82 as Duration, + renderStart: 1421.5 as RelativeTime, + styleAndLayoutStart: 1428 as RelativeTime, + firstUIEventTimestamp: 0 as RelativeTime, + blockingDuration: 0 as Duration, + scripts: [ + { + name: 'script', + entryType: 'script', + startTime: 1348 as RelativeTime, + duration: 6 as Duration, + invoker: 'http://example.com/script.js', + invokerType: 'classic-script', + windowAttribution: 'self', + executionStart: 1348.7 as RelativeTime, + forcedStyleAndLayoutDuration: 0 as Duration, + pauseDuration: 0 as Duration, + sourceURL: 'http://example.com/script.js', + sourceFunctionName: '', + sourceCharPosition: 9876, + }, + ], + }, + overrides + ) as EntryTypeToReturnType[T] + + return { ...entry, toJSON: () => entry } + } case RumPerformanceEntryType.RESOURCE: { const entry = assign( { From 8735d4b3614e94bf85cf31ac472dff31945c0f66 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Fri, 9 Aug 2024 15:48:15 +0200 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=91=8C=20use=20collectAndValidateRawR?= =?UTF-8?q?umEvents=20after=20merging=20main?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../longAnimationFrameCollection.spec.ts | 56 +------------------ 1 file changed, 3 insertions(+), 53 deletions(-) diff --git a/packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.spec.ts b/packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.spec.ts index df2d8928b9..7fd3b62cd5 100644 --- a/packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.spec.ts +++ b/packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.spec.ts @@ -1,11 +1,9 @@ -import { type Context, type TimeStamp, type RelativeTime, type ServerDuration, combine } from '@datadog/browser-core' +import { type RelativeTime, type ServerDuration } from '@datadog/browser-core' import { registerCleanupTask } from '@datadog/browser-core/test' -import { createPerformanceEntry, mockPerformanceObserver, validateRumFormat } from '../../../test' +import { collectAndValidateRawRumEvents, createPerformanceEntry, mockPerformanceObserver } from '../../../test' import { RumPerformanceEntryType } from '../../browser/performanceObservable' -import type { RawRumEvent, RumContext } from '../../rawRumEvent.types' import { RumEventType, RumLongTaskEntryType } from '../../rawRumEvent.types' -import type { RawRumEventCollectedData } from '../lifeCycle' -import { LifeCycle, LifeCycleEventType } from '../lifeCycle' +import { LifeCycle } from '../lifeCycle' import type { RumConfiguration } from '../configuration' import { startLongAnimationFrameCollection } from './longAnimationFrameCollection' @@ -98,51 +96,3 @@ function setupLongAnimationFrameCollection() { rawRumEvents, } } - -// TODO: replace with packages/rum-core/test/eventFormatValidation.ts from this PR https://github.com/DataDog/browser-sdk/pull/2913 -function collectAndValidateRawRumEvents(lifeCycle: LifeCycle) { - const rawRumEvents: Array> = [] - const subscription = lifeCycle.subscribe(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, (data) => { - rawRumEvents.push(data) - validateRumEventFormat(data.rawRumEvent) - }) - registerCleanupTask(() => { - subscription.unsubscribe() - }) - - return rawRumEvents -} - -function validateRumEventFormat(rawRumEvent: RawRumEvent) { - const fakeId = '00000000-aaaa-0000-aaaa-000000000000' - const fakeContext: RumContext = { - _dd: { - format_version: 2, - drift: 0, - configuration: { - session_sample_rate: 40, - session_replay_sample_rate: 60, - }, - }, - application: { - id: fakeId, - }, - date: 0 as TimeStamp, - source: 'browser', - session: { - id: fakeId, - type: 'user', - }, - view: { - id: fakeId, - referrer: '', - url: 'fake url', - }, - connectivity: { - status: 'connected', - interfaces: ['wifi'], - effective_type: '4g', - }, - } - validateRumFormat(combine(fakeContext as RumContext & Context, rawRumEvent)) -} From afe5c7ed9f8a41f09e9b3b46442d630bdef857c4 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Mon, 12 Aug 2024 08:08:20 +0200 Subject: [PATCH 7/7] sync rum-events-format --- .../domain/telemetry/telemetryEvent.types.ts | 13 ++- packages/rum-core/src/rumEvent.types.ts | 102 +++++++++++++++++- rum-events-format | 2 +- 3 files changed, 110 insertions(+), 7 deletions(-) diff --git a/packages/core/src/domain/telemetry/telemetryEvent.types.ts b/packages/core/src/domain/telemetry/telemetryEvent.types.ts index 59a93bfc07..c986d4779d 100644 --- a/packages/core/src/domain/telemetry/telemetryEvent.types.ts +++ b/packages/core/src/domain/telemetry/telemetryEvent.types.ts @@ -133,6 +133,10 @@ export type TelemetryConfigurationEvent = CommonTelemetryProperties & { * Whether the session replay start is handled manually */ start_session_replay_recording_manually?: boolean + /** + * Whether Session Replay should automatically start a recording when enabled + */ + start_recording_immediately?: boolean /** * Whether a proxy is used */ @@ -476,6 +480,13 @@ export type TelemetryBrowserFeaturesUsage = feature: 'start-duration-vital' [k: string]: unknown } + | { + /** + * stopDurationVital API + */ + feature: 'stop-duration-vital' + [k: string]: unknown + } | { /** * addDurationVital API @@ -513,7 +524,7 @@ export interface CommonTelemetryProperties { /** * The source of this event */ - readonly source: 'android' | 'ios' | 'browser' | 'flutter' | 'react-native' | 'unity' + readonly source: 'android' | 'ios' | 'browser' | 'flutter' | 'react-native' | 'unity' | 'kotlin-multiplatform' /** * The version of the SDK generating the telemetry event */ diff --git a/packages/rum-core/src/rumEvent.types.ts b/packages/rum-core/src/rumEvent.types.ts index 64bfdaf2ac..c9750326a2 100644 --- a/packages/rum-core/src/rumEvent.types.ts +++ b/packages/rum-core/src/rumEvent.types.ts @@ -223,7 +223,7 @@ export type RumErrorEvent = CommonProperties & /** * The specific category of the error. It provides a high-level grouping for different types of errors. */ - readonly category?: 'ANR' | 'App Hang' | 'Exception' + readonly category?: 'ANR' | 'App Hang' | 'Exception' | 'Watchdog Termination' | 'Memory Warning' /** * Whether the error has been handled manually in the source code or not */ @@ -444,17 +444,93 @@ export type RumLongTaskEvent = CommonProperties & */ readonly long_task: { /** - * UUID of the long task + * UUID of the long task or long animation frame */ readonly id?: string /** - * Duration in ns of the long task + * Type of the event: long task or long animation frame + */ + readonly entry_type?: 'long-task' | 'long-animation-frame' + /** + * Duration in ns of the long task or long animation frame */ readonly duration: number + /** + * Duration in ns for which the animation frame was being blocked + */ + readonly blocking_duration?: number + /** + * Start time of the rendering cycle, which includes requestAnimationFrame callbacks, style and layout calculation, resize observer and intersection observer callbacks + */ + readonly render_start?: number + /** + * Start time of the time period spent in style and layout calculations + */ + readonly style_and_layout_start?: number + /** + * Start time of of the first UI event (mouse/keyboard and so on) to be handled during the course of this frame + */ + readonly first_ui_event_timestamp?: number /** * Whether this long task is considered a frozen frame */ readonly is_frozen_frame?: boolean + /** + * A list of long scripts that were executed over the course of the long frame + */ + readonly scripts?: { + /** + * Duration in ns between startTime and when the subsequent microtask queue has finished processing + */ + readonly duration?: number + /** + * Duration in ns of the total time spent in 'pausing' synchronous operations (alert, synchronous XHR) + */ + readonly pause_duration?: number + /** + * Duration in ns of the the total time spent processing forced layout and style inside this function + */ + readonly forced_style_and_layout_duration?: number + /** + * Time the entry function was invoked + */ + readonly start_time?: number + /** + * Time after compilation + */ + readonly execution_start?: number + /** + * The script resource name where available (or empty if not found) + */ + readonly source_url?: string + /** + * The script function name where available (or empty if not found) + */ + readonly source_function_name?: string + /** + * The script character position where available (or -1 if not found) + */ + readonly source_char_position?: number + /** + * Information about the invoker of the script + */ + readonly invoker?: string + /** + * Type of the invoker of the script + */ + readonly invoker_type?: + | 'user-callback' + | 'event-listener' + | 'resolve-promise' + | 'reject-promise' + | 'classic-script' + | 'module-script' + /** + * The container (the top-level document, or an