diff --git a/packages/rum/src/domain/profiling/profiler.spec.ts b/packages/rum/src/domain/profiling/profiler.spec.ts index 9dd29f6482..1dc1314a1a 100644 --- a/packages/rum/src/domain/profiling/profiler.spec.ts +++ b/packages/rum/src/domain/profiling/profiler.spec.ts @@ -23,9 +23,10 @@ import { 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 type { BrowserProfilerTrace } from '../../types' import { mockedTrace } from './test-utils/mockedTrace' import { createRumProfiler } from './profiler' -import type { ProfilerTrace, RumProfilerTrace } from './types' +import type { ProfilerTrace } from './types' import type { ProfilingContextManager } from './profilingContext' import { startProfilingContext } from './profilingContext' import type { ProfileEventPayload } from './transport/assembly' @@ -56,7 +57,7 @@ describe('profiler', () => { const mockProfilerTrace: ProfilerTrace = deepClone(mockedTrace) - const mockedRumProfilerTrace: RumProfilerTrace = Object.assign(mockProfilerTrace, { + const mockedRumProfilerTrace: BrowserProfilerTrace = Object.assign(mockProfilerTrace, { startClocks: { relative: relativeNow(), timeStamp: timeStampNow(), diff --git a/packages/rum/src/domain/profiling/profiler.ts b/packages/rum/src/domain/profiling/profiler.ts index 073844fa28..ad41e5591d 100644 --- a/packages/rum/src/domain/profiling/profiler.ts +++ b/packages/rum/src/domain/profiling/profiler.ts @@ -22,14 +22,13 @@ import type { ViewHistory, } from '@datadog/browser-rum-core' import { createFormDataTransport, LifeCycleEventType } from '@datadog/browser-rum-core' +import type { BrowserProfilerTrace, RumViewEntry } from '../../types' import type { - RumProfilerTrace, RumProfilerInstance, Profiler, RUMProfiler, RUMProfilerConfiguration, RumProfilerStoppedInstance, - RumViewEntry, } from './types' import { getNumberOfSamples } from './utils/getNumberOfSamples' import type { ProfilingContextManager } from './profilingContext' @@ -290,7 +289,7 @@ export function createRumProfiler( instance.views.push(viewEntry) } - function handleProfilerTrace(trace: RumProfilerTrace): void { + function handleProfilerTrace(trace: BrowserProfilerTrace): void { // Find current session to assign it to the Profile. const sessionId = session.findTrackedSession()?.id const payload = assembleProfilingPayload(trace, configuration, sessionId) diff --git a/packages/rum/src/domain/profiling/transport/assembly.ts b/packages/rum/src/domain/profiling/transport/assembly.ts index 4aeeddb02e..723d20c486 100644 --- a/packages/rum/src/domain/profiling/transport/assembly.ts +++ b/packages/rum/src/domain/profiling/transport/assembly.ts @@ -1,30 +1,15 @@ import { buildTags, currentDrift } from '@datadog/browser-core' import type { RumConfiguration } from '@datadog/browser-rum-core' -import type { RumProfilerTrace } from '../types' +import type { BrowserProfileEvent, BrowserProfilerTrace } from '../../../types' import { buildProfileEventAttributes } from './buildProfileEventAttributes' -import type { ProfileEventAttributes } from './buildProfileEventAttributes' - -export interface ProfileEvent extends ProfileEventAttributes { - attachments: string[] - start: string // ISO date - end: string // ISO date - family: 'chrome' - runtime: 'chrome' - format: 'json' - version: 4 - tags_profiler: string - _dd: { - clock_drift: number - } -} export interface ProfileEventPayload { - event: ProfileEvent - 'wall-time.json': RumProfilerTrace + event: BrowserProfileEvent + 'wall-time.json': BrowserProfilerTrace } export function assembleProfilingPayload( - profilerTrace: RumProfilerTrace, + profilerTrace: BrowserProfilerTrace, configuration: RumConfiguration, sessionId: string | undefined ): ProfileEventPayload { @@ -37,15 +22,15 @@ export function assembleProfilingPayload( } function buildProfileEvent( - profilerTrace: RumProfilerTrace, + profilerTrace: BrowserProfilerTrace, configuration: RumConfiguration, sessionId: string | undefined -): ProfileEvent { +): ProfileEventPayload['event'] { const tags = buildTags(configuration) // TODO: get that from the tagContext hook const profileAttributes = buildProfileEventAttributes(profilerTrace, configuration.applicationId, sessionId) const profileEventTags = buildProfileEventTags(tags) - const profileEvent: ProfileEvent = { + const profileEvent: ProfileEventPayload['event'] = { ...profileAttributes, attachments: ['wall-time.json'], start: new Date(profilerTrace.startClocks.timeStamp).toISOString(), diff --git a/packages/rum/src/domain/profiling/transport/buildProfileEventAttributes.spec.ts b/packages/rum/src/domain/profiling/transport/buildProfileEventAttributes.spec.ts index 63a2fc7a17..77e3dde664 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 type { RumProfilerTrace, RumViewEntry } from '../types' +import type { BrowserProfilerTrace, RumViewEntry } from '../../../types' import { buildProfileEventAttributes, type ProfileEventAttributes } from './buildProfileEventAttributes' describe('buildProfileEventAttributes', () => { @@ -27,7 +27,7 @@ describe('buildProfileEventAttributes', () => { } } - function createMockProfilerTrace(overrides: Partial = {}): RumProfilerTrace { + function createMockProfilerTrace(overrides: Partial = {}): BrowserProfilerTrace { return { startClocks: clocksOrigin(), endClocks: clocksOrigin(), diff --git a/packages/rum/src/domain/profiling/transport/buildProfileEventAttributes.ts b/packages/rum/src/domain/profiling/transport/buildProfileEventAttributes.ts index 33836bd31e..b2fdc1f203 100644 --- a/packages/rum/src/domain/profiling/transport/buildProfileEventAttributes.ts +++ b/packages/rum/src/domain/profiling/transport/buildProfileEventAttributes.ts @@ -1,10 +1,19 @@ -import type { RumProfilerTrace, RumViewEntry } from '../types' +import type { BrowserProfilerTrace, RumViewEntry } from '../../../types' export interface ProfileEventAttributes { - application: { id: string } - session?: { id: string } - view?: { id: string[]; name: string[] } - long_task?: { id: string[] } + application: { + id: string + } + session?: { + id: string + } + view?: { + id: string[] + name: string[] + } + long_task?: { + id: string[] + } } /** @@ -16,35 +25,34 @@ export interface ProfileEventAttributes { * @returns Additional attributes. */ export function buildProfileEventAttributes( - profilerTrace: RumProfilerTrace, + profilerTrace: BrowserProfilerTrace, applicationId: string, sessionId: string | undefined ): ProfileEventAttributes { - const attributes: ProfileEventAttributes = { - application: { - id: applicationId, - }, - } - if (sessionId) { - attributes.session = { - id: sessionId, - } - } - // Extract view ids and names from the profiler trace and add them as attributes of the profile event. // This will be used to filter the profiles by @view.id and/or @view.name. const { ids, names } = extractViewIdsAndNames(profilerTrace.views) - if (ids.length) { - attributes.view = { - id: ids, - name: names, - } - } const longTaskIds: string[] = profilerTrace.longTasks.map((longTask) => longTask.id).filter((id) => id !== undefined) - if (longTaskIds.length) { - attributes.long_task = { id: longTaskIds } + const attributes: ProfileEventAttributes = { + application: { + id: applicationId, + }, + ...(sessionId && { + session: { + id: sessionId, + }, + }), + ...(ids.length && { + view: { + id: ids, + name: names, + }, + }), + ...(longTaskIds.length && { + long_task: { id: longTaskIds }, + }), } return attributes } diff --git a/packages/rum/src/domain/profiling/types/rumProfiler.types.ts b/packages/rum/src/domain/profiling/types/rumProfiler.types.ts index ac6210f5b4..77e02eedec 100644 --- a/packages/rum/src/domain/profiling/types/rumProfiler.types.ts +++ b/packages/rum/src/domain/profiling/types/rumProfiler.types.ts @@ -1,15 +1,7 @@ import type { TimeoutId, ClocksState } from '@datadog/browser-core' import type { LongTaskContext } from '@datadog/browser-rum-core' -import type { ProfilerTrace, Profiler } from './profilerApi.types' - -export interface RumViewEntry { - /** Detected start time of view */ - readonly startClocks: ClocksState - /** RUM view id */ - readonly viewId: string - /** RUM view name */ - readonly viewName: string | undefined -} +import type { RumViewEntry } from '../../../types' +import type { Profiler } from './profilerApi.types' /** * Additional data recorded during profiling session @@ -21,17 +13,6 @@ export interface RumProfilerEnrichmentData { readonly views: RumViewEntry[] } -export interface RumProfilerTrace extends ProfilerTrace, RumProfilerEnrichmentData { - /** High resolution time when profiler trace started, relative to the profiling session's time origin */ - readonly startClocks: ClocksState - /** High resolution time when profiler trace ended, relative to the profiling session's time origin */ - readonly endClocks: ClocksState - /** Time origin of the profiling session */ - readonly clocksOrigin: ClocksState - /** Sample interval in milliseconds */ - readonly sampleInterval: number -} - /** * Describes profiler session state when it's stopped */ diff --git a/packages/rum/src/types/index.ts b/packages/rum/src/types/index.ts index 411eab48a1..607c54e7ca 100644 --- a/packages/rum/src/types/index.ts +++ b/packages/rum/src/types/index.ts @@ -1,2 +1,3 @@ export type * from './sessionReplay' export * from './sessionReplayConstants' +export type * from './profiling' diff --git a/packages/rum/src/types/profiling.ts b/packages/rum/src/types/profiling.ts new file mode 100644 index 0000000000..bf751ccade --- /dev/null +++ b/packages/rum/src/types/profiling.ts @@ -0,0 +1,229 @@ +/* eslint-disable */ +/** + * DO NOT MODIFY IT BY HAND. Run `yarn json-schemas:sync` instead. + */ + +/** + * Schema of Browser SDK Profiling types. + */ +export type BrowserProfiling = BrowserProfileEvent | BrowserProfilerTrace +/** + * Schema of the Browser SDK Profile Event payload. + */ +export type BrowserProfileEvent = ProfileCommonProperties & { + /** + * Profile data format. + */ + readonly format: 'json' + /** + * Datadog internal metadata. + */ + readonly _dd: { + /** + * Clock drift value. Used by Browser SDK. + */ + readonly clock_drift: number + } +} + +/** + * Schema of a Profile Event metadata. Contains attributes shared by all profiles. + */ +export interface ProfileCommonProperties { + /** + * Application properties. + */ + readonly application: { + /** + * Application ID. + */ + readonly id: string + } + /** + * Session properties. + */ + readonly session?: { + /** + * Session ID. + */ + readonly id: string + } + /** + * View properties. + */ + readonly view?: { + /** + * Array of view IDs. + */ + readonly id: string[] + /** + * Array of view names. + */ + readonly name: string[] + } + /** + * Long task properties. + */ + readonly long_task?: { + /** + * Array of long task IDs. + */ + readonly id: string[] + } + /** + * List of attachment filenames. + */ + readonly attachments: string[] + /** + * Start time as ISO 8601 date string (yyyy-MM-dd'T'HH:mm:ss.SSS'Z'). + */ + readonly start: string + /** + * End time marking when the profile ended, as ISO 8601 date string (yyyy-MM-dd'T'HH:mm:ss.SSS'Z'). + */ + readonly end: string + /** + * Profiler family. + */ + readonly family: 'android' | 'chrome' | 'ios' + /** + * Runtime environment. + */ + readonly runtime: 'android' | 'chrome' | 'ios' + /** + * Profile ingestion event version. + */ + readonly version: number + /** + * Comma-separated profiler tags. + */ + readonly tags_profiler: string +} +/** + * Schema of a RUM profiler trace containing profiling data enriched with RUM context. + */ +export interface BrowserProfilerTrace { + /** + * An array of profiler resources. + */ + readonly resources: string[] + /** + * An array of profiler frames. + */ + readonly frames: ProfilerFrame[] + /** + * An array of profiler stacks. + */ + readonly stacks: ProfilerStack[] + /** + * An array of profiler samples. + */ + readonly samples: ProfilerSample[] + startClocks: ClocksState + endClocks: ClocksState + clocksOrigin: ClocksState + /** + * Sample interval in milliseconds. + */ + readonly sampleInterval: number + /** + * List of detected long tasks. + */ + readonly longTasks: RumProfilerLongTaskEntry[] + /** + * List of detected navigation entries. + */ + readonly views: RumViewEntry[] +} +/** + * Schema of a profiler frame from the JS Self-Profiling API. + */ +export interface ProfilerFrame { + /** + * A function instance name. + */ + readonly name: string + /** + * Index in the trace.resources array. + */ + readonly resourceId?: number + /** + * 1-based index of the line. + */ + readonly line?: number + /** + * 1-based index of the column. + */ + readonly column?: number +} +/** + * Schema of a profiler stack from the JS Self-Profiling API. + */ +export interface ProfilerStack { + /** + * Index in the trace.stacks array. + */ + readonly parentId?: number + /** + * Index in the trace.frames array. + */ + readonly frameId: number +} +/** + * Schema of a profiler sample from the JS Self-Profiling API. + */ +export interface ProfilerSample { + /** + * High resolution time relative to the profiling session's time origin. + */ + readonly timestamp: number + /** + * Index in the trace.stacks array. + */ + readonly stackId?: number +} +/** + * Schema of timing state with both relative and absolute timestamps. + */ +export interface ClocksState { + /** + * Time relative to navigation start in milliseconds. + */ + readonly relative: number + /** + * Epoch time in milliseconds. + */ + readonly timeStamp: number +} +/** + * Schema of a long task entry recorded during profiling. + */ +export interface RumProfilerLongTaskEntry { + /** + * RUM Long Task id. + */ + readonly id?: string + /** + * Duration in ns of the long task or long animation frame. + */ + readonly duration: number + /** + * Type of the event: long task or long animation frame + */ + readonly entryType: 'longtask' | 'long-animation-frame' + startClocks: ClocksState +} +/** + * Schema of a RUM view entry recorded during profiling. + */ +export interface RumViewEntry { + startClocks: ClocksState + /** + * RUM view id. + */ + readonly viewId: string + /** + * RUM view name. + */ + readonly viewName?: string +} diff --git a/rum-events-format b/rum-events-format index d555ad8888..bc42a89466 160000 --- a/rum-events-format +++ b/rum-events-format @@ -1 +1 @@ -Subproject commit d555ad8888ebdabf2b453d3df439c42373d5e999 +Subproject commit bc42a894668a29818b2ebb35b3852dbf545d151b diff --git a/scripts/generate-schema-types.ts b/scripts/generate-schema-types.ts index 37ca904357..b2de112db8 100644 --- a/scripts/generate-schema-types.ts +++ b/scripts/generate-schema-types.ts @@ -29,6 +29,13 @@ async function generateRumEventsFormatTypes(schemasDirectoryPath: string): Promi 'session-replay-browser-schema.json', { options: { additionalProperties: false } } ) + + await generateTypesFromSchema( + path.join(import.meta.dirname, '../packages/rum/src/types/profiling.ts'), + schemasDirectoryPath, + 'profiling-browser-schema.json', + { options: { additionalProperties: false } } + ) } async function generateRemoteConfigurationTypes(schemasDirectoryPath: string): Promise {