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-1577] Collect page lifecycle states #2229

Merged
merged 9 commits into from
May 17, 2023
36 changes: 19 additions & 17 deletions packages/rum-core/src/domain/contexts/pageStateHistory.spec.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,31 @@
import type { RelativeTime, ServerDuration } from '@datadog/browser-core'
import { resetExperimentalFeatures } from '@datadog/browser-core'
import type { TestSetupBuilder } from '../../../test'
import { setup } from '../../../test'
import type { Clock } from '../../../../core/test'
import { mockClock } from '../../../../core/test'
import type { PageStateHistory } from './pageStateHistory'
import { startPageStateHistory, PageState } from './pageStateHistory'

describe('pageStateHistory', () => {
let pageStateHistory: PageStateHistory
let setupBuilder: TestSetupBuilder

let clock: Clock
beforeEach(() => {
setupBuilder = setup()
.withFakeClock()
.beforeBuild(() => {
pageStateHistory = startPageStateHistory()
return pageStateHistory
})
clock = mockClock()
pageStateHistory = startPageStateHistory()
})

afterEach(() => {
setupBuilder.cleanup()
pageStateHistory.stop()
resetExperimentalFeatures()
clock.cleanup()
})

it('should have the current state when starting', () => {
setupBuilder.build()
expect(pageStateHistory.findAll(0 as RelativeTime, 10 as RelativeTime)).toBeDefined()
})

it('should return undefined if the time period is out of history bounds', () => {
pageStateHistory = startPageStateHistory()
expect(pageStateHistory.findAll(-10 as RelativeTime, 0 as RelativeTime)).not.toBeDefined()
})

it('should return the correct page states for the given time period', () => {
const { clock } = setupBuilder.build()

pageStateHistory.addPageState(PageState.ACTIVE)

clock.tick(10)
Expand Down Expand Up @@ -66,4 +55,17 @@ describe('pageStateHistory', () => {
},
])
})

it('should limit the number of selectable entries', () => {
const maxPageStateEntriesSelectable = 1
pageStateHistory = startPageStateHistory(maxPageStateEntriesSelectable)

pageStateHistory.addPageState(PageState.ACTIVE)
clock.tick(10)
pageStateHistory.addPageState(PageState.PASSIVE)

expect(pageStateHistory.findAll(0 as RelativeTime, Infinity as RelativeTime)?.length).toEqual(
maxPageStateEntriesSelectable
)
})
})
46 changes: 28 additions & 18 deletions packages/rum-core/src/domain/contexts/pageStateHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import {
} from '@datadog/browser-core'
import type { PageStateServerEntry } from '../../rawRumEvent.types'

export const MAX_PAGE_STATE_ENTRIES = 200
// Arbitrary value to cap number of element for memory consumption in the browser
export const MAX_PAGE_STATE_ENTRIES = 4000
// Arbitrary value to cap number of element for backend & to save bandwidth
export const MAX_PAGE_STATE_ENTRIES_SELECTABLE = 500

export const PAGE_STATE_CONTEXT_TIME_OUT_DELAY = SESSION_TIME_OUT_DELAY

Expand All @@ -25,12 +28,14 @@ export const enum PageState {
export type PageStateEntry = { state: PageState; startTime: RelativeTime }

export interface PageStateHistory {
findAll: (startTime: RelativeTime, duration?: Duration) => PageStateServerEntry[] | undefined
findAll: (startTime: RelativeTime, duration: Duration) => PageStateServerEntry[] | undefined
addPageState(nextPageState: PageState, startTime?: RelativeTime): void
stop: () => void
}

export function startPageStateHistory(): PageStateHistory {
export function startPageStateHistory(
maxPageStateEntriesSelectable = MAX_PAGE_STATE_ENTRIES_SELECTABLE
): PageStateHistory {
const pageStateHistory = new ValueHistory<PageStateEntry>(PAGE_STATE_CONTEXT_TIME_OUT_DELAY, MAX_PAGE_STATE_ENTRIES)

let currentPageState: PageState
Expand Down Expand Up @@ -77,23 +82,28 @@ export function startPageStateHistory(): PageStateHistory {
)

return {
findAll: (startTime: RelativeTime, duration?: Duration): PageStateServerEntry[] | undefined => {
const pageStateEntries = pageStateHistory
.findAll(startTime, duration)
.reverse()
.map((pageState) => {
const correctedStartTime = startTime > pageState.startTime ? startTime : pageState.startTime
const recenteredStartTime = elapsed(startTime, correctedStartTime)

return {
state: pageState.state,
start: toServerDuration(recenteredStartTime),
}
})
findAll: (startTime: RelativeTime, duration: Duration): PageStateServerEntry[] | undefined => {
const pageStateEntries = pageStateHistory.findAll(startTime, duration)

if (pageStateEntries.length === 0) {
return
}

if (pageStateEntries.length > 0) {
return pageStateEntries
const pageStateServerEntries = []
const limit = Math.max(0, pageStateEntries.length - maxPageStateEntriesSelectable)

for (let index = pageStateEntries.length - 1; index >= limit; index--) {
Copy link
Member

Choose a reason for hiding this comment

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

🥜 nitpick: ‏It could be easier to grasp as:

for (let index = pageStateEntries.length - 1; index >= 0 && pageStateServerEntries.length < maxPageStateEntriesSelectable; index--) {
}

Or, maybe you could adapt the previous version as: .findAll().slice(0, maxPageStateEntriesSelectable).reverse().map()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hum, I was hesitating to do that in the first place but as it is less effective performance wise I went with a good old loop. I don't have a strong opinion though. Maybe a third party @bcaudan can help deciding?

Copy link
Contributor

Choose a reason for hiding this comment

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

I had the same thought, maybe adding a couple comments could make this code easier to grasp while keeping the most performant approach.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added some comments:

// limit the number of entries to return
const limit = Math.max(0, pageStateEntries.length - maxPageStateEntriesSelectable)
// loop page state entries backward to return the selected ones in desc order
for (let index = pageStateEntries.length - 1; index >= limit; index--) {

const pageState = pageStateEntries[index]
const correctedStartTime = startTime > pageState.startTime ? startTime : pageState.startTime
const recenteredStartTime = elapsed(startTime, correctedStartTime)

pageStateServerEntries.push({
state: pageState.state,
start: toServerDuration(recenteredStartTime),
})
}

return pageStateServerEntries
},
addPageState,
stop: () => {
Expand Down