Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨[RUMF-737] migrate resource to v2 format (experimental) #584

Merged
merged 7 commits into from
Oct 23, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Expand Up @@ -2,6 +2,7 @@ import { RequestType, ResourceType } from '@datadog/browser-core'
import { setup, TestSetupBuilder } from '../../../../test/specHelper'
import { RumPerformanceResourceTiming } from '../../../browser/performanceCollection'
import { RumEventCategory, RumResourceEvent } from '../../../types'
import { RumEventType, RumResourceEventV2 } from '../../../typesV2'
import { LifeCycleEventType } from '../../lifeCycle'
import { RequestCompleteEvent } from '../../requestCollection'
import { RumSession } from '../../rumSession'
Expand Down Expand Up @@ -208,6 +209,198 @@ describe('resourceCollection', () => {
})
})

describe('resourceCollection V2', () => {
let setupBuilder: TestSetupBuilder

afterEach(() => {
setupBuilder.cleanup()
})
bcaudan marked this conversation as resolved.
Show resolved Hide resolved

describe('when resource tracking is enabled', () => {
beforeEach(() => {
setupBuilder = setup()
.withSession({
getId: () => '1234',
isTracked: () => true,
isTrackedWithResource: () => true,
})
.beforeBuild((lifeCycle, configuration, session: RumSession) => {
configuration.isEnabled = () => true
startResourceCollection(lifeCycle, configuration, session)
})
})

it('should create resource from performance entry', () => {
const { lifeCycle, rawRumEventsV2 } = setupBuilder.build()
lifeCycle.notify(
LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED,
createResourceEntry({
duration: 100,
name: 'https://resource.com/valid',
startTime: 1234,
})
)

expect(rawRumEventsV2[0].startTime).toBe(1234)
expect(rawRumEventsV2[0].rawRumEvent).toEqual({
date: (jasmine.any(Number) as unknown) as number,
resource: {
download: jasmine.anything(),
duration: 100 * 1e6,
firstByte: jasmine.anything(),
redirect: jasmine.anything(),
size: undefined,
type: ResourceType.OTHER,
url: 'https://resource.com/valid',
},
type: RumEventType.RESOURCE,
})
})

it('should create resource from completed request', () => {
const { lifeCycle, rawRumEventsV2 } = setupBuilder.build()
lifeCycle.notify(
LifeCycleEventType.REQUEST_COMPLETED,
createCompletedRequest({
duration: 100,
method: 'GET',
startTime: 1234,
status: 200,
type: RequestType.XHR,
url: 'https://resource.com/valid',
})
)

expect(rawRumEventsV2[0].startTime).toBe(1234)
expect(rawRumEventsV2[0].rawRumEvent).toEqual({
date: (jasmine.any(Number) as unknown) as number,
resource: {
duration: 100 * 1e6,
method: 'GET',
statusCode: 200,
type: ResourceType.XHR,
url: 'https://resource.com/valid',
},
type: RumEventType.RESOURCE,
})
})
})

describe('when resource tracking is disabled', () => {
beforeEach(() => {
setupBuilder = setup()
.withSession({
getId: () => '1234',
isTracked: () => true,
isTrackedWithResource: () => false,
})
.beforeBuild((lifeCycle, configuration, session: RumSession) => {
configuration.isEnabled = () => true
startResourceCollection(lifeCycle, configuration, session)
})
})

it('should not create resource from performance entry', () => {
const { lifeCycle, rawRumEventsV2 } = setupBuilder.build()
lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, createResourceEntry())

expect(rawRumEventsV2.length).toBe(0)
})

it('should not create resource from completed request', () => {
const { lifeCycle, rawRumEventsV2 } = setupBuilder.build()
lifeCycle.notify(LifeCycleEventType.REQUEST_COMPLETED, createCompletedRequest())

expect(rawRumEventsV2.length).toBe(0)
})
})

describe('when resource tracking change', () => {
let isTrackedWithResource = true

beforeEach(() => {
setupBuilder = setup()
.withSession({
getId: () => '1234',
isTracked: () => true,
isTrackedWithResource: () => isTrackedWithResource,
})
.beforeBuild((lifeCycle, configuration, session: RumSession) => {
configuration.isEnabled = () => true
startResourceCollection(lifeCycle, configuration, session)
})
})

it('should enable/disable resource creation from performance entry', () => {
const { lifeCycle, rawRumEventsV2 } = setupBuilder.build()

lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, createResourceEntry())
expect(rawRumEventsV2.length).toBe(1)

isTrackedWithResource = false
lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, createResourceEntry())
expect(rawRumEventsV2.length).toBe(1)

isTrackedWithResource = true
lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, createResourceEntry())
expect(rawRumEventsV2.length).toBe(2)
})

it('should enable/disable resource creation from completed request', () => {
const { lifeCycle, rawRumEventsV2 } = setupBuilder.build()

lifeCycle.notify(LifeCycleEventType.REQUEST_COMPLETED, createCompletedRequest())
expect(rawRumEventsV2.length).toBe(1)

isTrackedWithResource = false
lifeCycle.notify(LifeCycleEventType.REQUEST_COMPLETED, createCompletedRequest())
expect(rawRumEventsV2.length).toBe(1)

isTrackedWithResource = true
lifeCycle.notify(LifeCycleEventType.REQUEST_COMPLETED, createCompletedRequest())
expect(rawRumEventsV2.length).toBe(2)
})
})

describe('tracing info', () => {
beforeEach(() => {
setupBuilder = setup().beforeBuild((lifeCycle, configuration, session: RumSession) => {
configuration.isEnabled = () => true
startResourceCollection(lifeCycle, configuration, session)
})
})
it('should be processed from traced initial document', () => {
const { lifeCycle, rawRumEventsV2 } = setupBuilder.build()
lifeCycle.notify(
LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED,
createResourceEntry({
traceId: 'xxx',
})
)

const traceInfo = (rawRumEventsV2[0].rawRumEvent as RumResourceEventV2)._dd!
expect(traceInfo).toBeDefined()
expect(traceInfo.traceId).toBe('xxx')
})

it('should be processed from completed request', () => {
const { lifeCycle, rawRumEventsV2 } = setupBuilder.build()
lifeCycle.notify(
LifeCycleEventType.REQUEST_COMPLETED,
createCompletedRequest({
spanId: new TraceIdentifier(),
traceId: new TraceIdentifier(),
})
)

const traceInfo = (rawRumEventsV2[0].rawRumEvent as RumResourceEventV2)._dd!
expect(traceInfo).toBeDefined()
expect(traceInfo.traceId).toBeDefined()
expect(traceInfo.spanId).toBeDefined()
})
})
})

function createResourceEntry(details?: Partial<RumPerformanceResourceTiming>): RumPerformanceResourceTiming {
const entry: Partial<RumPerformanceResourceTiming> = {
duration: 100,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '@datadog/browser-core'
import { RumPerformanceResourceTiming } from '../../../browser/performanceCollection'
import { RumEventCategory, RumResourceEvent } from '../../../types'
import { RumEventType, RumResourceEventV2 } from '../../../typesV2'
import { LifeCycle, LifeCycleEventType } from '../../lifeCycle'
import { RequestCompleteEvent } from '../../requestCollection'
import { RumSession } from '../../rumSession'
Expand All @@ -24,14 +25,18 @@ import {
export function startResourceCollection(lifeCycle: LifeCycle, configuration: Configuration, session: RumSession) {
lifeCycle.subscribe(LifeCycleEventType.REQUEST_COMPLETED, (request: RequestCompleteEvent) => {
if (session.isTrackedWithResource()) {
lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, processRequest(request))
configuration.isEnabled('v2_format')
? lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_V2_COLLECTED, processRequestV2(request))
: lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, processRequest(request))
lifeCycle.notify(LifeCycleEventType.RESOURCE_ADDED_TO_BATCH)
Copy link
Member

Choose a reason for hiding this comment

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

NIT for another PR: RESOURCE_ADDED_TO_BATCH could probably be replaced with a RAW_RUM_EVENT_COLLECTED usage + test on evt.category

Copy link
Contributor Author

@bcaudan bcaudan Oct 23, 2020

Choose a reason for hiding this comment

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

yep, I've listed some potential cleanup here

}
})

lifeCycle.subscribe(LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, (entry) => {
if (session.isTrackedWithResource() && entry.entryType === 'resource' && !isRequestKind(entry)) {
lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, processResourceEntry(entry))
configuration.isEnabled('v2_format')
? lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_V2_COLLECTED, processResourceEntryV2(entry))
: lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, processResourceEntry(entry))
lifeCycle.notify(LifeCycleEventType.RESOURCE_ADDED_TO_BATCH)
}
})
Expand Down Expand Up @@ -68,6 +73,33 @@ function processRequest(request: RequestCompleteEvent) {
return { startTime, rawRumEvent: resourceEvent }
}

function processRequestV2(request: RequestCompleteEvent) {
const type = request.type === RequestType.XHR ? ResourceType.XHR : ResourceType.FETCH

const matchingTiming = matchRequestTiming(request)
const startTime = matchingTiming ? matchingTiming.startTime : request.startTime
const correspondingTimingOverrides = matchingTiming ? computePerformanceEntryMetricsV2(matchingTiming) : undefined

const tracingInfo = computeRequestTracingInfo(request)

const resourceEvent = combine(
{
date: getTimestamp(startTime),
resource: {
type,
duration: msToNs(request.duration),
method: request.method,
statusCode: request.status,
url: request.url,
},
type: RumEventType.RESOURCE,
},
tracingInfo,
correspondingTimingOverrides
)
return { startTime, rawRumEvent: resourceEvent as RumResourceEventV2 }
}

function processResourceEntry(entry: RumPerformanceResourceTiming) {
const resourceKind = computeResourceKind(entry)
const entryMetrics = computePerformanceEntryMetrics(entry)
Expand All @@ -92,6 +124,26 @@ function processResourceEntry(entry: RumPerformanceResourceTiming) {
return { startTime: entry.startTime, rawRumEvent: resourceEvent }
}

function processResourceEntryV2(entry: RumPerformanceResourceTiming) {
const type = computeResourceKind(entry)
const entryMetrics = computePerformanceEntryMetricsV2(entry)
const tracingInfo = computeEntryTracingInfo(entry)

const resourceEvent = combine(
{
date: getTimestamp(entry.startTime),
resource: {
type,
url: entry.name,
},
type: RumEventType.RESOURCE,
},
tracingInfo,
entryMetrics
)
return { startTime: entry.startTime, rawRumEvent: resourceEvent as RumResourceEventV2 }
}

function computePerformanceEntryMetrics(timing: RumPerformanceResourceTiming) {
return {
duration: computePerformanceResourceDuration(timing),
Expand All @@ -104,6 +156,16 @@ function computePerformanceEntryMetrics(timing: RumPerformanceResourceTiming) {
}
}

function computePerformanceEntryMetricsV2(timing: RumPerformanceResourceTiming) {
return {
resource: {
duration: computePerformanceResourceDuration(timing),
size: computeSize(timing),
...computePerformanceResourceDetails(timing),
},
}
}

function computeRequestTracingInfo(request: RequestCompleteEvent) {
const hasBeenTraced = request.traceId && request.spanId
if (!hasBeenTraced) {
Expand Down
4 changes: 2 additions & 2 deletions packages/rum/src/typesV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ export interface RumResourceEventV2 {
dns?: PerformanceResourceDetailsElement
connect?: PerformanceResourceDetailsElement
ssl?: PerformanceResourceDetailsElement
firstByte: PerformanceResourceDetailsElement
download: PerformanceResourceDetailsElement
firstByte?: PerformanceResourceDetailsElement
download?: PerformanceResourceDetailsElement
}
_dd?: {
traceId: string
Expand Down