Skip to content

Commit

Permalink
⚗🚩 [RUMF-989] add a feature flag to disable max activity duration
Browse files Browse the repository at this point in the history
  • Loading branch information
BenoitZugmeyer committed Aug 30, 2021
1 parent a58f86c commit 436d266
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ export interface AutoActionCreatedEvent {
export function trackActions(
lifeCycle: LifeCycle,
domMutationObservable: DOMMutationObservable,
{ actionNameAttribute }: Configuration
configuration: Configuration
) {
const action = startActionManagement(lifeCycle, domMutationObservable)
const action = startActionManagement(lifeCycle, domMutationObservable, configuration)

// New views trigger the discard of the current pending Action
lifeCycle.subscribe(LifeCycleEventType.VIEW_CREATED, () => {
Expand All @@ -66,7 +66,7 @@ export function trackActions(
if (!(event.target instanceof Element)) {
return
}
const name = getActionNameFromElement(event.target, actionNameAttribute)
const name = getActionNameFromElement(event.target, configuration.actionNameAttribute)
if (!name) {
return
}
Expand All @@ -84,7 +84,11 @@ export function trackActions(
}
}

function startActionManagement(lifeCycle: LifeCycle, domMutationObservable: DOMMutationObservable) {
function startActionManagement(
lifeCycle: LifeCycle,
domMutationObservable: DOMMutationObservable,
configuration: Configuration
) {
let currentAction: PendingAutoAction | undefined
let currentIdlePageActivitySubscription: { stop: () => void }

Expand All @@ -97,14 +101,19 @@ function startActionManagement(lifeCycle: LifeCycle, domMutationObservable: DOMM
const pendingAutoAction = new PendingAutoAction(lifeCycle, type, name, event)

currentAction = pendingAutoAction
currentIdlePageActivitySubscription = waitIdlePageActivity(lifeCycle, domMutationObservable, (params) => {
if (params.hadActivity) {
pendingAutoAction.complete(params.endTime)
} else {
pendingAutoAction.discard()
currentIdlePageActivitySubscription = waitIdlePageActivity(
lifeCycle,
domMutationObservable,
configuration,
(params) => {
if (params.hadActivity) {
pendingAutoAction.complete(params.endTime)
} else {
pendingAutoAction.discard()
}
currentAction = undefined
}
currentAction = undefined
})
)
},
discardCurrent: () => {
if (currentAction) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Duration, noop, elapsed, round, timeStampNow } from '@datadog/browser-core'
import { Duration, noop, elapsed, round, timeStampNow, Configuration } from '@datadog/browser-core'
import { supportPerformanceTimingEvent } from '../../../browser/performanceCollection'
import { ViewLoadingType } from '../../../rawRumEvent.types'
import { LifeCycle, LifeCycleEventType } from '../../lifeCycle'
Expand All @@ -16,7 +16,8 @@ export function trackViewMetrics(
lifeCycle: LifeCycle,
domMutationObservable: DOMMutationObservable,
scheduleViewUpdate: () => void,
loadingType: ViewLoadingType
loadingType: ViewLoadingType,
configuration: Configuration
) {
const viewMetrics: ViewMetrics = {
eventCounts: {
Expand All @@ -39,6 +40,7 @@ export function trackViewMetrics(
const { stop: stopActivityLoadingTimeTracking } = trackActivityLoadingTime(
lifeCycle,
domMutationObservable,
configuration,
setActivityLoadingTime
)

Expand Down Expand Up @@ -97,16 +99,22 @@ function trackLoadingTime(loadType: ViewLoadingType, callback: (loadingTime: Dur
function trackActivityLoadingTime(
lifeCycle: LifeCycle,
domMutationObservable: DOMMutationObservable,
configuration: Configuration,
callback: (loadingTimeValue: Duration | undefined) => void
) {
const startTime = timeStampNow()
const { stop: stopWaitIdlePageActivity } = waitIdlePageActivity(lifeCycle, domMutationObservable, (params) => {
if (params.hadActivity) {
callback(elapsed(startTime, params.endTime))
} else {
callback(undefined)
const { stop: stopWaitIdlePageActivity } = waitIdlePageActivity(
lifeCycle,
domMutationObservable,
configuration,
(params) => {
if (params.hadActivity) {
callback(elapsed(startTime, params.endTime))
} else {
callback(undefined)
}
}
})
)

return { stop: stopWaitIdlePageActivity }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
timeStampNow,
TimeStamp,
display,
Configuration,
} from '@datadog/browser-core'
import { DOMMutationObservable } from '../../../browser/domMutationObservable'
import { ViewLoadingType, ViewCustomTimings } from '../../../rawRumEvent.types'
Expand Down Expand Up @@ -58,6 +59,7 @@ export function trackViews(
lifeCycle: LifeCycle,
domMutationObservable: DOMMutationObservable,
areViewsTrackedAutomatically: boolean,
configuration: Configuration,
initialViewName?: string
) {
const { stop: stopInitialViewTracking, initialView } = trackInitialView(initialViewName)
Expand All @@ -75,6 +77,7 @@ export function trackViews(
location,
ViewLoadingType.INITIAL_LOAD,
document.referrer,
configuration,
clocksOrigin(),
name
)
Expand All @@ -92,6 +95,7 @@ export function trackViews(
location,
ViewLoadingType.ROUTE_CHANGE,
currentView.url,
configuration,
startClocks,
name
)
Expand Down Expand Up @@ -172,6 +176,7 @@ function newView(
initialLocation: Location,
loadingType: ViewLoadingType,
referrer: string,
configuration: Configuration,
startClocks: ClocksState = clocksNow(),
name?: string
) {
Expand All @@ -198,7 +203,8 @@ function newView(
lifeCycle,
domMutationObservable,
scheduleViewUpdate,
loadingType
loadingType,
configuration
)

// Initial view update
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,14 @@ export function startViewCollection(
)
)

return trackViews(location, lifeCycle, domMutationObservable, !configuration.trackViewsManually, initialViewName)
return trackViews(
location,
lifeCycle,
domMutationObservable,
!configuration.trackViewsManually,
configuration,
initialViewName
)
}

function processViewUpdate(
Expand Down
86 changes: 66 additions & 20 deletions packages/rum-core/src/domain/trackPageActivities.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { noop, Observable, TimeStamp, timeStampNow } from '@datadog/browser-core'
import { Configuration, noop, Observable, TimeStamp, timeStampNow } from '@datadog/browser-core'
import { Clock, mockClock } from '../../../core/test/specHelper'
import { RumPerformanceNavigationTiming, RumPerformanceResourceTiming } from '../browser/performanceCollection'
import { LifeCycle, LifeCycleEventType } from './lifeCycle'
Expand All @@ -20,19 +20,6 @@ const BEFORE_PAGE_ACTIVITY_END_DELAY = PAGE_ACTIVITY_END_DELAY * 0.8
// A long delay used to wait after any action is finished.
const EXPIRE_DELAY = PAGE_ACTIVITY_MAX_DURATION * 10

function eventsCollector<T>() {
const events: T[] = []
beforeEach(() => {
events.length = 0
})
return {
events,
pushEvent: (event: T) => {
events.push(event)
},
}
}

describe('trackPagePageActivities', () => {
const { events, pushEvent } = eventsCollector<PageActivityEvent>()

Expand Down Expand Up @@ -154,7 +141,7 @@ describe('waitPageActivitiesCompletion', () => {
})

it('should not collect an event that is not followed by page activity', () => {
waitPageActivitiesCompletion(new Observable(), noop, completionCallbackSpy)
waitPageActivitiesCompletion(new Observable(), noop, createConfiguration(), completionCallbackSpy)

clock.tick(EXPIRE_DELAY)

Expand All @@ -167,7 +154,7 @@ describe('waitPageActivitiesCompletion', () => {
const activityObservable = new Observable<PageActivityEvent>()

const startTime = timeStampNow()
waitPageActivitiesCompletion(activityObservable, noop, completionCallbackSpy)
waitPageActivitiesCompletion(activityObservable, noop, createConfiguration(), completionCallbackSpy)

clock.tick(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY)
activityObservable.notify({ isBusy: false })
Expand All @@ -189,7 +176,7 @@ describe('waitPageActivitiesCompletion', () => {
// Extend the action but stops before PAGE_ACTIVITY_MAX_DURATION
const extendCount = Math.floor(PAGE_ACTIVITY_MAX_DURATION / BEFORE_PAGE_ACTIVITY_END_DELAY - 1)

waitPageActivitiesCompletion(activityObservable, noop, completionCallbackSpy)
waitPageActivitiesCompletion(activityObservable, noop, createConfiguration(), completionCallbackSpy)

for (let i = 0; i < extendCount; i += 1) {
clock.tick(BEFORE_PAGE_ACTIVITY_END_DELAY)
Expand All @@ -216,7 +203,7 @@ describe('waitPageActivitiesCompletion', () => {
completionCallbackSpy.and.callFake(() => {
stop = true
})
waitPageActivitiesCompletion(activityObservable, noop, completionCallbackSpy)
waitPageActivitiesCompletion(activityObservable, noop, createConfiguration(), completionCallbackSpy)

for (let i = 0; i < extendCount && !stop; i += 1) {
clock.tick(BEFORE_PAGE_ACTIVITY_END_DELAY)
Expand All @@ -231,13 +218,34 @@ describe('waitPageActivitiesCompletion', () => {
endTime: (startTime + PAGE_ACTIVITY_MAX_DURATION) as TimeStamp,
})
})

it('with eternal-page-activities, does not expire if the page is busy for too long', () => {
const activityObservable = new Observable<PageActivityEvent>()

// Extend the action until it's more than PAGE_ACTIVITY_MAX_DURATION
const extendCount = Math.ceil(PAGE_ACTIVITY_MAX_DURATION / BEFORE_PAGE_ACTIVITY_END_DELAY + 1)

waitPageActivitiesCompletion(
activityObservable,
noop,
createConfiguration('eternal-page-activities'),
completionCallbackSpy
)

for (let i = 0; i < extendCount; i += 1) {
clock.tick(BEFORE_PAGE_ACTIVITY_END_DELAY)
activityObservable.notify({ isBusy: false })
}

expect(completionCallbackSpy).not.toHaveBeenCalled()
})
})

describe('busy activities', () => {
it('is extended while the page is busy', () => {
const activityObservable = new Observable<PageActivityEvent>()
const startTime = timeStampNow()
waitPageActivitiesCompletion(activityObservable, noop, completionCallbackSpy)
waitPageActivitiesCompletion(activityObservable, noop, createConfiguration(), completionCallbackSpy)

clock.tick(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY)
activityObservable.notify({ isBusy: true })
Expand All @@ -257,7 +265,7 @@ describe('waitPageActivitiesCompletion', () => {
it('expires is the page is busy for too long', () => {
const activityObservable = new Observable<PageActivityEvent>()
const startTime = timeStampNow()
waitPageActivitiesCompletion(activityObservable, noop, completionCallbackSpy)
waitPageActivitiesCompletion(activityObservable, noop, createConfiguration(), completionCallbackSpy)

clock.tick(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY)
activityObservable.notify({ isBusy: true })
Expand All @@ -270,5 +278,43 @@ describe('waitPageActivitiesCompletion', () => {
endTime: (startTime + PAGE_ACTIVITY_MAX_DURATION) as TimeStamp,
})
})

it('with eternal-page-activities, does not expire if the page is busy for too long', () => {
const activityObservable = new Observable<PageActivityEvent>()
waitPageActivitiesCompletion(
activityObservable,
noop,
createConfiguration('eternal-page-activities'),
completionCallbackSpy
)

clock.tick(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY)
activityObservable.notify({ isBusy: true })

clock.tick(EXPIRE_DELAY)

expect(completionCallbackSpy).not.toHaveBeenCalled()
})
})
})

function eventsCollector<T>() {
const events: T[] = []
beforeEach(() => {
events.length = 0
})
return {
events,
pushEvent: (event: T) => {
events.push(event)
},
}
}

function createConfiguration(enabledFeatureFlag?: string) {
return {
isEnabled(flag: string) {
return enabledFeatureFlag === flag
},
} as Configuration
}
19 changes: 13 additions & 6 deletions packages/rum-core/src/domain/trackPageActivities.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { monitor, Observable, Subscription, TimeStamp, timeStampNow } from '@datadog/browser-core'
import { Configuration, monitor, Observable, Subscription, TimeStamp, timeStampNow } from '@datadog/browser-core'
import { DOMMutationObservable } from '../browser/domMutationObservable'
import { LifeCycle, LifeCycleEventType } from './lifeCycle'

Expand All @@ -18,6 +18,7 @@ export type CompletionCallbackParameters = { hadActivity: true; endTime: TimeSta
export function waitIdlePageActivity(
lifeCycle: LifeCycle,
domMutationObservable: DOMMutationObservable,
configuration: Configuration,
completionCallback: (params: CompletionCallbackParameters) => void
) {
const { observable: pageActivitiesObservable, stop: stopPageActivitiesTracking } = trackPageActivities(
Expand All @@ -28,6 +29,7 @@ export function waitIdlePageActivity(
const { stop: stopWaitPageActivitiesCompletion } = waitPageActivitiesCompletion(
pageActivitiesObservable,
stopPageActivitiesTracking,
configuration,
completionCallback
)

Expand Down Expand Up @@ -121,6 +123,7 @@ export function trackPageActivities(
export function waitPageActivitiesCompletion(
pageActivitiesObservable: Observable<PageActivityEvent>,
stopPageActivitiesTracking: () => void,
configuration: Configuration,
completionCallback: (params: CompletionCallbackParameters) => void
): { stop: () => void } {
let idleTimeoutId: number
Expand All @@ -130,10 +133,12 @@ export function waitPageActivitiesCompletion(
monitor(() => complete({ hadActivity: false })),
PAGE_ACTIVITY_VALIDATION_DELAY
)
const maxDurationTimeoutId = setTimeout(
monitor(() => complete({ hadActivity: true, endTime: timeStampNow() })),
PAGE_ACTIVITY_MAX_DURATION
)
const maxDurationTimeoutId =
!configuration.isEnabled('eternal-page-activities') &&
setTimeout(
monitor(() => complete({ hadActivity: true, endTime: timeStampNow() })),
PAGE_ACTIVITY_MAX_DURATION
)

pageActivitiesObservable.subscribe(({ isBusy }) => {
clearTimeout(validationTimeoutId)
Expand All @@ -151,7 +156,9 @@ export function waitPageActivitiesCompletion(
hasCompleted = true
clearTimeout(validationTimeoutId)
clearTimeout(idleTimeoutId)
clearTimeout(maxDurationTimeoutId)
if (maxDurationTimeoutId) {
clearTimeout(maxDurationTimeoutId)
}
stopPageActivitiesTracking()
}

Expand Down
1 change: 1 addition & 0 deletions packages/rum-core/test/specHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ export function setupViewTest(
lifeCycle,
domMutationObservable,
!configuration.trackViewsManually,
configuration,
initialViewName
)
return {
Expand Down

0 comments on commit 436d266

Please sign in to comment.