Skip to content

Commit

Permalink
Use performanceObserver for lcp entries
Browse files Browse the repository at this point in the history
  • Loading branch information
amortemousque committed Sep 13, 2024
1 parent 73ae1f5 commit 6654258
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 55 deletions.
7 changes: 5 additions & 2 deletions packages/rum-core/src/browser/performanceCollection.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ describe('startPerformanceCollection', () => {
;[
RumPerformanceEntryType.LONG_TASK,
RumPerformanceEntryType.PAINT,
RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT,
RumPerformanceEntryType.FIRST_INPUT,
RumPerformanceEntryType.LAYOUT_SHIFT,
RumPerformanceEntryType.EVENT,
Expand All @@ -36,7 +35,11 @@ describe('startPerformanceCollection', () => {
expect(entryCollectedCallback).toHaveBeenCalledWith([jasmine.objectContaining({ entryType })])
})
})
;[(RumPerformanceEntryType.NAVIGATION, RumPerformanceEntryType.RESOURCE)].forEach((entryType) => {
;[
(RumPerformanceEntryType.NAVIGATION,
RumPerformanceEntryType.RESOURCE,
RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT),
].forEach((entryType) => {
it(`should not notify ${entryType} timings`, () => {
const { notifyPerformanceEntries } = mockPerformanceObserver()
setupStartPerformanceCollection()
Expand Down
1 change: 0 additions & 1 deletion packages/rum-core/src/browser/performanceCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export function startPerformanceCollection(lifeCycle: LifeCycle, configuration:
)
const mainEntries = [RumPerformanceEntryType.LONG_TASK, RumPerformanceEntryType.PAINT]
const experimentalEntries = [
RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT,
RumPerformanceEntryType.FIRST_INPUT,
RumPerformanceEntryType.LAYOUT_SHIFT,
RumPerformanceEntryType.EVENT,
Expand Down
7 changes: 4 additions & 3 deletions packages/rum-core/src/domain/view/trackViews.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,9 +461,8 @@ describe('view metrics', () => {
clock.tick(KEEP_TRACKING_AFTER_VIEW_DELAY - 1)
lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [
createPerformanceEntry(RumPerformanceEntryType.PAINT),
lcpEntry,
])
notifyPerformanceEntries([createPerformanceEntry(RumPerformanceEntryType.NAVIGATION)])
notifyPerformanceEntries([createPerformanceEntry(RumPerformanceEntryType.NAVIGATION), lcpEntry])

clock.tick(THROTTLE_VIEW_UPDATE_PERIOD)

Expand Down Expand Up @@ -513,9 +512,11 @@ describe('view metrics', () => {

lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [
createPerformanceEntry(RumPerformanceEntryType.PAINT),
])
notifyPerformanceEntries([
createPerformanceEntry(RumPerformanceEntryType.NAVIGATION),
createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT),
])
notifyPerformanceEntries([createPerformanceEntry(RumPerformanceEntryType.NAVIGATION)])

clock.tick(THROTTLE_VIEW_UPDATE_PERIOD)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export function trackInitialViewMetrics(
})

const { stop: stopLCPTracking } = trackLargestContentfulPaint(
lifeCycle,
configuration,
firstHidden,
window,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import {
restorePageVisibility,
registerCleanupTask,
} from '@datadog/browser-core/test'
import type { RumPerformanceEntry } from '../../../browser/performanceObservable'
import { RumPerformanceEntryType } from '../../../browser/performanceObservable'
import { appendElement, createPerformanceEntry, mockRumConfiguration } from '../../../../test'
import { LifeCycle, LifeCycleEventType } from '../../lifeCycle'
import { appendElement, createPerformanceEntry, mockPerformanceObserver, mockRumConfiguration } from '../../../../test'
import { LifeCycle } from '../../lifeCycle'
import type { LargestContentfulPaint } from './trackLargestContentfulPaint'
import { LCP_MAXIMUM_DELAY, trackLargestContentfulPaint } from './trackLargestContentfulPaint'
import { trackFirstHidden } from './trackFirstHidden'
Expand All @@ -17,8 +18,11 @@ describe('trackLargestContentfulPaint', () => {
const lifeCycle = new LifeCycle()
let lcpCallback: jasmine.Spy<(lcp: LargestContentfulPaint) => void>
let eventTarget: Window
let notifyPerformanceEntries: (entries: RumPerformanceEntry[]) => void

function startLCPTracking() {
;({ notifyPerformanceEntries } = mockPerformanceObserver())

const firstHidden = trackFirstHidden(mockRumConfiguration())
const largestContentfulPaint = trackLargestContentfulPaint(
lifeCycle,
Expand All @@ -42,16 +46,14 @@ describe('trackLargestContentfulPaint', () => {

it('should provide the largest contentful paint timing', () => {
startLCPTracking()
lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [
createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT),
])
notifyPerformanceEntries([createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT)])

expect(lcpCallback).toHaveBeenCalledOnceWith({ value: 789 as RelativeTime, targetSelector: undefined })
})

it('should provide the largest contentful paint target selector', () => {
startLCPTracking()
lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [
notifyPerformanceEntries([
createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT, {
element: appendElement('<button id="lcp-target-element"></button>'),
}),
Expand All @@ -64,9 +66,7 @@ describe('trackLargestContentfulPaint', () => {
startLCPTracking()
eventTarget.dispatchEvent(createNewEvent(DOM_EVENT.KEY_DOWN, { timeStamp: 1 }))

lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [
createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT),
])
notifyPerformanceEntries([createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT)])

expect(lcpCallback).not.toHaveBeenCalled()
})
Expand All @@ -75,16 +75,14 @@ describe('trackLargestContentfulPaint', () => {
setPageVisibility('hidden')
startLCPTracking()

lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [
createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT),
])
notifyPerformanceEntries([createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT)])

expect(lcpCallback).not.toHaveBeenCalled()
})

it('should be discarded if it is reported after a long time', () => {
startLCPTracking()
lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [
notifyPerformanceEntries([
createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT, {
startTime: LCP_MAXIMUM_DELAY as RelativeTime,
}),
Expand All @@ -95,14 +93,14 @@ describe('trackLargestContentfulPaint', () => {

it('should be discarded if it has a size inferior to the previous LCP entry', () => {
startLCPTracking()
lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [
notifyPerformanceEntries([
createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT, {
startTime: 1 as RelativeTime,
size: 10,
}),
])

lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [
notifyPerformanceEntries([
createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT, {
startTime: 2 as RelativeTime,
size: 5,
Expand All @@ -114,14 +112,14 @@ describe('trackLargestContentfulPaint', () => {

it('should notify multiple times when the size is bigger than the previous entry', () => {
startLCPTracking()
lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [
notifyPerformanceEntries([
createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT, {
startTime: 1 as RelativeTime,
size: 5,
}),
])

lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [
notifyPerformanceEntries([
createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT, {
startTime: 2 as RelativeTime,
size: 10,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type { RelativeTime } from '@datadog/browser-core'
import { DOM_EVENT, ONE_MINUTE, addEventListeners, findLast } from '@datadog/browser-core'
import { LifeCycleEventType } from '../../lifeCycle'
import type { LifeCycle } from '../../lifeCycle'
import type { RumConfiguration } from '../../configuration'
import { RumPerformanceEntryType } from '../../../browser/performanceObservable'
import { createPerformanceObservable, RumPerformanceEntryType } from '../../../browser/performanceObservable'
import type { RumLargestContentfulPaintTiming } from '../../../browser/performanceObservable'
import { getSelectorFromElement } from '../../getSelectorFromElement'
import type { FirstHidden } from './trackFirstHidden'
Expand All @@ -24,7 +22,6 @@ export interface LargestContentfulPaint {
* Reference implementation: https://github.com/GoogleChrome/web-vitals/blob/master/src/onLCP.ts
*/
export function trackLargestContentfulPaint(
lifeCycle: LifeCycle,
configuration: RumConfiguration,
firstHidden: FirstHidden,
eventTarget: Window,
Expand All @@ -45,40 +42,40 @@ export function trackLargestContentfulPaint(
)

let biggestLcpSize = 0
const { unsubscribe: unsubscribeLifeCycle } = lifeCycle.subscribe(
LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED,
(entries) => {
const lcpEntry = findLast(
entries,
(entry): entry is RumLargestContentfulPaintTiming =>
entry.entryType === RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT &&
entry.startTime < firstInteractionTimestamp &&
entry.startTime < firstHidden.timeStamp &&
entry.startTime < LCP_MAXIMUM_DELAY &&
// Ensure to get the LCP entry with the biggest size, see
// https://bugs.chromium.org/p/chromium/issues/detail?id=1516655
entry.size > biggestLcpSize
)
const performanceLcpSubscription = createPerformanceObservable(configuration, {
type: RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT,
buffered: true,
}).subscribe((entries) => {
const lcpEntry = findLast(
entries,
(entry): entry is RumLargestContentfulPaintTiming =>
entry.entryType === RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT &&
entry.startTime < firstInteractionTimestamp &&
entry.startTime < firstHidden.timeStamp &&
entry.startTime < LCP_MAXIMUM_DELAY &&
// Ensure to get the LCP entry with the biggest size, see
// https://bugs.chromium.org/p/chromium/issues/detail?id=1516655
entry.size > biggestLcpSize
)

if (lcpEntry) {
let lcpTargetSelector
if (lcpEntry.element) {
lcpTargetSelector = getSelectorFromElement(lcpEntry.element, configuration.actionNameAttribute)
}

callback({
value: lcpEntry.startTime,
targetSelector: lcpTargetSelector,
})
biggestLcpSize = lcpEntry.size
if (lcpEntry) {
let lcpTargetSelector
if (lcpEntry.element) {
lcpTargetSelector = getSelectorFromElement(lcpEntry.element, configuration.actionNameAttribute)
}

callback({
value: lcpEntry.startTime,
targetSelector: lcpTargetSelector,
})
biggestLcpSize = lcpEntry.size
}
)
})

return {
stop: () => {
stopEventListener()
unsubscribeLifeCycle()
performanceLcpSubscription.unsubscribe()
},
}
}

0 comments on commit 6654258

Please sign in to comment.