diff --git a/packages/rum-core/src/boot/rumPublicApi.spec.ts b/packages/rum-core/src/boot/rumPublicApi.spec.ts index cb1eedf30f..752f4cd483 100644 --- a/packages/rum-core/src/boot/rumPublicApi.spec.ts +++ b/packages/rum-core/src/boot/rumPublicApi.spec.ts @@ -23,7 +23,6 @@ const noopStartRum = (): ReturnType => ({ getInternalContext: () => undefined, lifeCycle: {} as any, viewHistory: {} as any, - longTaskContexts: {} as any, session: {} as any, stopSession: () => undefined, startDurationVital: () => ({}) as DurationVitalReference, diff --git a/packages/rum-core/src/boot/rumPublicApi.ts b/packages/rum-core/src/boot/rumPublicApi.ts index 0bd96bb88b..5086fb216f 100644 --- a/packages/rum-core/src/boot/rumPublicApi.ts +++ b/packages/rum-core/src/boot/rumPublicApi.ts @@ -52,7 +52,6 @@ import { createCustomVitalsState } from '../domain/vital/vitalCollection' import { callPluginsMethod } from '../domain/plugins' import type { Hooks } from '../domain/hooks' import type { SdkName } from '../domain/contexts/defaultContext' -import type { LongTaskContexts } from '../domain/longTask/longTaskCollection' import { createPreStartStrategy } from './preStartRum' import type { StartRum, StartRumResult } from './startRum' @@ -485,7 +484,6 @@ export interface ProfilerApi { configuration: RumConfiguration, sessionManager: RumSessionManager, viewHistory: ViewHistory, - longTaskContexts: LongTaskContexts, createEncoder: (streamId: DeflateEncoderStreamId) => Encoder ) => void } @@ -578,7 +576,6 @@ export function makeRumPublicApi( configuration, startRumResult.session, startRumResult.viewHistory, - startRumResult.longTaskContexts, createEncoder ) diff --git a/packages/rum-core/src/boot/startRum.ts b/packages/rum-core/src/boot/startRum.ts index 1e7eff1b9a..af418603ac 100644 --- a/packages/rum-core/src/boot/startRum.ts +++ b/packages/rum-core/src/boot/startRum.ts @@ -239,7 +239,7 @@ export function startRumEventCollection( const { stop: stopResourceCollection } = startResourceCollection(lifeCycle, configuration, pageStateHistory) cleanupTasks.push(stopResourceCollection) - const { stop: stopLongTaskCollection, longTaskContexts } = startLongTaskCollection(lifeCycle, configuration) + const { stop: stopLongTaskCollection } = startLongTaskCollection(lifeCycle, configuration) cleanupTasks.push(stopLongTaskCollection) const { addError } = startErrorCollection(lifeCycle, configuration, bufferedDataObservable) @@ -276,7 +276,6 @@ export function startRumEventCollection( globalContext, userContext, accountContext, - longTaskContexts, stop: () => cleanupTasks.forEach((task) => task()), } } diff --git a/packages/rum-core/src/domain/action/actionCollection.spec.ts b/packages/rum-core/src/domain/action/actionCollection.spec.ts index 92821534a9..eff6f3cddb 100644 --- a/packages/rum-core/src/domain/action/actionCollection.spec.ts +++ b/packages/rum-core/src/domain/action/actionCollection.spec.ts @@ -64,7 +64,7 @@ describe('actionCollection', () => { events: [event], }) - expect(rawRumEvents[0].startTime).toBe(1234 as RelativeTime) + expect(rawRumEvents[0].startClocks.relative).toBe(1234 as RelativeTime) expect(rawRumEvents[0].rawRumEvent).toEqual({ action: { error: { @@ -116,7 +116,7 @@ describe('actionCollection', () => { context: { foo: 'bar' }, }) - expect(rawRumEvents[0].startTime).toBe(1234 as RelativeTime) + expect(rawRumEvents[0].startClocks.relative).toBe(1234 as RelativeTime) expect(rawRumEvents[0].rawRumEvent).toEqual({ action: { id: jasmine.any(String), diff --git a/packages/rum-core/src/domain/action/actionCollection.ts b/packages/rum-core/src/domain/action/actionCollection.ts index 4bf36878ff..7aadf65bb6 100644 --- a/packages/rum-core/src/domain/action/actionCollection.ts +++ b/packages/rum-core/src/domain/action/actionCollection.ts @@ -146,7 +146,7 @@ function processAction(action: AutoAction | CustomAction): RawRumEventCollectedD return { rawRumEvent: actionEvent, duration, - startTime: action.startClocks.relative, + startClocks: action.startClocks, domainContext, } } diff --git a/packages/rum-core/src/domain/assembly.spec.ts b/packages/rum-core/src/domain/assembly.spec.ts index 56b94647a6..d0452bd27f 100644 --- a/packages/rum-core/src/domain/assembly.spec.ts +++ b/packages/rum-core/src/domain/assembly.spec.ts @@ -1,5 +1,12 @@ import type { ClocksState, RelativeTime, TimeStamp } from '@datadog/browser-core' -import { ErrorSource, HookNames, ONE_MINUTE, display, startGlobalContext } from '@datadog/browser-core' +import { + ErrorSource, + HookNames, + ONE_MINUTE, + display, + relativeToClocks, + startGlobalContext, +} from '@datadog/browser-core' import type { Clock } from '@datadog/browser-core/test' import { registerCleanupTask, mockClock } from '@datadog/browser-core/test' import { @@ -461,7 +468,7 @@ describe('rum assembly', () => { notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.ACTION), - startTime: 123 as RelativeTime, + startClocks: relativeToClocks(123 as RelativeTime), }) expect(sessionManager.findTrackedSession).toHaveBeenCalledWith(123 as RelativeTime) @@ -567,11 +574,11 @@ describe('rum assembly', () => { function notifyRawRumEvent( lifeCycle: LifeCycle, - partialData: Omit, 'startTime' | 'domainContext'> & - Partial, 'startTime' | 'domainContext'>> + partialData: Omit, 'startClocks' | 'domainContext'> & + Partial, 'startClocks' | 'domainContext'>> ) { const fullData = { - startTime: 0 as RelativeTime, + startClocks: relativeToClocks(0 as RelativeTime), domainContext: {} as RumEventDomainContext, ...partialData, } diff --git a/packages/rum-core/src/domain/assembly.ts b/packages/rum-core/src/domain/assembly.ts index 2aacf816db..e1b32dfc1c 100644 --- a/packages/rum-core/src/domain/assembly.ts +++ b/packages/rum-core/src/domain/assembly.ts @@ -92,12 +92,12 @@ export function startRumAssembly( lifeCycle.subscribe( LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, - ({ startTime, duration, rawRumEvent, domainContext }) => { + ({ startClocks, duration, rawRumEvent, domainContext }) => { const defaultRumEventAttributes = hooks.triggerHook(HookNames.Assemble, { eventType: rawRumEvent.type, rawRumEvent, domainContext, - startTime, + startTime: startClocks.relative, duration, } as AssembleHookParams)! diff --git a/packages/rum-core/src/domain/error/errorCollection.spec.ts b/packages/rum-core/src/domain/error/errorCollection.spec.ts index 2a3d203d44..7e572cde69 100644 --- a/packages/rum-core/src/domain/error/errorCollection.spec.ts +++ b/packages/rum-core/src/domain/error/errorCollection.spec.ts @@ -65,10 +65,11 @@ describe('error collection', () => { it(`notifies a raw rum error event from ${testCase}`, () => { setupErrorCollection() + const startClocks = { relative: 1234 as RelativeTime, timeStamp: 123456789 as TimeStamp } addError({ error, handlingStack: 'Error: handling foo', - startClocks: { relative: 1234 as RelativeTime, timeStamp: 123456789 as TimeStamp }, + startClocks, }) expect(rawRumEvents.length).toBe(1) @@ -92,7 +93,7 @@ describe('error collection', () => { type: RumEventType.ERROR, context: undefined, }, - startTime: 1234 as RelativeTime, + startClocks, domainContext: { error, handlingStack: 'Error: handling foo', @@ -239,7 +240,7 @@ describe('error collection', () => { }, }) - expect(rawRumEvents[0].startTime).toBe(1234 as RelativeTime) + expect(rawRumEvents[0].startClocks.relative).toBe(1234 as RelativeTime) expect(rawRumEvents[0].rawRumEvent).toEqual({ date: jasmine.any(Number), error: { diff --git a/packages/rum-core/src/domain/error/errorCollection.ts b/packages/rum-core/src/domain/error/errorCollection.ts index baa08d67a7..d3a5c11722 100644 --- a/packages/rum-core/src/domain/error/errorCollection.ts +++ b/packages/rum-core/src/domain/error/errorCollection.ts @@ -98,7 +98,7 @@ function processError(error: RawError): RawRumEventCollectedData { eventCollection.addEvent(startTime, event, domainContext, duration) expect(notifySpy).toHaveBeenCalledWith(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { - startTime, + startClocks: { relative: startTime, timeStamp: jasmine.any(Number) }, rawRumEvent: event, domainContext, duration, diff --git a/packages/rum-core/src/domain/event/eventCollection.ts b/packages/rum-core/src/domain/event/eventCollection.ts index 2f10bf8593..eeb099b076 100644 --- a/packages/rum-core/src/domain/event/eventCollection.ts +++ b/packages/rum-core/src/domain/event/eventCollection.ts @@ -1,3 +1,4 @@ +import { relativeToClocks } from '@datadog/browser-core' import type { Context, Duration, RelativeTime } from '@datadog/browser-core' import type { RumEventDomainContext } from '../../domainContext.types' import type { LifeCycle } from '../lifeCycle' @@ -43,7 +44,7 @@ export function startEventCollection(lifeCycle: LifeCycle) { } lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { - startTime, + startClocks: relativeToClocks(startTime), rawRumEvent: event, domainContext, duration, diff --git a/packages/rum-core/src/domain/lifeCycle.ts b/packages/rum-core/src/domain/lifeCycle.ts index b84cb842f4..348e071939 100644 --- a/packages/rum-core/src/domain/lifeCycle.ts +++ b/packages/rum-core/src/domain/lifeCycle.ts @@ -1,4 +1,4 @@ -import type { Context, Duration, PageMayExitEvent, RawError, RelativeTime } from '@datadog/browser-core' +import type { ClocksState, Context, Duration, PageMayExitEvent, RawError } from '@datadog/browser-core' import { AbstractLifeCycle } from '@datadog/browser-core' import type { RumEventDomainContext } from '../domainContext.types' import type { RawRumEvent, AssembledRumEvent } from '../rawRumEvent.types' @@ -92,7 +92,7 @@ export interface LifeCycleEventMap { } export interface RawRumEventCollectedData { - startTime: RelativeTime + startClocks: ClocksState duration?: Duration rawRumEvent: E domainContext: RumEventDomainContext diff --git a/packages/rum-core/src/domain/longTask/longTaskCollection.spec.ts b/packages/rum-core/src/domain/longTask/longTaskCollection.spec.ts index 17a56a9de6..9196abfe01 100644 --- a/packages/rum-core/src/domain/longTask/longTaskCollection.spec.ts +++ b/packages/rum-core/src/domain/longTask/longTaskCollection.spec.ts @@ -1,4 +1,4 @@ -import type { Duration, RelativeTime, ServerDuration } from '@datadog/browser-core' +import type { RelativeTime, ServerDuration } from '@datadog/browser-core' import { registerCleanupTask } from '@datadog/browser-core/test' import { collectAndValidateRawRumEvents, @@ -8,7 +8,7 @@ import { } from '../../../test' import type { RumPerformanceEntry } from '../../browser/performanceObservable' import { RumPerformanceEntryType } from '../../browser/performanceObservable' -import type { RawRumEvent, RawRumLongTaskEvent } from '../../rawRumEvent.types' +import type { RawRumEvent } from '../../rawRumEvent.types' import { RumEventType, RumLongTaskEntryType } from '../../rawRumEvent.types' import type { RawRumEventCollectedData } from '../lifeCycle' import { LifeCycle } from '../lifeCycle' @@ -49,7 +49,7 @@ describe('longTaskCollection', () => { notifyPerformanceEntries([performanceLongAnimationFrameTiming]) - expect(rawRumEvents[0].startTime).toBe(1234 as RelativeTime) + expect(rawRumEvents[0].startClocks.relative).toBe(1234 as RelativeTime) expect(rawRumEvents[0].rawRumEvent).toEqual({ date: jasmine.any(Number), long_task: { @@ -98,21 +98,6 @@ describe('longTaskCollection', () => { expect(rawRumEvents.length).toBe(1) }) - it('should track long animation frame contexts', () => { - const longTaskCollection = setupLongTaskCollection() - const entry = createPerformanceEntry(RumPerformanceEntryType.LONG_ANIMATION_FRAME) - notifyPerformanceEntries([entry]) - - const longTask = (rawRumEvents[0].rawRumEvent as RawRumLongTaskEvent).long_task - const longTasks = longTaskCollection.longTaskContexts.findLongTasks(1234 as RelativeTime, 100 as Duration) - expect(longTasks).toContain({ - id: longTask.id, - startClocks: jasmine.objectContaining({ relative: entry.startTime }), - duration: entry.duration, - entryType: RumPerformanceEntryType.LONG_ANIMATION_FRAME, - }) - }) - it('should not collect when trackLongTasks=false', () => { setupLongTaskCollection({ trackLongTasks: false }) @@ -127,7 +112,7 @@ describe('longTaskCollection', () => { setupLongTaskCollection({ supportedEntryType: RumPerformanceEntryType.LONG_TASK }) notifyPerformanceEntries([createPerformanceEntry(RumPerformanceEntryType.LONG_TASK)]) - expect(rawRumEvents[0].startTime).toBe(1234 as RelativeTime) + expect(rawRumEvents[0].startClocks.relative).toBe(1234 as RelativeTime) expect(rawRumEvents[0].rawRumEvent).toEqual({ date: jasmine.any(Number), long_task: { @@ -158,21 +143,6 @@ describe('longTaskCollection', () => { expect(rawRumEvents.length).toBe(1) }) - it('should track long tasks contexts', () => { - const longTaskCollection = setupLongTaskCollection({ supportedEntryType: RumPerformanceEntryType.LONG_TASK }) - const entry = createPerformanceEntry(RumPerformanceEntryType.LONG_TASK) - notifyPerformanceEntries([entry]) - - const longTask = (rawRumEvents[0].rawRumEvent as RawRumLongTaskEvent).long_task - const longTasks = longTaskCollection.longTaskContexts.findLongTasks(1234 as RelativeTime, 100 as Duration) - expect(longTasks).toContain({ - id: longTask.id, - startClocks: jasmine.objectContaining({ relative: entry.startTime }), - duration: entry.duration, - entryType: RumPerformanceEntryType.LONG_TASK, - }) - }) - it('should not collect when trackLongTasks=false', () => { setupLongTaskCollection({ supportedEntryType: RumPerformanceEntryType.LONG_TASK, trackLongTasks: false }) diff --git a/packages/rum-core/src/domain/longTask/longTaskCollection.ts b/packages/rum-core/src/domain/longTask/longTaskCollection.ts index 4e175ece1a..e709fbd875 100644 --- a/packages/rum-core/src/domain/longTask/longTaskCollection.ts +++ b/packages/rum-core/src/domain/longTask/longTaskCollection.ts @@ -1,12 +1,5 @@ -import type { RelativeTime, ClocksState, Duration } from '@datadog/browser-core' -import { - toServerDuration, - relativeToClocks, - generateUUID, - createValueHistory, - SESSION_TIME_OUT_DELAY, - addDuration, -} from '@datadog/browser-core' +import type { ClocksState } from '@datadog/browser-core' +import { toServerDuration, relativeToClocks, generateUUID } from '@datadog/browser-core' import type { RawRumLongTaskEvent, RawRumLongAnimationFrameEvent } from '../../rawRumEvent.types' import { RumEventType, RumLongTaskEntryType } from '../../rawRumEvent.types' import type { LifeCycle } from '../lifeCycle' @@ -23,23 +16,7 @@ import type { } from '../../browser/performanceObservable' import type { RumConfiguration } from '../configuration' -export const LONG_TASK_ID_HISTORY_TIME_OUT_DELAY = SESSION_TIME_OUT_DELAY - -export interface LongTaskContext { - id: string - startClocks: ClocksState - duration: Duration - entryType: RumPerformanceEntryType.LONG_ANIMATION_FRAME | RumPerformanceEntryType.LONG_TASK -} - -export interface LongTaskContexts { - findLongTasks: (startTime: RelativeTime, duration: Duration) => LongTaskContext[] -} - export function startLongTaskCollection(lifeCycle: LifeCycle, configuration: RumConfiguration) { - const history = createValueHistory({ - expireDelay: LONG_TASK_ID_HISTORY_TIME_OUT_DELAY, - }) const entryType = supportPerformanceTimingEvent(RumPerformanceEntryType.LONG_ANIMATION_FRAME) ? RumPerformanceEntryType.LONG_ANIMATION_FRAME : RumPerformanceEntryType.LONG_TASK @@ -54,39 +31,29 @@ export function startLongTaskCollection(lifeCycle: LifeCycle, configuration: Rum } const startClocks = relativeToClocks(entry.startTime) - const taskId = generateUUID() - const rawRumEvent = processEntry(entry, startClocks, taskId) + const rawRumEvent = processEntry(entry, startClocks) lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { rawRumEvent, - startTime: startClocks.relative, + startClocks, duration: entry.duration, domainContext: { performanceEntry: entry }, }) - - history.add({ id: taskId, startClocks, duration: entry.duration, entryType }, startClocks.relative) - history.closeActive(addDuration(startClocks.relative, entry.duration)) } }) - const longTaskContexts: LongTaskContexts = { - findLongTasks: (startTime: RelativeTime, duration: Duration) => history.findAll(startTime, duration), - } - return { - stop: () => { - subscription.unsubscribe() - history.stop() - }, - longTaskContexts, + stop: () => subscription.unsubscribe(), } } function processEntry( entry: RumPerformanceLongTaskTiming | RumPerformanceLongAnimationFrameTiming, - startClocks: ClocksState, - taskId: string + startClocks: ClocksState ): RawRumLongTaskEvent | RawRumLongAnimationFrameEvent { + const id = generateUUID() + const duration = toServerDuration(entry.duration) + const baseEvent = { date: startClocks.timeStamp, type: RumEventType.LONG_TASK, @@ -97,9 +64,9 @@ function processEntry( return { ...baseEvent, long_task: { - id: taskId, + id, entry_type: RumLongTaskEntryType.LONG_TASK, - duration: toServerDuration(entry.duration), + duration, }, } } @@ -107,9 +74,9 @@ function processEntry( return { ...baseEvent, long_task: { - id: taskId, + id, entry_type: RumLongTaskEntryType.LONG_ANIMATION_FRAME, - duration: toServerDuration(entry.duration), + duration, blocking_duration: toServerDuration(entry.blockingDuration), first_ui_event_timestamp: toServerDuration(entry.firstUIEventTimestamp), render_start: toServerDuration(entry.renderStart), diff --git a/packages/rum-core/src/domain/resource/resourceCollection.spec.ts b/packages/rum-core/src/domain/resource/resourceCollection.spec.ts index 9c52ce06bf..4feafee495 100644 --- a/packages/rum-core/src/domain/resource/resourceCollection.spec.ts +++ b/packages/rum-core/src/domain/resource/resourceCollection.spec.ts @@ -70,7 +70,7 @@ describe('resourceCollection', () => { notifyPerformanceEntries([performanceEntry]) runTasks() - expect(rawRumEvents[0].startTime).toBe(200 as RelativeTime) + expect(rawRumEvents[0].startClocks.relative).toBe(200 as RelativeTime) expect(rawRumEvents[0].rawRumEvent).toEqual({ date: jasmine.any(Number) as unknown as TimeStamp, resource: { @@ -117,7 +117,7 @@ describe('resourceCollection', () => { }, }) - expect(rawRumEvents[0].startTime).toBe(200 as RelativeTime) + expect(rawRumEvents[0].startClocks.relative).toBe(200 as RelativeTime) expect(rawRumEvents[0].rawRumEvent).toEqual({ date: jasmine.any(Number), resource: { @@ -288,7 +288,7 @@ describe('resourceCollection', () => { runTasks() expect(rawRumEvents.length).toBe(1) - expect(rawRumEvents[0].startTime).toBe(200 as RelativeTime) + expect(rawRumEvents[0].startClocks.relative).toBe(200 as RelativeTime) expect(rawRumEvents[0].rawRumEvent).toEqual({ date: jasmine.any(Number), resource: { @@ -402,7 +402,7 @@ describe('resourceCollection', () => { }, }) - expect(rawRumEvents[0].startTime).toBe(200 as RelativeTime) + expect(rawRumEvents[0].startClocks.relative).toBe(200 as RelativeTime) expect(rawRumEvents[0].rawRumEvent).toEqual({ date: jasmine.any(Number), resource: { diff --git a/packages/rum-core/src/domain/resource/resourceCollection.ts b/packages/rum-core/src/domain/resource/resourceCollection.ts index 6f41d430ca..3b96acc16d 100644 --- a/packages/rum-core/src/domain/resource/resourceCollection.ts +++ b/packages/rum-core/src/domain/resource/resourceCollection.ts @@ -165,7 +165,7 @@ function assembleResource( ) return { - startTime: startClocks.relative, + startClocks, duration, rawRumEvent: resourceEvent, domainContext: getResourceDomainContext(entry, request), diff --git a/packages/rum-core/src/domain/view/viewCollection.spec.ts b/packages/rum-core/src/domain/view/viewCollection.spec.ts index 84c56ae5d0..192b6b844d 100644 --- a/packages/rum-core/src/domain/view/viewCollection.spec.ts +++ b/packages/rum-core/src/domain/view/viewCollection.spec.ts @@ -110,7 +110,7 @@ describe('viewCollection', () => { setupViewCollection() lifeCycle.notify(LifeCycleEventType.VIEW_UPDATED, VIEW) - expect(rawRumEvents[rawRumEvents.length - 1].startTime).toBe(1234 as RelativeTime) + expect(rawRumEvents[rawRumEvents.length - 1].startClocks.relative).toBe(1234 as RelativeTime) expect(rawRumEvents[rawRumEvents.length - 1].rawRumEvent).toEqual({ _dd: { document_version: 3, diff --git a/packages/rum-core/src/domain/view/viewCollection.ts b/packages/rum-core/src/domain/view/viewCollection.ts index 3d46c4aa1d..cdcd738080 100644 --- a/packages/rum-core/src/domain/view/viewCollection.ts +++ b/packages/rum-core/src/domain/view/viewCollection.ts @@ -163,7 +163,7 @@ function processViewUpdate( return { rawRumEvent: viewEvent, - startTime: view.startClocks.relative, + startClocks: view.startClocks, duration: view.duration, domainContext: { location: view.location, diff --git a/packages/rum-core/src/domain/vital/vitalCollection.spec.ts b/packages/rum-core/src/domain/vital/vitalCollection.spec.ts index 41f2ef96e9..d619a1c27a 100644 --- a/packages/rum-core/src/domain/vital/vitalCollection.spec.ts +++ b/packages/rum-core/src/domain/vital/vitalCollection.spec.ts @@ -208,7 +208,7 @@ describe('vitalCollection', () => { vitalCollection.startDurationVital('foo') vitalCollection.stopDurationVital('foo') - expect(rawRumEvents[0].startTime).toEqual(jasmine.any(Number)) + expect(rawRumEvents[0].startClocks.relative).toEqual(jasmine.any(Number)) expect(rawRumEvents[0].rawRumEvent).toEqual({ date: jasmine.any(Number), vital: { @@ -228,7 +228,7 @@ describe('vitalCollection', () => { mockExperimentalFeatures([ExperimentalFeature.FEATURE_OPERATION_VITAL]) vitalCollection.addOperationStepVital('foo', 'start') - expect(rawRumEvents[0].startTime).toEqual(jasmine.any(Number)) + expect(rawRumEvents[0].startClocks.relative).toEqual(jasmine.any(Number)) expect(rawRumEvents[0].rawRumEvent).toEqual({ date: jasmine.any(Number), vital: { diff --git a/packages/rum-core/src/domain/vital/vitalCollection.ts b/packages/rum-core/src/domain/vital/vitalCollection.ts index 56d1af1842..c749242cad 100644 --- a/packages/rum-core/src/domain/vital/vitalCollection.ts +++ b/packages/rum-core/src/domain/vital/vitalCollection.ts @@ -226,7 +226,7 @@ function processVital(vital: DurationVital | OperationStepVital): RawRumEventCol type: RumEventType.VITAL, context, }, - startTime: startClocks.relative, + startClocks, duration: type === VitalType.DURATION ? vital.duration : undefined, domainContext: {}, } diff --git a/packages/rum-core/src/index.ts b/packages/rum-core/src/index.ts index 5f7b4283ea..eda1e197a9 100644 --- a/packages/rum-core/src/index.ts +++ b/packages/rum-core/src/index.ts @@ -24,13 +24,12 @@ export type { RumVitalEventDomainContext, } from './domainContext.types' export type { ReplayStats, RawRumActionEvent, RawRumEvent } from './rawRumEvent.types' -export { ActionType, RumEventType, FrustrationType } from './rawRumEvent.types' +export { ActionType, RumEventType, FrustrationType, RumLongTaskEntryType } from './rawRumEvent.types' export { startRum } from './boot/startRum' export type { RawRumEventCollectedData } from './domain/lifeCycle' export { LifeCycle, LifeCycleEventType } from './domain/lifeCycle' export type { ViewCreatedEvent, ViewOptions } from './domain/view/trackViews' export type { ViewHistoryEntry, ViewHistory } from './domain/contexts/viewHistory' -export type { LongTaskContexts, LongTaskContext } from './domain/longTask/longTaskCollection' export { startViewHistory } from './domain/contexts/viewHistory' export type { RumSessionManager, RumSession } from './domain/rumSessionManager' export { getMutationObserverConstructor } from './browser/domMutationObservable' diff --git a/packages/rum/src/boot/profilerApi.ts b/packages/rum/src/boot/profilerApi.ts index fa1fa31327..c4f6ad7aa5 100644 --- a/packages/rum/src/boot/profilerApi.ts +++ b/packages/rum/src/boot/profilerApi.ts @@ -5,7 +5,6 @@ import type { RumConfiguration, ProfilerApi, Hooks, - LongTaskContexts, } from '@datadog/browser-rum-core' import type { DeflateEncoderStreamId, Encoder } from '@datadog/browser-core' import { isSampled } from '@datadog/browser-rum-core' @@ -24,7 +23,6 @@ export function makeProfilerApi(): ProfilerApi { configuration: RumConfiguration, sessionManager: RumSessionManager, viewHistory: ViewHistory, - longTaskContexts: LongTaskContexts, createEncoder: (streamId: DeflateEncoderStreamId) => Encoder ) { const session = sessionManager.findTrackedSession() // Check if the session is tracked. @@ -66,7 +64,6 @@ export function makeProfilerApi(): ProfilerApi { lifeCycle, sessionManager, profilingContextManager, - longTaskContexts, createEncoder, viewHistory, undefined diff --git a/packages/rum/src/domain/profiling/longTaskHistory.spec.ts b/packages/rum/src/domain/profiling/longTaskHistory.spec.ts new file mode 100644 index 0000000000..5ec5660459 --- /dev/null +++ b/packages/rum/src/domain/profiling/longTaskHistory.spec.ts @@ -0,0 +1,132 @@ +import { relativeToClocks, type Duration, type RelativeTime } from '@datadog/browser-core' +import { LifeCycle, LifeCycleEventType, RumEventType } from '@datadog/browser-rum-core' +import { createRawRumEvent } from '@datadog/browser-rum-core/test' +import { createLongTaskHistory } from './longTaskHistory' + +describe('longTaskHistory', () => { + let lifeCycle: LifeCycle + + beforeEach(() => { + lifeCycle = new LifeCycle() + }) + + const fakeDomainContext = { + performanceEntry: {} as PerformanceEntry, + } + + describe('createLongTaskHistory', () => { + it('should create a history with the correct expire delay', () => { + const history = createLongTaskHistory(lifeCycle) + expect(history).toBeDefined() + }) + + it('should add long task IDs to history when RAW_RUM_EVENT_COLLECTED is triggered with long_task event', () => { + const history = createLongTaskHistory(lifeCycle) + + lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK, { + long_task: { + id: 'long-task-123', + }, + }), + startClocks: relativeToClocks(10 as RelativeTime), + duration: 20 as Duration, + domainContext: fakeDomainContext, + }) + + expect(history.findAll(5 as RelativeTime, 30 as RelativeTime)).toEqual([ + { + id: 'long-task-123', + startClocks: relativeToClocks(10 as RelativeTime), + duration: 20 as Duration, + entryType: 'long-task', + }, + ]) + }) + + it('should not add events to history for non-long_task events', () => { + const history = createLongTaskHistory(lifeCycle) + + lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + rawRumEvent: createRawRumEvent(RumEventType.VIEW, { + view: { + id: 'view-123', + }, + }), + startClocks: relativeToClocks(50 as RelativeTime), + duration: 10 as Duration, + domainContext: { location: window.location }, + }) + + expect(history.findAll(40 as RelativeTime, 30 as RelativeTime)).toEqual([]) + }) + + it('should store multiple long task IDs with their time ranges', () => { + const history = createLongTaskHistory(lifeCycle) + + lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK, { + long_task: { + id: 'long-task-1', + }, + }), + startClocks: relativeToClocks(10 as RelativeTime), + duration: 20 as Duration, + domainContext: fakeDomainContext, + }) + + lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK, { + long_task: { + id: 'long-task-2', + }, + }), + startClocks: relativeToClocks(50 as RelativeTime), + duration: 30 as Duration, + domainContext: fakeDomainContext, + }) + + expect(history.findAll(5 as RelativeTime, 30 as RelativeTime).map((longTask) => longTask.id)).toEqual([ + 'long-task-1', + ]) + expect(history.findAll(45 as RelativeTime, 40 as RelativeTime).map((longTask) => longTask.id)).toEqual([ + 'long-task-2', + ]) + expect(history.findAll(0 as RelativeTime, 100 as RelativeTime).map((longTask) => longTask.id)).toEqual([ + 'long-task-2', + 'long-task-1', + ]) + }) + + it('should handle overlapping long task time ranges', () => { + const history = createLongTaskHistory(lifeCycle) + + lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK, { + long_task: { + id: 'long-task-1', + }, + }), + startClocks: relativeToClocks(10 as RelativeTime), + duration: 40 as Duration, + domainContext: fakeDomainContext, + }) + + lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK, { + long_task: { + id: 'long-task-2', + }, + }), + startClocks: relativeToClocks(30 as RelativeTime), + duration: 40 as Duration, + domainContext: fakeDomainContext, + }) + + expect(history.findAll(35 as RelativeTime, 20 as RelativeTime).map((longTask) => longTask.id)).toEqual([ + 'long-task-2', + 'long-task-1', + ]) + }) + }) +}) diff --git a/packages/rum/src/domain/profiling/longTaskHistory.ts b/packages/rum/src/domain/profiling/longTaskHistory.ts new file mode 100644 index 0000000000..10a55957d7 --- /dev/null +++ b/packages/rum/src/domain/profiling/longTaskHistory.ts @@ -0,0 +1,37 @@ +import type { ClocksState, Duration } from '@datadog/browser-core' +import { addDuration, createValueHistory, SESSION_TIME_OUT_DELAY } from '@datadog/browser-core' +import { LifeCycleEventType } from '@datadog/browser-rum-core' +import type { LifeCycle, RumLongTaskEntryType } from '@datadog/browser-rum-core' + +export const LONG_TASK_ID_HISTORY_EXPIRE_DELAY = SESSION_TIME_OUT_DELAY + +export interface LongTaskContext { + id: string + startClocks: ClocksState + duration: Duration + entryType: RumLongTaskEntryType +} + +export function createLongTaskHistory(lifeCycle: LifeCycle) { + const history = createValueHistory({ + expireDelay: LONG_TASK_ID_HISTORY_EXPIRE_DELAY, + }) + + lifeCycle.subscribe(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, ({ rawRumEvent, startClocks, duration }) => { + if (rawRumEvent.type === 'long_task') { + history + .add( + { + id: rawRumEvent.long_task.id, + startClocks, + duration: duration!, + entryType: rawRumEvent.long_task.entry_type, + }, + startClocks.relative + ) + .close(addDuration(startClocks.relative, duration!)) + } + }) + + return history +} diff --git a/packages/rum/src/domain/profiling/profiler.spec.ts b/packages/rum/src/domain/profiling/profiler.spec.ts index 9dd29f6482..356872c664 100644 --- a/packages/rum/src/domain/profiling/profiler.spec.ts +++ b/packages/rum/src/domain/profiling/profiler.spec.ts @@ -1,6 +1,6 @@ -import type { LongTaskContext, ViewHistoryEntry } from '@datadog/browser-rum-core' -import { LifeCycle, LifeCycleEventType, RumPerformanceEntryType, createHooks } from '@datadog/browser-rum-core' -import type { Duration, RelativeTime } from '@datadog/browser-core' +import type { ViewHistoryEntry } from '@datadog/browser-rum-core' +import { LifeCycle, LifeCycleEventType, RumLongTaskEntryType, createHooks } from '@datadog/browser-rum-core' +import type { Duration } from '@datadog/browser-core' import { addDuration, clocksNow, @@ -8,6 +8,7 @@ import { createIdentityEncoder, createValueHistory, deepClone, + ONE_DAY, relativeNow, timeStampNow, } from '@datadog/browser-core' @@ -20,7 +21,6 @@ import { readFormDataRequest, mockClock, } from '@datadog/browser-core/test' -import { LONG_TASK_ID_HISTORY_TIME_OUT_DELAY } from 'packages/rum-core/src/domain/longTask/longTaskCollection' import { createRumSessionManagerMock, mockRumConfiguration, mockViewHistory } from '../../../../rum-core/test' import { mockProfiler } from '../../../test' import { mockedTrace } from './test-utils/mockedTrace' @@ -29,6 +29,7 @@ import type { ProfilerTrace, RumProfilerTrace } from './types' import type { ProfilingContextManager } from './profilingContext' import { startProfilingContext } from './profilingContext' import type { ProfileEventPayload } from './transport/assembly' +import type { LongTaskContext } from './longTaskHistory' describe('profiler', () => { // Store the original pathname @@ -85,22 +86,9 @@ describe('profiler', () => { // Replace Browser's Profiler with a mock for testing purpose. mockProfiler(mockProfilerTrace) - // Mock longTaskContexts - function mockLongTaskContexts() { - const longTaskContexts = createValueHistory({ - expireDelay: LONG_TASK_ID_HISTORY_TIME_OUT_DELAY, - }) - - return { - findLongTasks: (startTime: RelativeTime, duration: Duration): LongTaskContext[] => - longTaskContexts.findAll(startTime, duration), - addLongTask: (longTask: LongTaskContext) => - longTaskContexts - .add(longTask, longTask.startClocks.relative) - .close(addDuration(longTask.startClocks.relative, longTask.duration)), - } - } - const longTaskContexts = mockLongTaskContexts() + const longTaskHistory = createValueHistory({ + expireDelay: ONE_DAY, + }) // Start collection of profile. const profiler = createRumProfiler( @@ -108,7 +96,6 @@ describe('profiler', () => { lifeCycle, sessionManager, profilingContextManager, - longTaskContexts, createIdentityEncoder, viewHistory, // Overrides default configuration for testing purpose. @@ -117,9 +104,17 @@ describe('profiler', () => { collectIntervalMs: 60000, // 1min minNumberOfSamples: 0, minProfileDurationMs: 0, - } + }, + longTaskHistory ) - return { profiler, profilingContextManager, mockedRumProfilerTrace, longTaskContexts } + return { + profiler, + profilingContextManager, + mockedRumProfilerTrace, + addLongTask: (longTask: LongTaskContext) => { + longTaskHistory.add(longTask, relativeNow()).close(addDuration(relativeNow(), longTask.duration)) + }, + } } it('should start profiling collection and collect data on stop', async () => { @@ -196,26 +191,26 @@ describe('profiler', () => { it('should collect long task happening during a profiling session', async () => { const clock = mockClock() - const { profiler, profilingContextManager, longTaskContexts } = setupProfiler() + const { profiler, profilingContextManager, addLongTask } = setupProfiler() // Start collection of profile. profiler.start() await waitForBoolean(() => profiler.isRunning()) expect(profilingContextManager.get()?.status).toBe('running') - longTaskContexts.addLongTask({ + addLongTask({ id: 'long-task-id-1', startClocks: clocksNow(), duration: 50 as Duration, - entryType: RumPerformanceEntryType.LONG_ANIMATION_FRAME, + entryType: RumLongTaskEntryType.LONG_ANIMATION_FRAME, }) clock.tick(50) - longTaskContexts.addLongTask({ + addLongTask({ id: 'long-task-id-2', startClocks: clocksNow(), duration: 100 as Duration, - entryType: RumPerformanceEntryType.LONG_ANIMATION_FRAME, + entryType: RumLongTaskEntryType.LONG_ANIMATION_FRAME, }) // Stop first profiling session. @@ -227,11 +222,11 @@ describe('profiler', () => { profiler.start() await waitForBoolean(() => profiler.isRunning()) - longTaskContexts.addLongTask({ + addLongTask({ id: 'long-task-id-3', startClocks: clocksNow(), duration: 100 as Duration, - entryType: RumPerformanceEntryType.LONG_ANIMATION_FRAME, + entryType: RumLongTaskEntryType.LONG_ANIMATION_FRAME, }) clock.tick(500) @@ -255,13 +250,13 @@ describe('profiler', () => { id: 'long-task-id-2', startClocks: jasmine.any(Object), duration: 100 as Duration, - entryType: RumPerformanceEntryType.LONG_ANIMATION_FRAME, + entryType: RumLongTaskEntryType.LONG_ANIMATION_FRAME, }, { id: 'long-task-id-1', startClocks: jasmine.any(Object), duration: 50 as Duration, - entryType: RumPerformanceEntryType.LONG_ANIMATION_FRAME, + entryType: RumLongTaskEntryType.LONG_ANIMATION_FRAME, }, ]) @@ -271,7 +266,7 @@ describe('profiler', () => { id: 'long-task-id-3', startClocks: jasmine.any(Object), duration: 100 as Duration, - entryType: RumPerformanceEntryType.LONG_ANIMATION_FRAME, + entryType: RumLongTaskEntryType.LONG_ANIMATION_FRAME, }, ]) }) diff --git a/packages/rum/src/domain/profiling/profiler.ts b/packages/rum/src/domain/profiling/profiler.ts index 073844fa28..cac2568540 100644 --- a/packages/rum/src/domain/profiling/profiler.ts +++ b/packages/rum/src/domain/profiling/profiler.ts @@ -1,4 +1,4 @@ -import type { Encoder } from '@datadog/browser-core' +import type { Encoder, ValueHistory } from '@datadog/browser-core' import { addEventListener, clearTimeout, @@ -15,7 +15,6 @@ import { import type { LifeCycle, - LongTaskContexts, RumConfiguration, RumSessionManager, TransportPayload, @@ -35,6 +34,8 @@ import { getNumberOfSamples } from './utils/getNumberOfSamples' import type { ProfilingContextManager } from './profilingContext' import { getCustomOrDefaultViewName } from './utils/getCustomOrDefaultViewName' import { assembleProfilingPayload } from './transport/assembly' +import type { LongTaskContext } from './longTaskHistory' +import { createLongTaskHistory } from './longTaskHistory' export const DEFAULT_RUM_PROFILER_CONFIGURATION: RUMProfilerConfiguration = { sampleIntervalMs: 10, // Sample stack trace every 10ms @@ -48,10 +49,10 @@ export function createRumProfiler( lifeCycle: LifeCycle, session: RumSessionManager, profilingContextManager: ProfilingContextManager, - longTaskContexts: LongTaskContexts, createEncoder: (streamId: DeflateEncoderStreamId) => Encoder, viewHistory: ViewHistory, - profilerConfiguration: RUMProfilerConfiguration = DEFAULT_RUM_PROFILER_CONFIGURATION + profilerConfiguration: RUMProfilerConfiguration = DEFAULT_RUM_PROFILER_CONFIGURATION, + longTaskHistory: Pick, 'findAll'> = createLongTaskHistory(lifeCycle) ): RUMProfiler { const transport = createFormDataTransport(configuration, lifeCycle, createEncoder, DeflateEncoderStreamId.PROFILING) @@ -231,7 +232,7 @@ export function createRumProfiler( .then((trace) => { const endClocks = clocksNow() const duration = elapsed(startClocks.timeStamp, endClocks.timeStamp) - const longTasks = longTaskContexts.findLongTasks(startClocks.relative, duration) + const longTasks = longTaskHistory.findAll(startClocks.relative, duration) const isBelowDurationThreshold = duration < profilerConfiguration.minProfileDurationMs const isBelowSampleThreshold = getNumberOfSamples(trace.samples) < profilerConfiguration.minNumberOfSamples diff --git a/packages/rum/src/domain/profiling/transport/buildProfileEventAttributes.spec.ts b/packages/rum/src/domain/profiling/transport/buildProfileEventAttributes.spec.ts index 63a2fc7a17..102925cc34 100644 --- a/packages/rum/src/domain/profiling/transport/buildProfileEventAttributes.spec.ts +++ b/packages/rum/src/domain/profiling/transport/buildProfileEventAttributes.spec.ts @@ -1,7 +1,7 @@ import { clocksOrigin } from '@datadog/browser-core' -import { RumPerformanceEntryType } from '@datadog/browser-rum-core' -import type { LongTaskContext } from '@datadog/browser-rum-core' +import { RumLongTaskEntryType } from '@datadog/browser-rum-core' import type { RumProfilerTrace, RumViewEntry } from '../types' +import type { LongTaskContext } from '../longTaskHistory' import { buildProfileEventAttributes, type ProfileEventAttributes } from './buildProfileEventAttributes' describe('buildProfileEventAttributes', () => { @@ -21,7 +21,7 @@ describe('buildProfileEventAttributes', () => { return { id: 'longtask-456', duration: 100 as any, - entryType: RumPerformanceEntryType.LONG_TASK, + entryType: RumLongTaskEntryType.LONG_TASK, startClocks: clocksOrigin(), ...overrides, } diff --git a/packages/rum/src/domain/profiling/types/rumProfiler.types.ts b/packages/rum/src/domain/profiling/types/rumProfiler.types.ts index ac6210f5b4..f259f7f5d9 100644 --- a/packages/rum/src/domain/profiling/types/rumProfiler.types.ts +++ b/packages/rum/src/domain/profiling/types/rumProfiler.types.ts @@ -1,5 +1,5 @@ import type { TimeoutId, ClocksState } from '@datadog/browser-core' -import type { LongTaskContext } from '@datadog/browser-rum-core' +import type { LongTaskContext } from '../longTaskHistory' import type { ProfilerTrace, Profiler } from './profilerApi.types' export interface RumViewEntry { diff --git a/packages/rum/test/rumFrustrationEvent.ts b/packages/rum/test/rumFrustrationEvent.ts index ce16199672..fa2a919a60 100644 --- a/packages/rum/test/rumFrustrationEvent.ts +++ b/packages/rum/test/rumFrustrationEvent.ts @@ -1,10 +1,10 @@ -import { relativeNow, timeStampNow } from '@datadog/browser-core' +import { clocksNow, timeStampNow } from '@datadog/browser-core' import type { RawRumActionEvent, RawRumEventCollectedData } from '@datadog/browser-rum-core' import { ActionType, FrustrationType, RumEventType } from '@datadog/browser-rum-core' export function createRumFrustrationEvent(mouseEvent: MouseEvent): RawRumEventCollectedData { return { - startTime: relativeNow(), + startClocks: clocksNow(), rawRumEvent: { date: timeStampNow(), type: RumEventType.ACTION,