diff --git a/packages/rum-core/src/domain/resource/resourceCollection.spec.ts b/packages/rum-core/src/domain/resource/resourceCollection.spec.ts index 0ce125cfbd..12e024f278 100644 --- a/packages/rum-core/src/domain/resource/resourceCollection.spec.ts +++ b/packages/rum-core/src/domain/resource/resourceCollection.spec.ts @@ -2,9 +2,11 @@ import type { Duration, RelativeTime, ServerDuration, TaskQueue, TimeStamp } fro import { createTaskQueue, noop, RequestType, ResourceType } from '@datadog/browser-core' import { registerCleanupTask } from '@datadog/browser-core/test' import type { RumFetchResourceEventDomainContext, RumXhrResourceEventDomainContext } from '../../domainContext.types' +import type { GlobalPerformanceBufferMock } from '../../../test' import { collectAndValidateRawRumEvents, createPerformanceEntry, + mockGlobalPerformanceBuffer, mockPageStateHistory, mockPerformanceObserver, mockRumConfiguration, @@ -29,6 +31,7 @@ describe('resourceCollection', () => { let lifeCycle: LifeCycle let wasInPageStateDuringPeriodSpy: jasmine.Spy let notifyPerformanceEntries: (entries: RumPerformanceEntry[]) => void + let globalPerformanceObjectMock: GlobalPerformanceBufferMock let rawRumEvents: Array> = [] let taskQueuePushSpy: jasmine.Spy @@ -54,6 +57,12 @@ describe('resourceCollection', () => { beforeEach(() => { ;({ notifyPerformanceEntries } = mockPerformanceObserver()) + globalPerformanceObjectMock = mockGlobalPerformanceBuffer() + globalPerformanceObjectMock.addPerformanceEntry( + createPerformanceEntry(RumPerformanceEntryType.RESOURCE, { + responseStart: 250 as RelativeTime, + }) + ) wasInPageStateDuringPeriodSpy = spyOn(pageStateHistory, 'wasInPageStateDuringPeriod') }) @@ -105,18 +114,12 @@ describe('resourceCollection', () => { lifeCycle.notify( LifeCycleEventType.REQUEST_COMPLETED, createCompletedRequest({ - duration: 100 as Duration, - method: 'GET', - startClocks: { relative: 1234 as RelativeTime, timeStamp: 123456789 as TimeStamp }, - status: 200, - type: RequestType.XHR, - url: 'https://resource.com/valid', xhr, isAborted: false, }) ) - expect(rawRumEvents[0].startTime).toBe(1234 as RelativeTime) + expect(rawRumEvents[0].startTime).toBe(200 as RelativeTime) expect(rawRumEvents[0].rawRumEvent).toEqual({ date: jasmine.any(Number), resource: { @@ -124,10 +127,17 @@ describe('resourceCollection', () => { duration: (100 * 1e6) as ServerDuration, method: 'GET', status_code: 200, - delivery_type: undefined, - protocol: undefined, + delivery_type: 'cache', + protocol: 'HTTP/1.0', type: ResourceType.XHR, url: 'https://resource.com/valid', + render_blocking_status: 'non-blocking', + size: 1000, + encoded_body_size: 500, + decoded_body_size: 1000, + transfer_size: 500, + download: Object({ duration: 50_000_000, start: 50_000_000 }), + first_byte: Object({ duration: 50_000_000, start: 0 }), }, type: RumEventType.RESOURCE, _dd: { @@ -136,7 +146,7 @@ describe('resourceCollection', () => { }) expect(rawRumEvents[0].domainContext).toEqual({ xhr, - performanceEntry: undefined, + performanceEntry: jasmine.any(Object), response: undefined, requestInput: undefined, requestInit: undefined, @@ -197,7 +207,7 @@ describe('resourceCollection', () => { }) }) - it('should not have a duration if a frozen state happens during the request and no performance entry matches', () => { + it('should not have any duration properties if a frozen state happens during the request and no performance entry matches', () => { setupResourceCollection() const mockXHR = createCompletedRequest() @@ -205,8 +215,28 @@ describe('resourceCollection', () => { lifeCycle.notify(LifeCycleEventType.REQUEST_COMPLETED, mockXHR) - const rawRumResourceEventFetch = rawRumEvents[0].rawRumEvent as RawRumResourceEvent - expect(rawRumResourceEventFetch.resource.duration).toBeUndefined() + expect(rawRumEvents[0].rawRumEvent).toEqual({ + date: jasmine.any(Number), + resource: { + id: jasmine.any(String), + duration: undefined, + method: 'GET', + status_code: 200, + delivery_type: 'cache', + protocol: 'HTTP/1.0', + type: ResourceType.XHR, + url: 'https://resource.com/valid', + render_blocking_status: 'non-blocking', + size: 1000, + encoded_body_size: 500, + decoded_body_size: 1000, + transfer_size: 500, + }, + type: RumEventType.RESOURCE, + _dd: { + discarded: false, + }, + }) }) it('should create resource from completed fetch request', () => { @@ -215,10 +245,6 @@ describe('resourceCollection', () => { lifeCycle.notify( LifeCycleEventType.REQUEST_COMPLETED, createCompletedRequest({ - duration: 100 as Duration, - method: 'GET', - startClocks: { relative: 1234 as RelativeTime, timeStamp: 123456789 as TimeStamp }, - status: 200, type: RequestType.FETCH, url: 'https://resource.com/valid', response, @@ -228,7 +254,7 @@ describe('resourceCollection', () => { }) ) - expect(rawRumEvents[0].startTime).toBe(1234 as RelativeTime) + expect(rawRumEvents[0].startTime).toBe(200 as RelativeTime) expect(rawRumEvents[0].rawRumEvent).toEqual({ date: jasmine.any(Number), resource: { @@ -236,10 +262,17 @@ describe('resourceCollection', () => { duration: (100 * 1e6) as ServerDuration, method: 'GET', status_code: 200, - delivery_type: undefined, - protocol: undefined, + delivery_type: 'cache', + protocol: 'HTTP/1.0', type: ResourceType.FETCH, url: 'https://resource.com/valid', + render_blocking_status: 'non-blocking', + size: 1000, + encoded_body_size: 500, + decoded_body_size: 1000, + transfer_size: 500, + download: Object({ duration: 50_000_000, start: 50_000_000 }), + first_byte: Object({ duration: 50_000_000, start: 0 }), }, type: RumEventType.RESOURCE, _dd: { @@ -247,7 +280,7 @@ describe('resourceCollection', () => { }, }) expect(rawRumEvents[0].domainContext).toEqual({ - performanceEntry: undefined, + performanceEntry: jasmine.any(Object), xhr: undefined, response, requestInput: 'https://resource.com/valid', @@ -439,7 +472,7 @@ function createCompletedRequest(details?: Partial): Reques const request: Partial = { duration: 100 as Duration, method: 'GET', - startClocks: { relative: 1234 as RelativeTime, timeStamp: 123456789 as TimeStamp }, + startClocks: { relative: 200 as RelativeTime, timeStamp: 123456789 as TimeStamp }, status: 200, type: RequestType.XHR, url: 'https://resource.com/valid', diff --git a/packages/rum-core/src/domain/resource/resourceCollection.ts b/packages/rum-core/src/domain/resource/resourceCollection.ts index 92a98f99d1..66833183b7 100644 --- a/packages/rum-core/src/domain/resource/resourceCollection.ts +++ b/packages/rum-core/src/domain/resource/resourceCollection.ts @@ -54,13 +54,13 @@ export function startResourceCollection( }).subscribe((entries) => { for (const entry of entries) { if (!isResourceEntryRequestType(entry)) { - handleResource(() => processResourceEntry(entry, configuration)) + handleResource(() => processResourceEntry(entry, configuration, pageStateHistory)) } } }) retrieveInitialDocumentResourceTimingImpl(configuration, (timing) => { - handleResource(() => processResourceEntry(timing, configuration)) + handleResource(() => processResourceEntry(timing, configuration, pageStateHistory)) }) function handleResource(computeRawEvent: () => RawRumEventCollectedData | undefined) { @@ -93,7 +93,9 @@ function processRequest( const type = request.type === RequestType.XHR ? ResourceType.XHR : ResourceType.FETCH - const correspondingTimingOverrides = matchingTiming ? computeResourceEntryMetrics(matchingTiming) : undefined + const correspondingTimingOverrides = matchingTiming + ? computeResourceEntryMetrics(matchingTiming, pageStateHistory) + : undefined const duration = computeRequestDuration(pageStateHistory, startClocks, request.duration) @@ -137,7 +139,8 @@ function processRequest( function processResourceEntry( entry: RumPerformanceResourceTiming, - configuration: RumConfiguration + configuration: RumConfiguration, + pageStateHistory: PageStateHistory ): RawRumEventCollectedData | undefined { const startClocks = relativeToClocks(entry.startTime) const tracingInfo = computeResourceEntryTracingInfo(entry, configuration) @@ -146,7 +149,7 @@ function processResourceEntry( } const type = computeResourceEntryType(entry) - const entryMetrics = computeResourceEntryMetrics(entry) + const entryMetrics = computeResourceEntryMetrics(entry, pageStateHistory) const resourceEvent = combine( { @@ -176,16 +179,26 @@ function processResourceEntry( } } -function computeResourceEntryMetrics(entry: RumPerformanceResourceTiming) { +function computeResourceEntryMetrics(entry: RumPerformanceResourceTiming, pageStateHistory: PageStateHistory) { const { renderBlockingStatus } = entry - return { + + const resourceEntryMetrics = { resource: { - duration: computeResourceEntryDuration(entry), render_blocking_status: renderBlockingStatus, ...computeResourceEntrySize(entry), - ...computeResourceEntryDetails(entry), }, } + + if (pageStateHistory.wasInPageStateDuringPeriod(PageState.FROZEN, entry.startTime, entry.duration)) { + return resourceEntryMetrics + } + + return combine(resourceEntryMetrics, { + resource: { + duration: computeResourceEntryDuration(entry), + ...computeResourceEntryDetails(entry), + }, + }) } function computeRequestTracingInfo(request: RequestCompleteEvent, configuration: RumConfiguration) {