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-373] Add View load duration and load type #388

Merged
merged 48 commits into from
May 20, 2020
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
d5e20f7
:sparkles: add page load UA
mquentin Apr 21, 2020
3f0b7fe
rm inputEventCounts
mquentin Apr 21, 2020
a24903d
:sparkles: fix duplicated UserActionType.LOAD_VIEW sent on update
mquentin Apr 21, 2020
f8c40a3
:sparkles: clean currentUserAction = undefined process
mquentin Apr 21, 2020
d8d12a3
Merge branch 'maxime.quentin/RUMF-384SDKcollectPageLoad' into maxime.…
mquentin Apr 29, 2020
f99052e
add view loading state in the UA lifecycle
mquentin Apr 29, 2020
6eeb910
cancel current UA in case of load view
mquentin Apr 29, 2020
4ade8fd
:sparkles: collect loadDuration
mquentin Apr 30, 2020
8c28472
:sparkles: add view load type
mquentin Apr 30, 2020
3dff2fd
:white_check_mark: add soft, hard load tests
mquentin May 4, 2020
52d880b
:white_check_mark: add load duration View update subscription
mquentin May 4, 2020
2278761
rename load types
mquentin May 4, 2020
53d468d
:white_check_mark: add UA vs view load tests
mquentin May 4, 2020
a843204
Merge branch 'master' into maxime.quentin/SDKviewAddLoadDurationAndLo…
mquentin May 4, 2020
64c39ef
:ok_hand: v1
mquentin May 5, 2020
86a9b74
:truck: create the new trackPageActivities module and it generic wait…
mquentin May 5, 2020
39146fa
:ok_hand: v2
mquentin May 5, 2020
6b8d47b
:pencil: about UA lifecycle
mquentin May 5, 2020
8a6994d
:white_check_mark: add view id checks
mquentin May 5, 2020
bfa22eb
:white_check_mark: add test when missing id
mquentin May 5, 2020
c1162c4
:sparkles: Collect new UA when the page is loading
mquentin May 6, 2020
f49c377
rename load types
mquentin May 6, 2020
35e4ed5
:ok_hand: add trackPageActivities.spec.ts test file
mquentin May 7, 2020
00d5f00
:ok_hand: add stop processes to newUserAction and newViewLoading
mquentin May 11, 2020
12e185c
:ok_hand: split load waitPageactivity and UA waitpageactivity
mquentin May 11, 2020
da8801b
:ok_hand: move lifecycle doc
mquentin May 11, 2020
02f1c74
:ok_hand: rm unused exports
mquentin May 11, 2020
d8779f0
:ok_hand: refactor stopCurrentUserAction process
mquentin May 11, 2020
8aef86e
:ok_hand: move stopCurrentUserAction() at the newView Level
mquentin May 11, 2020
34f9ee8
:ok_hand: add newView to the test scenario
mquentin May 11, 2020
946104d
:ok_hand: fix lint
mquentin May 12, 2020
e53c670
:ok_hand: fix user action stop process
mquentin May 12, 2020
761ef8a
:ok_hand: improve tests and reformat waitPageActivitiesCompletion cal…
mquentin May 13, 2020
bfb1503
:ok_hand: move tests into waitPageActivitiesCompletion into trackPage…
mquentin May 13, 2020
c2999e3
:ok_hand: nit
mquentin May 13, 2020
8131f75
:ok_hand: improve view loading type testing
mquentin May 13, 2020
94fc77f
:ok_hand: remove stop ua function dependency in the ViewCollection
mquentin May 14, 2020
3a64616
Merge branch 'master' into maxime.quentin/SDKviewAddLoadDurationAndLo…
mquentin May 14, 2020
6cb2807
🎨 rename laodDuration to loadTime
mquentin May 14, 2020
75d0f7a
✨ add the loadTime and loadType in the RumEvent batch
mquentin May 14, 2020
4614cfd
👌 implement new reviews
mquentin May 15, 2020
1eaf083
✅ clarify loading time tests
mquentin May 15, 2020
7035c08
✅ remove view collection dependence from user action testing
mquentin May 18, 2020
aa2180d
✅ clarify the LifeCycleEventType.VIEW_COLLECTED subscribe
mquentin May 18, 2020
50fdd0b
Merge branch 'master' into maxime.quentin/SDKviewAddLoadDurationAndLo…
mquentin May 18, 2020
502a7d2
♻️ stop current user action
mquentin May 18, 2020
a43f468
Merge branch 'master' into maxime.quentin/SDKviewAddLoadDurationAndLo…
mquentin May 18, 2020
65bb6c8
Merge branch 'master' into maxime.quentin/SDKviewAddLoadDurationAndLo…
mquentin May 20, 2020
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
6 changes: 5 additions & 1 deletion packages/rum/src/rum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
import { InternalContext, RumGlobal } from './rum.entry'
import { RumSession } from './rumSession'
import { getUserActionReference, UserActionMeasures, UserActionReference, UserActionType } from './userActionCollection'
import { viewContext, ViewMeasures } from './viewCollection'
import { viewContext, ViewLoadingType, ViewMeasures } from './viewCollection'

export interface PerformancePaintTiming extends PerformanceEntry {
entryType: 'paint'
Expand Down Expand Up @@ -108,6 +108,8 @@ export interface RumViewEvent {
documentVersion: number
}
view: {
loadingTime?: number
loadingType: ViewLoadingType
measures: ViewMeasures
}
}
Expand Down Expand Up @@ -261,6 +263,8 @@ function trackView(lifeCycle: LifeCycle, upsertRumEvent: (event: RumViewEvent, k
documentVersion: view.documentVersion,
},
view: {
loadingTime: view.loadingTime,
loadingType: view.loadingType,
measures: view.measures,
},
},
Expand Down
148 changes: 148 additions & 0 deletions packages/rum/src/trackPageActivities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { monitor, Observable } from '@datadog/browser-core'
import { LifeCycle, LifeCycleEventType, Subscription } from './lifeCycle'

// Delay to wait for a page activity to validate the tracking process
export const PAGE_ACTIVITY_VALIDATION_DELAY = 100
// Delay to wait after a page activity to end the tracking process
export const PAGE_ACTIVITY_END_DELAY = 100
// Maximum duration of the tracking process
export const PAGE_ACTIVITY_MAX_DURATION = 10_000

export interface PageActivityEvent {
isBusy: boolean
}

export function waitIdlePageActivity(
lifeCycle: LifeCycle,
completionCallback: (hadActivity: boolean, endTime: number) => void
): { stop(): void } {
const { observable: pageActivitiesObservable, stop: stopPageActivitiesTracking } = trackPageActivities(lifeCycle)

const { stop: stopWaitPageActivitiesCompletion } = waitPageActivitiesCompletion(
pageActivitiesObservable,
stopPageActivitiesTracking,
completionCallback
)

function stop() {
stopWaitPageActivitiesCompletion()
stopPageActivitiesTracking()
}

return { stop }
}

// Automatic user action collection lifecycle overview:
// (Start new trackPageActivities)
// .-------------------'--------------------.
// v v
// [Wait for a page activity ] [Wait for a maximum duration]
// [timeout: VALIDATION_DELAY] [ timeout: MAX_DURATION ]
// / \ |
// v v |
// [No page activity] [Page activity] |
// | |,----------------------. |
// v v | |
// (Discard) [Wait for a page activity] | |
// [ timeout: END_DELAY ] | |
// / \ | |
// v v | |
// [No page activity] [Page activity] | |
// | | | |
// | '------------' |
// '-----------. ,--------------------'
// v
// (End)
//
// Note: because MAX_DURATION > VALIDATION_DELAY, we are sure that if the process is still alive
// after MAX_DURATION, it has been validated.
export function trackPageActivities(lifeCycle: LifeCycle): { observable: Observable<PageActivityEvent>; stop(): void } {
const observable = new Observable<PageActivityEvent>()
const subscriptions: Subscription[] = []
let firstRequestId: undefined | number
let pendingRequestsCount = 0

subscriptions.push(lifeCycle.subscribe(LifeCycleEventType.DOM_MUTATED, () => notifyPageActivity()))

subscriptions.push(
lifeCycle.subscribe(LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, (entry) => {
if (entry.entryType !== 'resource') {
return
}

notifyPageActivity()
})
)

subscriptions.push(
lifeCycle.subscribe(LifeCycleEventType.REQUEST_STARTED, (startEvent) => {
if (firstRequestId === undefined) {
firstRequestId = startEvent.requestId
}

pendingRequestsCount += 1
notifyPageActivity()
})
)

subscriptions.push(
lifeCycle.subscribe(LifeCycleEventType.REQUEST_COMPLETED, (request) => {
// If the request started before the tracking start, ignore it
if (firstRequestId === undefined || request.requestId < firstRequestId) {
return
}
pendingRequestsCount -= 1
notifyPageActivity()
})
)

function notifyPageActivity() {
observable.notify({ isBusy: pendingRequestsCount > 0 })
}

return {
observable,
stop() {
subscriptions.forEach((s) => s.unsubscribe())
},
}
}

export function waitPageActivitiesCompletion(
pageActivitiesObservable: Observable<PageActivityEvent>,
stopPageActivitiesTracking: () => void,
completionCallback: (hadActivity: boolean, endTime: number) => void
): { stop(): void } {
let idleTimeoutId: ReturnType<typeof setTimeout>
let hasCompleted = false

const validationTimeoutId = setTimeout(monitor(() => complete(false, 0)), PAGE_ACTIVITY_VALIDATION_DELAY)
const maxDurationTimeoutId = setTimeout(monitor(() => complete(true, performance.now())), PAGE_ACTIVITY_MAX_DURATION)

pageActivitiesObservable.subscribe(({ isBusy }) => {
clearTimeout(validationTimeoutId)
clearTimeout(idleTimeoutId)
const lastChangeTime = performance.now()
if (!isBusy) {
idleTimeoutId = setTimeout(monitor(() => complete(true, lastChangeTime)), PAGE_ACTIVITY_END_DELAY)
}
})

function stop() {
hasCompleted = true
clearTimeout(validationTimeoutId)
clearTimeout(idleTimeoutId)
clearTimeout(maxDurationTimeoutId)
stopPageActivitiesTracking()
}

function complete(hadActivity: boolean, endTime: number) {
if (hasCompleted) {
return
}
stop()
completionCallback(hadActivity, endTime)
}

return { stop }
}
Loading