From 7362dd0fec767b2b112423c4b88d3237db22f801 Mon Sep 17 00:00:00 2001 From: Fiona Date: Mon, 15 Jun 2026 12:20:32 +0800 Subject: [PATCH] feat(rum): add trackWebVitals config to opt out of initial view metrics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Initial view metrics (FCP/LCP/FID/loading time) are anchored to the page navigation start (`clocksOrigin()`) and only collected on the INITIAL_LOAD view. For pages that load in the background or are pre-warmed and kept hidden (e.g. a hidden Electron BrowserWindow that only renders content much later via IPC), LCP ends up measured from the original navigation and is reported as an abnormally large value. The existing safeguards (`firstHidden`, the 10-minute cap) don't help because such hidden windows don't reliably report `document.visibilityState === 'hidden'`. Add a `trackWebVitals` init option (default `true`). When set to `false`, `trackInitialViewMetrics` is not started, so no FCP/LCP/FID/loading-time is collected for that SDK instance — the field is simply absent rather than a misleading value. Like `profilingSampleRate` and `propagateTraceBaggage`, this fork-specific option is not part of the upstream telemetry schema, so it is intentionally excluded from `serializeRumConfiguration` (the generated telemetry types must not be hand-edited). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../configuration/configuration.spec.ts | 24 +++++++++++++++++++ .../src/domain/configuration/configuration.ts | 10 ++++++++ .../src/domain/view/trackViews.spec.ts | 15 ++++++++++++ .../rum-core/src/domain/view/trackViews.ts | 2 +- 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/packages/rum-core/src/domain/configuration/configuration.spec.ts b/packages/rum-core/src/domain/configuration/configuration.spec.ts index 912532c12f..e7d8ca5500 100644 --- a/packages/rum-core/src/domain/configuration/configuration.spec.ts +++ b/packages/rum-core/src/domain/configuration/configuration.spec.ts @@ -369,6 +369,28 @@ describe('validateAndBuildRumConfiguration', () => { }) }) + describe('trackWebVitals', () => { + it('defaults to true', () => { + expect(validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.trackWebVitals).toBeTrue() + }) + + it('is set to provided value', () => { + expect( + validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, trackWebVitals: true })!.trackWebVitals + ).toBeTrue() + expect( + validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, trackWebVitals: false })!.trackWebVitals + ).toBeFalse() + }) + + it('the provided value is cast to boolean', () => { + expect( + validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, trackWebVitals: 'foo' as any })! + .trackWebVitals + ).toBeTrue() + }) + }) + describe('trackResources', () => { it('defaults to true', () => { expect(validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.trackResources).toBeTrue() @@ -535,6 +557,7 @@ describe('serializeRumConfiguration', () => { trackUserInteractions: true, actionNameAttribute: 'test-id', trackViewsManually: true, + trackWebVitals: true, trackResources: true, trackLongTasks: true, remoteConfigurationId: '123', @@ -556,6 +579,7 @@ describe('serializeRumConfiguration', () => { | 'remoteConfigurationId' | 'profilingSampleRate' | 'propagateTraceBaggage' + | 'trackWebVitals' ? never : CamelToSnakeCase // By specifying the type here, we can ensure that serializeConfiguration is returning an diff --git a/packages/rum-core/src/domain/configuration/configuration.ts b/packages/rum-core/src/domain/configuration/configuration.ts index 8e25227ef5..d8cf3789a2 100644 --- a/packages/rum-core/src/domain/configuration/configuration.ts +++ b/packages/rum-core/src/domain/configuration/configuration.ts @@ -127,6 +127,14 @@ export interface RumInitConfiguration extends InitConfiguration { * Allows you to control RUM views creation. See [Override default RUM view names](https://docs.datadoghq.com/real_user_monitoring/browser/advanced_configuration/?tab=npm#override-default-rum-view-names) for further information. */ trackViewsManually?: boolean | undefined + /** + * Enables collection of Web Vitals / initial view metrics (FCP, LCP, FID, loading time) on the + * initial load view. Disable it for pages that are loaded in the background or pre-warmed (e.g. a + * hidden Electron window) where these metrics would be measured from an irrelevant navigation + * start and reported as abnormally large values. + * @default true + */ + trackWebVitals?: boolean | undefined /** * Enables collection of resource events. * @default true @@ -178,6 +186,7 @@ export interface RumConfiguration extends Configuration { startSessionReplayRecordingManually: boolean trackUserInteractions: boolean trackViewsManually: boolean + trackWebVitals: boolean trackResources: boolean trackLongTasks: boolean version?: string @@ -248,6 +257,7 @@ export function validateAndBuildRumConfiguration( compressIntakeRequests: !!initConfiguration.compressIntakeRequests, trackUserInteractions: !!(initConfiguration.trackUserInteractions ?? true), trackViewsManually: !!initConfiguration.trackViewsManually, + trackWebVitals: !!(initConfiguration.trackWebVitals ?? true), trackResources: !!(initConfiguration.trackResources ?? true), trackLongTasks: !!(initConfiguration.trackLongTasks ?? true), subdomain: initConfiguration.subdomain, diff --git a/packages/rum-core/src/domain/view/trackViews.spec.ts b/packages/rum-core/src/domain/view/trackViews.spec.ts index f5e88eb62a..3959baf390 100644 --- a/packages/rum-core/src/domain/view/trackViews.spec.ts +++ b/packages/rum-core/src/domain/view/trackViews.spec.ts @@ -447,6 +447,21 @@ describe('view metrics', () => { }) describe('initial view metrics', () => { + it('should not be collected when trackWebVitals is disabled', () => { + viewTest.stop() + viewTest = setupViewTest({ lifeCycle, partialConfig: { trackWebVitals: false } }) + + notifyPerformanceEntries([ + createPerformanceEntry(RumPerformanceEntryType.PAINT), + createPerformanceEntry(RumPerformanceEntryType.NAVIGATION), + createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT), + ]) + clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) + + const { getViewUpdate, getViewUpdateCount } = viewTest + expect(getViewUpdate(getViewUpdateCount() - 1).initialViewMetrics).toEqual({}) + }) + it('updates should be throttled', () => { const { getViewUpdateCount, getViewUpdate } = viewTest expect(getViewUpdateCount()).toEqual(1) diff --git a/packages/rum-core/src/domain/view/trackViews.ts b/packages/rum-core/src/domain/view/trackViews.ts index dd315e59dc..bf309e06db 100644 --- a/packages/rum-core/src/domain/view/trackViews.ts +++ b/packages/rum-core/src/domain/view/trackViews.ts @@ -252,7 +252,7 @@ function newView( ) const { stop: stopInitialViewMetricsTracking, initialViewMetrics } = - loadingType === ViewLoadingType.INITIAL_LOAD + loadingType === ViewLoadingType.INITIAL_LOAD && configuration.trackWebVitals ? trackInitialViewMetrics(configuration, setLoadEvent, scheduleViewUpdate) : { stop: noop, initialViewMetrics: {} as InitialViewMetrics }