diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index e7d44ab316a3..1d2b6b47c87e 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -83,7 +83,7 @@ interface StartTrackingWebVitalsOptions { */ export function startTrackingWebVitals({ recordClsStandaloneSpans }: StartTrackingWebVitalsOptions): () => void { const performance = getBrowserPerformanceAPI(); - if (performance && browserPerformanceTimeOrigin) { + if (performance && browserPerformanceTimeOrigin()) { // @ts-expect-error we want to make sure all of these are available, even if TS is sure they are if (performance.mark) { WINDOW.performance.mark('sentry-tracing-init'); @@ -117,7 +117,7 @@ export function startTrackingLongTasks(): void { const { op: parentOp, start_timestamp: parentStartTimestamp } = spanToJSON(parent); for (const entry of entries) { - const startTime = msToSec((browserPerformanceTimeOrigin as number) + entry.startTime); + const startTime = msToSec((browserPerformanceTimeOrigin() as number) + entry.startTime); const duration = msToSec(entry.duration); if (parentOp === 'navigation' && parentStartTimestamp && startTime < parentStartTimestamp) { @@ -156,7 +156,7 @@ export function startTrackingLongAnimationFrames(): void { continue; } - const startTime = msToSec((browserPerformanceTimeOrigin as number) + entry.startTime); + const startTime = msToSec((browserPerformanceTimeOrigin() as number) + entry.startTime); const { start_timestamp: parentStartTimestamp, op: parentOp } = spanToJSON(parent); @@ -167,7 +167,6 @@ export function startTrackingLongAnimationFrames(): void { // routing instrumentations continue; } - const duration = msToSec(entry.duration); const attributes: SpanAttributes = { @@ -210,7 +209,7 @@ export function startTrackingInteractions(): void { } for (const entry of entries) { if (entry.name === 'click') { - const startTime = msToSec((browserPerformanceTimeOrigin as number) + entry.startTime); + const startTime = msToSec((browserPerformanceTimeOrigin() as number) + entry.startTime); const duration = msToSec(entry.duration); const spanOptions: StartSpanOptions & Required> = { @@ -271,7 +270,7 @@ function _trackFID(): () => void { return; } - const timeOrigin = msToSec(browserPerformanceTimeOrigin as number); + const timeOrigin = msToSec(browserPerformanceTimeOrigin() as number); const startTime = msToSec(entry.startTime); _measurements['fid'] = { value: metric.value, unit: 'millisecond' }; _measurements['mark.fid'] = { value: timeOrigin + startTime, unit: 'second' }; @@ -300,12 +299,13 @@ interface AddPerformanceEntriesOptions { /** Add performance related spans to a transaction */ export function addPerformanceEntries(span: Span, options: AddPerformanceEntriesOptions): void { const performance = getBrowserPerformanceAPI(); - if (!performance?.getEntries || !browserPerformanceTimeOrigin) { + const origin = browserPerformanceTimeOrigin(); + if (!performance?.getEntries || !origin) { // Gatekeeper if performance API not available return; } - const timeOrigin = msToSec(browserPerformanceTimeOrigin); + const timeOrigin = msToSec(origin); const performanceEntries = performance.getEntries(); diff --git a/packages/browser-utils/src/metrics/cls.ts b/packages/browser-utils/src/metrics/cls.ts index 44cf0c6c9e34..f9a6c662d79d 100644 --- a/packages/browser-utils/src/metrics/cls.ts +++ b/packages/browser-utils/src/metrics/cls.ts @@ -90,7 +90,7 @@ export function trackClsAsStandaloneSpan(): void { function sendStandaloneClsSpan(clsValue: number, entry: LayoutShift | undefined, pageloadSpanId: string) { DEBUG_BUILD && logger.log(`Sending CLS span (${clsValue})`); - const startTime = msToSec((browserPerformanceTimeOrigin || 0) + (entry?.startTime || 0)); + const startTime = msToSec((browserPerformanceTimeOrigin() || 0) + (entry?.startTime || 0)); const routeName = getCurrentScope().getScopeData().transactionName; const name = entry ? htmlTreeAsString(entry.sources[0]?.node) : 'Layout shift'; diff --git a/packages/browser-utils/src/metrics/inp.ts b/packages/browser-utils/src/metrics/inp.ts index 924104c28b6a..64ea9cccaca0 100644 --- a/packages/browser-utils/src/metrics/inp.ts +++ b/packages/browser-utils/src/metrics/inp.ts @@ -28,7 +28,7 @@ const INTERACTIONS_SPAN_MAP = new Map(); */ export function startTrackingINP(): () => void { const performance = getBrowserPerformanceAPI(); - if (performance && browserPerformanceTimeOrigin) { + if (performance && browserPerformanceTimeOrigin()) { const inpCallback = _trackINP(); return (): void => { @@ -85,7 +85,7 @@ function _trackINP(): () => void { const interactionType = INP_ENTRY_MAP[entry.name]; /** Build the INP span, create an envelope from the span, and then send the envelope */ - const startTime = msToSec((browserPerformanceTimeOrigin as number) + entry.startTime); + const startTime = msToSec((browserPerformanceTimeOrigin() as number) + entry.startTime); const duration = msToSec(metric.value); const activeSpan = getActiveSpan(); const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined; diff --git a/packages/browser/src/profiling/utils.ts b/packages/browser/src/profiling/utils.ts index f06e1606302b..04661e0bbb1a 100644 --- a/packages/browser/src/profiling/utils.ts +++ b/packages/browser/src/profiling/utils.ts @@ -241,9 +241,9 @@ export function convertJSSelfProfileToSampledFormat(input: JSSelfProfile): Profi // when that happens, we need to ensure we are correcting the profile timings so the two timelines stay in sync. // Since JS self profiling time origin is always initialized to performance.timeOrigin, we need to adjust for // the drift between the SDK selected value and our profile time origin. - const origin = - typeof performance.timeOrigin === 'number' ? performance.timeOrigin : browserPerformanceTimeOrigin || 0; - const adjustForOriginChange = origin - (browserPerformanceTimeOrigin || origin); + const perfOrigin = browserPerformanceTimeOrigin(); + const origin = typeof performance.timeOrigin === 'number' ? performance.timeOrigin : perfOrigin || 0; + const adjustForOriginChange = origin - (perfOrigin || origin); input.samples.forEach((jsSample, i) => { // If sample has no stack, add an empty sample diff --git a/packages/browser/src/tracing/browserTracingIntegration.ts b/packages/browser/src/tracing/browserTracingIntegration.ts index f4ab605be9c2..543fc314366e 100644 --- a/packages/browser/src/tracing/browserTracingIntegration.ts +++ b/packages/browser/src/tracing/browserTracingIntegration.ts @@ -364,10 +364,11 @@ export const browserTracingIntegration = ((_options: Partial number { export const timestampInSeconds = createUnixTimestampInSecondsFunc(); /** - * Internal helper to store what is the source of browserPerformanceTimeOrigin below. For debugging only. - * - * @deprecated This variable will be removed in the next major version. + * Cached result of getBrowserTimeOrigin. */ -export let _browserPerformanceTimeOriginMode: string; +let cachedTimeOrigin: [number | undefined, string] | undefined; /** - * The number of milliseconds since the UNIX epoch. This value is only usable in a browser, and only when the - * performance API is available. + * Gets the time origin and the mode used to determine it. */ -export const browserPerformanceTimeOrigin = ((): number | undefined => { +function getBrowserTimeOrigin(): [number | undefined, string] { // Unfortunately browsers may report an inaccurate time origin data, through either performance.timeOrigin or // performance.timing.navigationStart, which results in poor results in performance data. We only treat time origin // data as reliable if they are within a reasonable threshold of the current time. const { performance } = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window; if (!performance?.now) { - // eslint-disable-next-line deprecation/deprecation - _browserPerformanceTimeOriginMode = 'none'; - return undefined; + return [undefined, 'none']; } const threshold = 3600 * 1000; @@ -114,18 +109,24 @@ export const browserPerformanceTimeOrigin = ((): number | undefined => { if (timeOriginIsReliable || navigationStartIsReliable) { // Use the more reliable time origin if (timeOriginDelta <= navigationStartDelta) { - // eslint-disable-next-line deprecation/deprecation - _browserPerformanceTimeOriginMode = 'timeOrigin'; - return performance.timeOrigin; + return [performance.timeOrigin, 'timeOrigin']; } else { - // eslint-disable-next-line deprecation/deprecation - _browserPerformanceTimeOriginMode = 'navigationStart'; - return navigationStart; + return [navigationStart, 'navigationStart']; } } // Either both timeOrigin and navigationStart are skewed or neither is available, fallback to Date. - // eslint-disable-next-line deprecation/deprecation - _browserPerformanceTimeOriginMode = 'dateNow'; - return dateNow; -})(); + return [dateNow, 'dateNow']; +} + +/** + * The number of milliseconds since the UNIX epoch. This value is only usable in a browser, and only when the + * performance API is available. + */ +export function browserPerformanceTimeOrigin(): number | undefined { + if (!cachedTimeOrigin) { + cachedTimeOrigin = getBrowserTimeOrigin(); + } + + return cachedTimeOrigin[0]; +} diff --git a/packages/ember/addon/instance-initializers/sentry-performance.ts b/packages/ember/addon/instance-initializers/sentry-performance.ts index c4e4621e563f..180bbe992ea3 100644 --- a/packages/ember/addon/instance-initializers/sentry-performance.ts +++ b/packages/ember/addon/instance-initializers/sentry-performance.ts @@ -366,8 +366,9 @@ function _instrumentInitialLoad(config: EmberSentryConfig): void { return; } + const origin = browserPerformanceTimeOrigin(); // Split performance check in two so clearMarks still happens even if timeOrigin isn't available. - if (!HAS_PERFORMANCE_TIMING || browserPerformanceTimeOrigin === undefined) { + if (!HAS_PERFORMANCE_TIMING || origin === undefined) { return; } const measureName = '@sentry/ember:initial-load'; @@ -383,7 +384,7 @@ function _instrumentInitialLoad(config: EmberSentryConfig): void { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const measure = measures[0]!; - const startTime = (measure.startTime + browserPerformanceTimeOrigin) / 1000; + const startTime = (measure.startTime + origin) / 1000; const endTime = startTime + measure.duration / 1000; startInactiveSpan({ diff --git a/packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts b/packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts index cba58b7d992b..6ff2da7f9a37 100644 --- a/packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts +++ b/packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts @@ -11,10 +11,11 @@ export const INCOMPLETE_APP_ROUTER_INSTRUMENTATION_TRANSACTION_NAME = 'incomplet /** Instruments the Next.js app router for pageloads. */ export function appRouterInstrumentPageLoad(client: Client): void { + const origin = browserPerformanceTimeOrigin(); startBrowserTracingPageLoadSpan(client, { name: WINDOW.location.pathname, // pageload should always start at timeOrigin (and needs to be in s, not ms) - startTime: browserPerformanceTimeOrigin ? browserPerformanceTimeOrigin / 1000 : undefined, + startTime: origin ? origin / 1000 : undefined, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.nextjs.app_router_instrumentation', diff --git a/packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts b/packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts index 11e48b3cec40..018868fb0679 100644 --- a/packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts +++ b/packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts @@ -119,12 +119,13 @@ export function pagesRouterInstrumentPageLoad(client: Client): void { name = name.replace(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|TRACE|CONNECT)\s+/i, ''); } + const origin = browserPerformanceTimeOrigin(); startBrowserTracingPageLoadSpan( client, { name, // pageload should always start at timeOrigin (and needs to be in s, not ms) - startTime: browserPerformanceTimeOrigin ? browserPerformanceTimeOrigin / 1000 : undefined, + startTime: origin ? origin / 1000 : undefined, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.nextjs.pages_router_instrumentation', diff --git a/packages/replay-internal/src/util/createPerformanceEntries.ts b/packages/replay-internal/src/util/createPerformanceEntries.ts index 6d2fc726f5d4..ea691467fc04 100644 --- a/packages/replay-internal/src/util/createPerformanceEntries.ts +++ b/packages/replay-internal/src/util/createPerformanceEntries.ts @@ -89,7 +89,7 @@ function createPerformanceEntry(entry: AllPerformanceEntry): ReplayPerformanceEn function getAbsoluteTime(time: number): number { // browserPerformanceTimeOrigin can be undefined if `performance` or // `performance.now` doesn't exist, but this is already checked by this integration - return ((browserPerformanceTimeOrigin || WINDOW.performance.timeOrigin) + time) / 1000; + return ((browserPerformanceTimeOrigin() || WINDOW.performance.timeOrigin) + time) / 1000; } function createPaintEntry(entry: PerformancePaintTiming): ReplayPerformanceEntry { diff --git a/packages/replay-internal/test/integration/flush.test.ts b/packages/replay-internal/test/integration/flush.test.ts index 5de390581790..843fe6ceedfd 100644 --- a/packages/replay-internal/test/integration/flush.test.ts +++ b/packages/replay-internal/test/integration/flush.test.ts @@ -93,7 +93,7 @@ describe('Integration | flush', () => { mockEventBufferFinish.mockClear(); Object.defineProperty(SentryUtils, 'browserPerformanceTimeOrigin', { - value: BASE_TIMESTAMP, + value: () => BASE_TIMESTAMP, writable: true, }); }); @@ -107,7 +107,7 @@ describe('Integration | flush', () => { writable: true, }); Object.defineProperty(SentryUtils, 'browserPerformanceTimeOrigin', { - value: prevBrowserPerformanceTimeOrigin, + value: () => prevBrowserPerformanceTimeOrigin, writable: true, }); }); diff --git a/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts b/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts index 2e49ade50a26..8660d365d3e5 100644 --- a/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts +++ b/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts @@ -7,7 +7,7 @@ vi.setSystemTime(new Date('2023-01-01')); vi.mock('@sentry/core', async () => ({ ...(await vi.importActual('@sentry/core')), - browserPerformanceTimeOrigin: new Date('2023-01-01').getTime(), + browserPerformanceTimeOrigin: () => new Date('2023-01-01').getTime(), })); import { WINDOW } from '../../../src/constants';