Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Duration, MatchOption, RelativeTime, ServerDuration, TaskQueue, TimeStamp } from '@datadog/browser-core'
import { createTaskQueue, display, elapsed, RequestType, ResourceType, toServerDuration } from '@datadog/browser-core'
import type { MockTelemetry } from '@datadog/browser-core/test'
import { registerCleanupTask, replaceMockable, startMockTelemetry } from '@datadog/browser-core/test'
import type { Clock, MockTelemetry } from '@datadog/browser-core/test'
import { mockClock, registerCleanupTask, replaceMockable, startMockTelemetry } from '@datadog/browser-core/test'
import {
collectAndValidateRawRumEvents,
createPerformanceEntry,
Expand All @@ -22,7 +22,7 @@ import { LifeCycle, LifeCycleEventType } from '../lifeCycle'
import type { RequestCompleteEvent } from '../requestCollection'
import { getDocumentTraceId } from '../tracing/getDocumentTraceId'
import { createSpanIdentifier, createTraceIdentifier } from '../tracing/identifier'
import { startResourceCollection } from './resourceCollection'
import { REQUEST_MATCHING_DELAY, startResourceCollection } from './resourceCollection'

function buildMatchHeadersForAllUrls(headerNames: MatchOption[]): MatchHeader[] {
return headerNames.map((name) => ({ name }))
Expand All @@ -36,6 +36,7 @@ describe('resourceCollection', () => {
let notifyPerformanceEntries: (entries: RumPerformanceEntry[]) => void
let rawRumEvents: Array<RawRumEventCollectedData<RawRumEvent>> = []
let taskQueuePushSpy: jasmine.Spy<TaskQueue['push']>
let clock: Clock

function setupResourceCollection(partialConfig: Partial<RumConfiguration> = { trackResources: true }) {
const { triggerOnDomLoaded } = mockDocumentReadyState()
Expand All @@ -55,6 +56,7 @@ describe('resourceCollection', () => {
}

beforeEach(() => {
clock = mockClock()
;({ notifyPerformanceEntries } = mockPerformanceObserver())
})

Expand Down Expand Up @@ -1277,6 +1279,9 @@ describe('resourceCollection', () => {
})

function runTasks() {
// Request-type entries are queued through a `setTimeout(…, REQUEST_MATCHING_DELAY)` before
// they reach the task queue β€” advance past it so they get pushed.
clock.tick(REQUEST_MATCHING_DELAY)
taskQueuePushSpy.calls.allArgs().forEach(([task]) => {
task()
})
Expand Down
24 changes: 19 additions & 5 deletions packages/rum-core/src/domain/resource/resourceCollection.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Duration } from '@datadog/browser-core'
import {
combine,
generateUUID,
Expand All @@ -11,6 +12,7 @@ import {
display,
addTelemetryDebug,
RequestType,
setTimeout,
} from '@datadog/browser-core'
import type { MatchHeader, RumConfiguration } from '../configuration'
import { RumPerformanceEntryType, createPerformanceObservable } from '../../browser/performanceObservable'
Expand All @@ -36,13 +38,16 @@ import {
sanitizeIfLongDataUrl,
} from './resourceUtils'
import type { ResourceLikeEntry } from './resourceUtils'
import type { RequestRegistry } from './requestRegistry'
import { createRequestRegistry } from './requestRegistry'
import type { GraphQlMetadata } from './graphql'
import { extractGraphQlMetadata, findGraphQlConfiguration } from './graphql'
import type { ManualResourceData } from './trackManualResources'
import { trackManualResources } from './trackManualResources'

// Delay before looking up the request matching a request-type performance entry. See the call
// site in `startResourceCollection` for the rationale.
export const REQUEST_MATCHING_DELAY = 50 as Duration

export function startResourceCollection(lifeCycle: LifeCycle, configuration: RumConfiguration) {
const taskQueue = mockable(createTaskQueue)()
const requestRegistry = createRequestRegistry(lifeCycle)
Expand All @@ -52,12 +57,22 @@ export function startResourceCollection(lifeCycle: LifeCycle, configuration: Rum
buffered: true,
}).subscribe((entries) => {
for (const entry of entries) {
handleResource(() => assembleResource(entry, requestRegistry, configuration))
if (isResourceEntryRequestType(entry)) {
// The PerformanceObserver callback can fire before the fetch's resolve microtask runs
// (notably on Firefox), so the matching REQUEST_COMPLETED isn't in the registry yet.
// Defer the lookup to give the request time to land before we look it up.
setTimeout(
() => handleResource(() => assembleResource(entry, requestRegistry.getMatchingRequest(entry), configuration)),
REQUEST_MATCHING_DELAY
Comment on lines +64 to +66
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Cancel delayed matching timers on stop

For request-type entries this timer can still fire after startResourceCollection().stop() has been called; taskQueue.stop() only clears work already queued, so the delayed callback will call handleResource() later and push a new task that can emit a stale resource event after the collection was stopped, especially during SDK teardown/re-init with in-flight fetch/XHR entries. Store and clear these timeout ids, or guard the callback with a stopped flag.

Useful? React with πŸ‘Β / πŸ‘Ž.

)
} else {
handleResource(() => assembleResource(entry, undefined, configuration))
}
}
})

const { stop: stopRunOnReadyState } = runOnReadyState(configuration, 'interactive', () => {
handleResource(() => assembleResource(getNavigationEntry(), requestRegistry, configuration))
handleResource(() => assembleResource(getNavigationEntry(), undefined, configuration))
})

function handleResource(computeRawEvent: () => RawRumEventCollectedData<RawRumResourceEvent> | undefined) {
Expand Down Expand Up @@ -86,10 +101,9 @@ export function startResourceCollection(lifeCycle: LifeCycle, configuration: Rum

function assembleResource(
entry: ResourceLikeEntry,
requestRegistry: RequestRegistry,
request: RequestCompleteEvent | undefined,
configuration: RumConfiguration
): RawRumEventCollectedData<RawRumResourceEvent> | undefined {
const request = isResourceEntryRequestType(entry) ? requestRegistry.getMatchingRequest(entry) : undefined
const tracingInfo = request
? computeRequestTracingInfo(request, configuration)
: computeResourceEntryTracingInfo(entry, configuration)
Expand Down
Loading