Skip to content

Commit

Permalink
✨[RUM] add view resource count (#189)
Browse files Browse the repository at this point in the history
* ✨[RUM] add view resource count

* ✅[e2e] add missing expectation

* ✅[e2e cbt] fix wrong expectation

on safari, there is a resource event for the current page

* 👌[RUM] rename life cycle events
  • Loading branch information
bcaudan authored Dec 4, 2019
1 parent aa70dbd commit 63a3a83
Show file tree
Hide file tree
Showing 11 changed files with 125 additions and 61 deletions.
34 changes: 19 additions & 15 deletions packages/rum/src/lifeCycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,37 @@ import { ErrorMessage, RequestDetails } from '@browser-sdk/core'
import { UserAction } from './rum'

export enum LifeCycleEventType {
error,
performance,
userAction,
request,
renewSession,
ERROR_COLLECTED,
PERFORMANCE_ENTRY_COLLECTED,
USER_ACTION_COLLECTED,
REQUEST_COLLECTED,
SESSION_RENEWED,
RESOURCE_ADDED_TO_BATCH,
}

export class LifeCycle {
private callbacks: { [key in LifeCycleEventType]?: Array<(data: any) => void> } = {}

notify(eventType: LifeCycleEventType.error, data: ErrorMessage): void
notify(eventType: LifeCycleEventType.performance, data: PerformanceEntry): void
notify(eventType: LifeCycleEventType.request, data: RequestDetails): void
notify(eventType: LifeCycleEventType.userAction, data: UserAction): void
notify(eventType: LifeCycleEventType.renewSession): void
notify(eventType: LifeCycleEventType.ERROR_COLLECTED, data: ErrorMessage): void
notify(eventType: LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, data: PerformanceEntry): void
notify(eventType: LifeCycleEventType.REQUEST_COLLECTED, data: RequestDetails): void
notify(eventType: LifeCycleEventType.USER_ACTION_COLLECTED, data: UserAction): void
notify(eventType: LifeCycleEventType.SESSION_RENEWED | LifeCycleEventType.RESOURCE_ADDED_TO_BATCH): void
notify(eventType: LifeCycleEventType, data?: any) {
const eventCallbacks = this.callbacks[eventType]
if (eventCallbacks) {
eventCallbacks.forEach((callback) => callback(data))
}
}

subscribe(eventType: LifeCycleEventType.error, callback: (data: ErrorMessage) => void): void
subscribe(eventType: LifeCycleEventType.performance, callback: (data: PerformanceEntry) => void): void
subscribe(eventType: LifeCycleEventType.request, callback: (data: RequestDetails) => void): void
subscribe(eventType: LifeCycleEventType.userAction, callback: (data: UserAction) => void): void
subscribe(eventType: LifeCycleEventType.renewSession, callback: () => void): void
subscribe(eventType: LifeCycleEventType.ERROR_COLLECTED, callback: (data: ErrorMessage) => void): void
subscribe(eventType: LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, callback: (data: PerformanceEntry) => void): void
subscribe(eventType: LifeCycleEventType.REQUEST_COLLECTED, callback: (data: RequestDetails) => void): void
subscribe(eventType: LifeCycleEventType.USER_ACTION_COLLECTED, callback: (data: UserAction) => void): void
subscribe(
eventType: LifeCycleEventType.SESSION_RENEWED | LifeCycleEventType.RESOURCE_ADDED_TO_BATCH,
callback: () => void
): void
subscribe(eventType: LifeCycleEventType, callback: (data?: any) => void) {
const eventCallbacks = this.callbacks[eventType]
if (eventCallbacks) {
Expand Down
2 changes: 1 addition & 1 deletion packages/rum/src/performanceCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function retrieveNavigationTimingWhenLoaded(callback: (timing: PerformanceNaviga

function handlePerformanceEntries(session: RumSession, lifeCycle: LifeCycle, entries: PerformanceEntry[]) {
function notify(entry: PerformanceEntry) {
lifeCycle.notify(LifeCycleEventType.performance, entry)
lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, entry)
}

if (session.isTrackedWithResource()) {
Expand Down
6 changes: 4 additions & 2 deletions packages/rum/src/rum.entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,10 @@ datadogRum.init = monitor((userConfiguration: RumUserConfiguration) => {
const requestObservable = startRequestCollection()
startPerformanceCollection(lifeCycle, session)

errorObservable.subscribe((errorMessage) => lifeCycle.notify(LifeCycleEventType.error, errorMessage))
requestObservable.subscribe((requestDetails) => lifeCycle.notify(LifeCycleEventType.request, requestDetails))
errorObservable.subscribe((errorMessage) => lifeCycle.notify(LifeCycleEventType.ERROR_COLLECTED, errorMessage))
requestObservable.subscribe((requestDetails) =>
lifeCycle.notify(LifeCycleEventType.REQUEST_COLLECTED, requestDetails)
)

const globalApi = startRum(rumUserConfiguration.applicationId, lifeCycle, configuration, session)
lodashAssign(datadogRum, globalApi)
Expand Down
17 changes: 10 additions & 7 deletions packages/rum/src/rum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export function startRum(
globalContext[key] = value
}),
addUserAction: monitor((name: string, context?: Context) => {
lifeCycle.notify(LifeCycleEventType.userAction, { name, context })
lifeCycle.notify(LifeCycleEventType.USER_ACTION_COLLECTED, { name, context })
}),
getInternalContext: monitor(() => {
return {
Expand Down Expand Up @@ -213,7 +213,7 @@ function startRumBatch(
}

function trackErrors(lifeCycle: LifeCycle, addRumEvent: (event: RumEvent) => void) {
lifeCycle.subscribe(LifeCycleEventType.error, ({ message, context }: ErrorMessage) => {
lifeCycle.subscribe(LifeCycleEventType.ERROR_COLLECTED, ({ message, context }: ErrorMessage) => {
addRumEvent({
message,
evt: {
Expand All @@ -228,7 +228,7 @@ function trackErrors(lifeCycle: LifeCycle, addRumEvent: (event: RumEvent) => voi
}

function trackUserAction(lifeCycle: LifeCycle, addUserEvent: (event: RumUserAction) => void) {
lifeCycle.subscribe(LifeCycleEventType.userAction, ({ name, context }) => {
lifeCycle.subscribe(LifeCycleEventType.USER_ACTION_COLLECTED, ({ name, context }) => {
addUserEvent({
...context,
evt: {
Expand All @@ -245,7 +245,7 @@ export function trackRequests(
session: RumSession,
addRumEvent: (event: RumEvent) => void
) {
lifeCycle.subscribe(LifeCycleEventType.request, (requestDetails: RequestDetails) => {
lifeCycle.subscribe(LifeCycleEventType.REQUEST_COLLECTED, (requestDetails: RequestDetails) => {
if (!session.isTrackedWithResource()) {
return
}
Expand Down Expand Up @@ -276,6 +276,7 @@ export function trackRequests(
},
traceId: requestDetails.traceId,
})
lifeCycle.notify(LifeCycleEventType.RESOURCE_ADDED_TO_BATCH)
})
}

Expand All @@ -284,10 +285,10 @@ function trackPerformanceTiming(
lifeCycle: LifeCycle,
addRumEvent: (event: RumEvent) => void
) {
lifeCycle.subscribe(LifeCycleEventType.performance, (entry) => {
lifeCycle.subscribe(LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, (entry) => {
switch (entry.entryType) {
case 'resource':
handleResourceEntry(configuration, entry as PerformanceResourceTiming, addRumEvent)
handleResourceEntry(configuration, entry as PerformanceResourceTiming, addRumEvent, lifeCycle)
break
case 'longtask':
handleLongTaskEntry(entry as PerformanceLongTaskTiming, addRumEvent)
Expand All @@ -301,7 +302,8 @@ function trackPerformanceTiming(
export function handleResourceEntry(
configuration: Configuration,
entry: PerformanceResourceTiming,
addRumEvent: (event: RumEvent) => void
addRumEvent: (event: RumEvent) => void,
lifeCycle: LifeCycle
) {
if (!isValidResource(entry.name, configuration)) {
return
Expand All @@ -326,6 +328,7 @@ export function handleResourceEntry(
kind: resourceKind,
},
})
lifeCycle.notify(LifeCycleEventType.RESOURCE_ADDED_TO_BATCH)
}

export function handleLongTaskEntry(entry: PerformanceLongTaskTiming, addRumEvent: (event: RumEvent) => void) {
Expand Down
2 changes: 1 addition & 1 deletion packages/rum/src/rumSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function startRumSession(configuration: Configuration, lifeCycle: LifeCyc
const session = startSessionManagement(RUM_COOKIE_NAME, (rawType) => computeSessionState(configuration, rawType))

session.renewObservable.subscribe(() => {
lifeCycle.notify(LifeCycleEventType.renewSession)
lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED)
})

return {
Expand Down
18 changes: 12 additions & 6 deletions packages/rum/src/viewTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ export interface ViewMeasures {
domContentLoaded?: number
domComplete?: number
loadEventEnd?: number
userActionCount: number
errorCount: number
resourceCount: number
longTaskCount: number
userActionCount: number
}

export let viewId: string
Expand Down Expand Up @@ -49,6 +50,7 @@ function newView(location: Location, addRumEvent: (event: RumEvent) => void) {
viewMeasures = {
errorCount: 0,
longTaskCount: 0,
resourceCount: 0,
userActionCount: 0,
}
viewLocation = { ...location }
Expand Down Expand Up @@ -104,7 +106,7 @@ function areDifferentViews(previous: Location, current: Location) {
}

function trackMeasures(lifeCycle: LifeCycle, scheduleViewUpdate: () => void) {
lifeCycle.subscribe(LifeCycleEventType.performance, (entry) => {
lifeCycle.subscribe(LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, (entry) => {
if (entry.entryType === 'navigation') {
const navigationEntry = entry as PerformanceNavigationTiming
viewMeasures = {
Expand All @@ -124,24 +126,28 @@ function trackMeasures(lifeCycle: LifeCycle, scheduleViewUpdate: () => void) {
scheduleViewUpdate()
}
})
lifeCycle.subscribe(LifeCycleEventType.error, () => {
lifeCycle.subscribe(LifeCycleEventType.ERROR_COLLECTED, () => {
viewMeasures.errorCount += 1
scheduleViewUpdate()
})
lifeCycle.subscribe(LifeCycleEventType.userAction, () => {
lifeCycle.subscribe(LifeCycleEventType.USER_ACTION_COLLECTED, () => {
viewMeasures.userActionCount += 1
scheduleViewUpdate()
})
lifeCycle.subscribe(LifeCycleEventType.performance, (entry) => {
lifeCycle.subscribe(LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, (entry) => {
if (entry.entryType === 'longtask') {
viewMeasures.longTaskCount += 1
scheduleViewUpdate()
}
})
lifeCycle.subscribe(LifeCycleEventType.RESOURCE_ADDED_TO_BATCH, () => {
viewMeasures.resourceCount += 1
scheduleViewUpdate()
})
}

function trackRenewSession(location: Location, lifeCycle: LifeCycle, addRumEvent: (event: RumEvent) => void) {
lifeCycle.subscribe(LifeCycleEventType.renewSession, () => {
lifeCycle.subscribe(LifeCycleEventType.SESSION_RENEWED, () => {
updateView(addRumEvent)
newView(location, addRumEvent)
})
Expand Down
53 changes: 34 additions & 19 deletions packages/rum/test/rum.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,12 @@ describe('rum handle performance entry', () => {
expectEntryToBeAdded: boolean
}) => {
it(description, () => {
handleResourceEntry(configuration as Configuration, entry as PerformanceResourceTiming, addRumEvent)
handleResourceEntry(
configuration as Configuration,
entry as PerformanceResourceTiming,
addRumEvent,
new LifeCycle()
)
const entryAdded = (addRumEvent as jasmine.Spy).calls.all().length !== 0
expect(entryAdded).toEqual(expectEntryToBeAdded)
})
Expand Down Expand Up @@ -115,7 +120,12 @@ describe('rum handle performance entry', () => {
it(`should compute resource kind: ${description}`, () => {
const entry: Partial<PerformanceResourceTiming> = { initiatorType, name: url, entryType: 'resource' }

handleResourceEntry(configuration as Configuration, entry as PerformanceResourceTiming, addRumEvent)
handleResourceEntry(
configuration as Configuration,
entry as PerformanceResourceTiming,
addRumEvent,
new LifeCycle()
)
const resourceEvent = getEntry(addRumEvent, 0) as RumResourceEvent
expect(resourceEvent.resource.kind).toEqual(expected)
})
Expand All @@ -132,7 +142,12 @@ describe('rum handle performance entry', () => {
responseStart: 25,
}

handleResourceEntry(configuration as Configuration, entry as PerformanceResourceTiming, addRumEvent)
handleResourceEntry(
configuration as Configuration,
entry as PerformanceResourceTiming,
addRumEvent,
new LifeCycle()
)
const resourceEvent = getEntry(addRumEvent, 0) as RumResourceEvent
expect(resourceEvent.http.performance!.connect.duration).toEqual(7 * 1e6)
expect(resourceEvent.http.performance!.download.duration).toEqual(75 * 1e6)
Expand Down Expand Up @@ -175,9 +190,9 @@ describe('rum session', () => {
server.requests = []

stubBuilder.fakeEntry(FAKE_RESOURCE as PerformanceEntry, 'resource')
lifeCycle.notify(LifeCycleEventType.error, FAKE_ERROR as ErrorMessage)
lifeCycle.notify(LifeCycleEventType.request, FAKE_REQUEST as RequestDetails)
lifeCycle.notify(LifeCycleEventType.userAction, FAKE_USER_ACTION)
lifeCycle.notify(LifeCycleEventType.ERROR_COLLECTED, FAKE_ERROR as ErrorMessage)
lifeCycle.notify(LifeCycleEventType.REQUEST_COLLECTED, FAKE_REQUEST as RequestDetails)
lifeCycle.notify(LifeCycleEventType.USER_ACTION_COLLECTED, FAKE_USER_ACTION)

expect(server.requests.length).toEqual(4)
})
Expand All @@ -194,10 +209,10 @@ describe('rum session', () => {
server.requests = []

stubBuilder.fakeEntry(FAKE_RESOURCE as PerformanceEntry, 'resource')
lifeCycle.notify(LifeCycleEventType.request, FAKE_REQUEST as RequestDetails)
lifeCycle.notify(LifeCycleEventType.REQUEST_COLLECTED, FAKE_REQUEST as RequestDetails)
expect(server.requests.length).toEqual(0)

lifeCycle.notify(LifeCycleEventType.error, FAKE_ERROR as ErrorMessage)
lifeCycle.notify(LifeCycleEventType.ERROR_COLLECTED, FAKE_ERROR as ErrorMessage)
expect(server.requests.length).toEqual(1)
})

Expand All @@ -213,9 +228,9 @@ describe('rum session', () => {
server.requests = []

stubBuilder.fakeEntry(FAKE_RESOURCE as PerformanceEntry, 'resource')
lifeCycle.notify(LifeCycleEventType.request, FAKE_REQUEST as RequestDetails)
lifeCycle.notify(LifeCycleEventType.error, FAKE_ERROR as ErrorMessage)
lifeCycle.notify(LifeCycleEventType.userAction, FAKE_USER_ACTION)
lifeCycle.notify(LifeCycleEventType.REQUEST_COLLECTED, FAKE_REQUEST as RequestDetails)
lifeCycle.notify(LifeCycleEventType.ERROR_COLLECTED, FAKE_ERROR as ErrorMessage)
lifeCycle.notify(LifeCycleEventType.USER_ACTION_COLLECTED, FAKE_USER_ACTION)

expect(server.requests.length).toEqual(0)
})
Expand Down Expand Up @@ -256,15 +271,15 @@ describe('rum session', () => {
startPerformanceCollection(lifeCycle, session)
server.requests = []

lifeCycle.notify(LifeCycleEventType.request, FAKE_REQUEST as RequestDetails)
lifeCycle.notify(LifeCycleEventType.REQUEST_COLLECTED, FAKE_REQUEST as RequestDetails)
expect(server.requests.length).toEqual(1)

isTrackedWithResource = false
lifeCycle.notify(LifeCycleEventType.request, FAKE_REQUEST as RequestDetails)
lifeCycle.notify(LifeCycleEventType.REQUEST_COLLECTED, FAKE_REQUEST as RequestDetails)
expect(server.requests.length).toEqual(1)

isTrackedWithResource = true
lifeCycle.notify(LifeCycleEventType.request, FAKE_REQUEST as RequestDetails)
lifeCycle.notify(LifeCycleEventType.REQUEST_COLLECTED, FAKE_REQUEST as RequestDetails)
expect(server.requests.length).toEqual(2)
})
})
Expand Down Expand Up @@ -325,16 +340,16 @@ describe('rum global context', () => {

it('should be added to the request', () => {
RUM.setRumGlobalContext({ bar: 'foo' })
lifeCycle.notify(LifeCycleEventType.error, FAKE_ERROR as ErrorMessage)
lifeCycle.notify(LifeCycleEventType.ERROR_COLLECTED, FAKE_ERROR as ErrorMessage)

expect((getRumMessage(server, 0) as any).bar).toEqual('foo')
})

it('should be updatable', () => {
RUM.setRumGlobalContext({ bar: 'foo' })
lifeCycle.notify(LifeCycleEventType.error, FAKE_ERROR as ErrorMessage)
lifeCycle.notify(LifeCycleEventType.ERROR_COLLECTED, FAKE_ERROR as ErrorMessage)
RUM.setRumGlobalContext({ foo: 'bar' })
lifeCycle.notify(LifeCycleEventType.error, FAKE_ERROR as ErrorMessage)
lifeCycle.notify(LifeCycleEventType.ERROR_COLLECTED, FAKE_ERROR as ErrorMessage)

expect((getRumMessage(server, 0) as any).bar).toEqual('foo')
expect((getRumMessage(server, 1) as any).foo).toEqual('bar')
Expand All @@ -343,7 +358,7 @@ describe('rum global context', () => {

it('should not be automatically snake cased', () => {
RUM.setRumGlobalContext({ fooBar: 'foo' })
lifeCycle.notify(LifeCycleEventType.error, FAKE_ERROR as ErrorMessage)
lifeCycle.notify(LifeCycleEventType.ERROR_COLLECTED, FAKE_ERROR as ErrorMessage)

expect((getRumMessage(server, 0) as any).fooBar).toEqual('foo')
})
Expand All @@ -367,7 +382,7 @@ describe('rum user action', () => {
})

it('should not be automatically snake cased', () => {
lifeCycle.notify(LifeCycleEventType.userAction, { name: 'hello', context: { fooBar: 'foo' } })
lifeCycle.notify(LifeCycleEventType.USER_ACTION_COLLECTED, { name: 'hello', context: { fooBar: 'foo' } })

expect((getRumMessage(server, 0) as any).fooBar).toEqual('foo')
})
Expand Down
2 changes: 1 addition & 1 deletion packages/rum/test/rumSession.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('rum session', () => {
jasmine.clock().mockDate(new Date())
renewSessionSpy = jasmine.createSpy('renewSessionSpy')
lifeCycle = new LifeCycle()
lifeCycle.subscribe(LifeCycleEventType.renewSession, renewSessionSpy)
lifeCycle.subscribe(LifeCycleEventType.SESSION_RENEWED, renewSessionSpy)
})

afterEach(() => {
Expand Down
Loading

0 comments on commit 63a3a83

Please sign in to comment.