diff --git a/packages/tracing/src/browser/metrics.ts b/packages/tracing/src/browser/metrics.ts index 00554fd02f6d..849503e6e068 100644 --- a/packages/tracing/src/browser/metrics.ts +++ b/packages/tracing/src/browser/metrics.ts @@ -134,6 +134,43 @@ export class MetricsInstrumentation { // Measurements are only available for pageload transactions if (transaction.op === 'pageload') { + // normalize applicable web vital values to be relative to transaction.startTimestamp + + const timeOrigin = msToSec(performance.timeOrigin); + + ['fcp', 'fp', 'lcp', 'ttfb'].forEach(name => { + if (!this._measurements[name] || timeOrigin >= transaction.startTimestamp) { + return; + } + + // The web vitals, fcp, fp, lcp, and ttfb, all measure relative to timeOrigin. + // Unfortunately, timeOrigin is not captured within the transaction span data, so these web vitals will need + // to be adjusted to be relative to transaction.startTimestamp. + + const oldValue = this._measurements[name].value; + const measurementTimestamp = timeOrigin + msToSec(oldValue); + // normalizedValue should be in milliseconds + const normalizedValue = (measurementTimestamp - transaction.startTimestamp) * 1000; + + const delta = normalizedValue - oldValue; + logger.log( + `[Measurements] Normalized ${name} from ${this._measurements[name].value} to ${normalizedValue} (${delta})`, + ); + + this._measurements[name].value = normalizedValue; + }); + + if (this._measurements['mark.fid'] && this._measurements['fid']) { + // create span for FID + + _startChild(transaction, { + description: 'first input delay', + endTimestamp: this._measurements['mark.fid'].value + msToSec(this._measurements['fid'].value), + op: 'web.vitals', + startTimestamp: this._measurements['mark.fid'].value, + }); + } + transaction.setMeasurements(this._measurements); } } @@ -253,6 +290,7 @@ function addNavigationSpans(transaction: Transaction, entry: Record addPerformanceNavigationTiming(transaction, entry, 'loadEvent', timeOrigin); addPerformanceNavigationTiming(transaction, entry, 'connect', timeOrigin); addPerformanceNavigationTiming(transaction, entry, 'secureConnection', timeOrigin, 'connectEnd'); + addPerformanceNavigationTiming(transaction, entry, 'fetch', timeOrigin, 'domainLookupStart'); addPerformanceNavigationTiming(transaction, entry, 'domainLookup', timeOrigin); addRequest(transaction, entry, timeOrigin); } diff --git a/packages/tracing/src/browser/web-vitals/getTTFB.ts b/packages/tracing/src/browser/web-vitals/getTTFB.ts index 43deb1268bbd..18f2aa0df45f 100644 --- a/packages/tracing/src/browser/web-vitals/getTTFB.ts +++ b/packages/tracing/src/browser/web-vitals/getTTFB.ts @@ -14,9 +14,13 @@ * limitations under the License. */ +import { getGlobalObject } from '@sentry/utils'; + import { initMetric } from './lib/initMetric'; import { ReportHandler } from './types'; +const global = getGlobalObject(); + interface NavigationEntryShim { // From `PerformanceNavigationTimingEntry`. entryType: string; @@ -80,7 +84,7 @@ const getNavigationEntryFromPerformanceTiming = (): PerformanceNavigationTiming // Note: browsers that do not support navigation entries will fall back to using performance.timing // (with the timestamps converted from epoch time to DOMHighResTimeStamp). // eslint-disable-next-line deprecation/deprecation - const timing = performance.timing; + const timing = global.performance.timing; const navigationEntry: NavigationEntryShim = { entryType: 'navigation', @@ -105,7 +109,7 @@ export const getTTFB = (onReport: ReportHandler): void => { try { // Use the NavigationTiming L2 entry if available. const navigationEntry = - performance.getEntriesByType('navigation')[0] || getNavigationEntryFromPerformanceTiming(); + global.performance.getEntriesByType('navigation')[0] || getNavigationEntryFromPerformanceTiming(); metric.value = metric.delta = (navigationEntry as PerformanceNavigationTiming).responseStart;