Skip to content

Commit

Permalink
[RUM-5705] Collect Long Animation Frames (#2924)
Browse files Browse the repository at this point in the history
  • Loading branch information
thomas-lebeau authored Aug 13, 2024
1 parent 2843c96 commit c0de7ea
Show file tree
Hide file tree
Showing 12 changed files with 403 additions and 11 deletions.
13 changes: 12 additions & 1 deletion packages/core/src/domain/telemetry/telemetryEvent.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
*/
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/tools/experimentalFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ExperimentalFeature> = new Set()
Expand Down
12 changes: 11 additions & 1 deletion packages/rum-core/src/boot/startRum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
addTelemetryDebug,
CustomerDataType,
drainPreStartTelemetry,
isExperimentalFeatureEnabled,
ExperimentalFeature,
} from '@datadog/browser-core'
import { createDOMMutationObservable } from '../browser/domMutationObservable'
import { startPerformanceCollection } from '../browser/performanceCollection'
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -165,7 +168,14 @@ export function startRum(
const { stop: stopResourceCollection } = startResourceCollection(lifeCycle, configuration, pageStateHistory)
cleanupTasks.push(stopResourceCollection)

startLongTaskCollection(lifeCycle, configuration)
if (isExperimentalFeatureEnabled(ExperimentalFeature.LONG_ANIMATION_FRAME)) {
if (configuration.trackLongTasks) {
const { stop: stopLongAnimationFrameCollection } = startLongAnimationFrameCollection(lifeCycle, configuration)
cleanupTasks.push(stopLongAnimationFrameCollection)
}
} else {
startLongTaskCollection(lifeCycle, configuration)
}

const { addError } = startErrorCollection(lifeCycle, configuration, pageStateHistory, featureFlagContexts)

Expand Down
39 changes: 39 additions & 0 deletions packages/rum-core/src/browser/performanceObservable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { type RelativeTime, type ServerDuration } from '@datadog/browser-core'
import { registerCleanupTask } from '@datadog/browser-core/test'
import { collectAndValidateRawRumEvents, createPerformanceEntry, mockPerformanceObserver } from '../../../test'
import { RumPerformanceEntryType } from '../../browser/performanceObservable'
import { RumEventType, RumLongTaskEntryType } from '../../rawRumEvent.types'
import { LifeCycle } 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,
}
}
Original file line number Diff line number Diff line change
@@ -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 { createPerformanceObservable, RumPerformanceEntryType } from '../../browser/performanceObservable'
import type { RumConfiguration } from '../configuration'

export function startLongAnimationFrameCollection(lifeCycle: LifeCycle, configuration: RumConfiguration) {
const performanceResourceSubscription = createPerformanceObservable(configuration, {
type: RumPerformanceEntryType.LONG_ANIMATION_FRAME,
buffered: true,
}).subscribe((entries) => {
for (const entry of entries) {
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),
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,
invoker: script.invoker,
invoker_type: script.invokerType,
window_attribution: script.windowAttribution,
})),
},
type: RumEventType.LONG_TASK,
_dd: {
discarded: false,
},
}

lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, {
rawRumEvent,
startTime: startClocks.relative,
domainContext: { performanceEntry: entry },
})
}
})

return {
stop: () => performanceResourceSubscription.unsubscribe(),
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { RelativeTime, ServerDuration } from '@datadog/browser-core'
import { collectAndValidateRawRumEvents, createPerformanceEntry, mockPerformanceObserver } from '../../../test'
import { RumPerformanceEntryType } from '../../browser/performanceObservable'
import type { RawRumEvent } from '../../rawRumEvent.types'
import { RumEventType } from '../../rawRumEvent.types'
import { RumEventType, RumLongTaskEntryType } from '../../rawRumEvent.types'
import type { RawRumEventCollectedData } from '../lifeCycle'
import { LifeCycle, LifeCycleEventType } from '../lifeCycle'
import type { RumConfiguration } from '../configuration'
Expand Down Expand Up @@ -61,6 +61,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,
Expand Down
3 changes: 2 additions & 1 deletion packages/rum-core/src/domain/longTask/longTaskCollection.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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,
Expand Down
Loading

0 comments on commit c0de7ea

Please sign in to comment.