From f091d76dde83c733fffe2c9c862f4bcdc086a3c4 Mon Sep 17 00:00:00 2001 From: Alberto Leal Date: Tue, 27 Oct 2020 16:19:44 -0400 Subject: [PATCH] feat(web-vitals): Capture extra information from LCP and CLS web vitals. --- packages/tracing/src/browser/metrics.ts | 28 +++++++++++++++++-- .../tracing/src/browser/web-vitals/getCLS.ts | 11 +++++++- .../tracing/src/browser/web-vitals/getLCP.ts | 15 ++++++++-- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/packages/tracing/src/browser/metrics.ts b/packages/tracing/src/browser/metrics.ts index 00554fd02f6d..634b246a4301 100644 --- a/packages/tracing/src/browser/metrics.ts +++ b/packages/tracing/src/browser/metrics.ts @@ -1,14 +1,14 @@ /* eslint-disable max-lines */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Measurements, SpanContext } from '@sentry/types'; -import { browserPerformanceTimeOrigin, getGlobalObject, logger } from '@sentry/utils'; +import { browserPerformanceTimeOrigin, getGlobalObject, htmlTreeAsString, logger } from '@sentry/utils'; import { Span } from '../span'; import { Transaction } from '../transaction'; import { msToSec } from '../utils'; -import { getCLS } from './web-vitals/getCLS'; +import { getCLS, LayoutShift } from './web-vitals/getCLS'; import { getFID } from './web-vitals/getFID'; -import { getLCP } from './web-vitals/getLCP'; +import { getLCP, LargestContentfulPaint } from './web-vitals/getLCP'; import { getTTFB } from './web-vitals/getTTFB'; import { getFirstHidden } from './web-vitals/lib/getFirstHidden'; import { NavigatorDeviceMemory, NavigatorNetworkInformation } from './web-vitals/types'; @@ -20,6 +20,8 @@ export class MetricsInstrumentation { private _measurements: Measurements = {}; private _performanceCursor: number = 0; + private _lcpEntry: LargestContentfulPaint | undefined; + private _clsEntry: LayoutShift | undefined; public constructor() { if (global && global.performance) { @@ -135,6 +137,24 @@ export class MetricsInstrumentation { // Measurements are only available for pageload transactions if (transaction.op === 'pageload') { transaction.setMeasurements(this._measurements); + + if (this._lcpEntry) { + logger.log('[Measurements] Adding LCP Data'); + transaction.setTag('measurements.lcp.url', this._lcpEntry.url); + transaction.setData('measurements.lcp', { + size: this._lcpEntry.size, + id: this._lcpEntry.id, + url: this._lcpEntry.url, + element: this._lcpEntry.element ? htmlTreeAsString(this._lcpEntry.element) : undefined, + }); + } + + if (this._clsEntry) { + logger.log('[Measurements] Adding CLS Data'); + transaction.setData('measurements.cls', { + sources: this._clsEntry.sources.map(source => htmlTreeAsString(source.node)), + }); + } } } @@ -149,6 +169,7 @@ export class MetricsInstrumentation { logger.log('[Measurements] Adding CLS'); this._measurements['cls'] = { value: metric.value }; + this._clsEntry = entry as LayoutShift; }); } @@ -206,6 +227,7 @@ export class MetricsInstrumentation { logger.log('[Measurements] Adding LCP'); this._measurements['lcp'] = { value: metric.value }; this._measurements['mark.lcp'] = { value: timeOrigin + startTime }; + this._lcpEntry = entry as LargestContentfulPaint; }); } diff --git a/packages/tracing/src/browser/web-vitals/getCLS.ts b/packages/tracing/src/browser/web-vitals/getCLS.ts index 530951834e47..eb3eed207a7d 100644 --- a/packages/tracing/src/browser/web-vitals/getCLS.ts +++ b/packages/tracing/src/browser/web-vitals/getCLS.ts @@ -21,9 +21,18 @@ import { onHidden } from './lib/onHidden'; import { ReportHandler } from './types'; // https://wicg.github.io/layout-instability/#sec-layout-shift -interface LayoutShift extends PerformanceEntry { +export interface LayoutShift extends PerformanceEntry { value: number; hadRecentInput: boolean; + lastInputTime: DOMHighResTimeStamp; + sources: Array; + toJSON(): Record; +} + +export interface LayoutShiftAttribution { + node?: Node; + previousRect: DOMRectReadOnly; + currentRect: DOMRectReadOnly; } export const getCLS = (onReport: ReportHandler, reportAllChanges = false): void => { diff --git a/packages/tracing/src/browser/web-vitals/getLCP.ts b/packages/tracing/src/browser/web-vitals/getLCP.ts index e0fdd9c7cfde..0d0c5b3e670a 100644 --- a/packages/tracing/src/browser/web-vitals/getLCP.ts +++ b/packages/tracing/src/browser/web-vitals/getLCP.ts @@ -22,13 +22,24 @@ import { onHidden } from './lib/onHidden'; import { whenInput } from './lib/whenInput'; import { ReportHandler } from './types'; +// https://wicg.github.io/largest-contentful-paint/#sec-largest-contentful-paint-interface +export interface LargestContentfulPaint extends PerformanceEntry { + renderTime: DOMHighResTimeStamp; + loadTime: DOMHighResTimeStamp; + size: number; + id: string; + url: string; + element?: Element; + toJSON(): Record; +} + export const getLCP = (onReport: ReportHandler, reportAllChanges = false): void => { const metric = initMetric('LCP'); const firstHidden = getFirstHidden(); let report: ReturnType; - const entryHandler = (entry: PerformanceEntry): void => { + const entryHandler = (entry: LargestContentfulPaint): void => { // The startTime attribute returns the value of the renderTime if it is not 0, // and the value of the loadTime otherwise. const value = entry.startTime; @@ -45,7 +56,7 @@ export const getLCP = (onReport: ReportHandler, reportAllChanges = false): void report(); }; - const po = observe('largest-contentful-paint', entryHandler); + const po = observe('largest-contentful-paint', entryHandler as PerformanceEntryHandler); if (po) { report = bindReporter(onReport, metric, po, reportAllChanges);