From 5ba8c9d089cc303cb01c530c9b70f6fdecb60b57 Mon Sep 17 00:00:00 2001 From: KevinL10 Date: Tue, 25 Jun 2024 15:56:41 -0400 Subject: [PATCH 01/14] feat(tracing): add long animation frame tracking --- packages/browser-utils/src/index.ts | 1 + .../src/metrics/browserMetrics.ts | 30 +++++++++++++++++++ .../browser-utils/src/metrics/instrument.ts | 1 + .../src/tracing/browserTracingIntegration.ts | 15 ++++++++++ 4 files changed, 47 insertions(+) diff --git a/packages/browser-utils/src/index.ts b/packages/browser-utils/src/index.ts index f59ccbf8da8f..c71b2d70e31d 100644 --- a/packages/browser-utils/src/index.ts +++ b/packages/browser-utils/src/index.ts @@ -11,6 +11,7 @@ export { addPerformanceEntries, startTrackingInteractions, startTrackingLongTasks, + startTrackingLongAnimationFrames, startTrackingWebVitals, startTrackingINP, registerInpInteractionListener, diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index 6999641a641f..ae0dce615511 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -120,6 +120,36 @@ export function startTrackingLongTasks(): void { }); } +/** + * Start tracking long animation frames. + */ +export function startTrackingLongAnimationFrames(): void { + addPerformanceInstrumentationHandler('long-animation-frame', ({ entries }) => { + for (const entry of entries) { + if (!getActiveSpan()) { + return; + } + const startTime = msToSec((browserPerformanceTimeOrigin as number) + entry.startTime); + const duration = msToSec(entry.duration); + + + const span = startInactiveSpan({ + name: 'Main UI thread blocked', + op: 'ui.long-animation-frame', + startTime, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', + }, + }); + if (span) { + span.end(startTime + duration); + } + } + }); +} + + + /** * Start tracking interaction events. */ diff --git a/packages/browser-utils/src/metrics/instrument.ts b/packages/browser-utils/src/metrics/instrument.ts index e22a345e3116..10a3937f84d7 100644 --- a/packages/browser-utils/src/metrics/instrument.ts +++ b/packages/browser-utils/src/metrics/instrument.ts @@ -10,6 +10,7 @@ import { onTTFB } from './web-vitals/onTTFB'; type InstrumentHandlerTypePerformanceObserver = | 'longtask' + | 'long-animation-frame' | 'event' | 'navigation' | 'paint' diff --git a/packages/browser/src/tracing/browserTracingIntegration.ts b/packages/browser/src/tracing/browserTracingIntegration.ts index c058b1930928..68988f6515aa 100644 --- a/packages/browser/src/tracing/browserTracingIntegration.ts +++ b/packages/browser/src/tracing/browserTracingIntegration.ts @@ -6,6 +6,7 @@ import { startTrackingINP, startTrackingInteractions, startTrackingLongTasks, + startTrackingLongAnimationFrames, startTrackingWebVitals, } from '@sentry-internal/browser-utils'; import { @@ -102,6 +103,13 @@ export interface BrowserTracingOptions { */ enableLongTask: boolean; + /** + * If true, Sentry will capture long animation frames and add them to the corresponding transaction. + * + * Default: false + */ + enableLongAnimationFrame: boolean; + /** * If true, Sentry will capture first input delay and add it to the corresponding transaction. * @@ -160,6 +168,7 @@ const DEFAULT_BROWSER_TRACING_OPTIONS: BrowserTracingOptions = { instrumentPageLoad: true, markBackgroundSpan: true, enableLongTask: true, + enableLongAnimationFrame: false, enableInp: true, _experiments: {}, ...defaultRequestInstrumentationOptions, @@ -180,6 +189,7 @@ export const browserTracingIntegration = ((_options: Partial Date: Tue, 25 Jun 2024 16:06:48 -0400 Subject: [PATCH 02/14] add PerformanceScriptTiming definitions --- packages/browser-utils/src/metrics/instrument.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/browser-utils/src/metrics/instrument.ts b/packages/browser-utils/src/metrics/instrument.ts index 10a3937f84d7..9ce58b86a65f 100644 --- a/packages/browser-utils/src/metrics/instrument.ts +++ b/packages/browser-utils/src/metrics/instrument.ts @@ -37,6 +37,12 @@ interface PerformanceEventTiming extends PerformanceEntry { interactionId?: number; } +interface PerformanceScriptTiming extends PerformanceEntry { + sourceCharPosition: number; + sourceFunctionName: string; + sourceUrl: string; +} + interface Metric { /** * The name of the metric (in acronym form). @@ -163,6 +169,10 @@ export function addPerformanceInstrumentationHandler( type: 'event', callback: (data: { entries: ((PerformanceEntry & { target?: unknown | null }) | PerformanceEventTiming)[] }) => void, ): CleanupHandlerCallback; +export function addPerformanceInstrumentationHandler( + type: 'long-animation-frame', + callback: (data: { entries: (PerformanceEntry & {scripts?: PerformanceScriptTiming[]})[] }) => void, +): CleanupHandlerCallback; export function addPerformanceInstrumentationHandler( type: InstrumentHandlerTypePerformanceObserver, callback: (data: { entries: PerformanceEntry[] }) => void, From d4ff07d1d04d68b866727bcfc59f56ba1a928252 Mon Sep 17 00:00:00 2001 From: KevinL10 Date: Thu, 27 Jun 2024 13:53:09 -0400 Subject: [PATCH 03/14] cleanup --- .../src/metrics/browserMetrics.ts | 47 +++++++++---------- .../browser-utils/src/metrics/instrument.ts | 11 ----- 2 files changed, 23 insertions(+), 35 deletions(-) diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index ae0dce615511..91cd94a9c754 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -124,32 +124,31 @@ export function startTrackingLongTasks(): void { * Start tracking long animation frames. */ export function startTrackingLongAnimationFrames(): void { - addPerformanceInstrumentationHandler('long-animation-frame', ({ entries }) => { - for (const entry of entries) { - if (!getActiveSpan()) { - return; - } - const startTime = msToSec((browserPerformanceTimeOrigin as number) + entry.startTime); - const duration = msToSec(entry.duration); - - - const span = startInactiveSpan({ - name: 'Main UI thread blocked', - op: 'ui.long-animation-frame', - startTime, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', - }, - }); - if (span) { - span.end(startTime + duration); - } - } - }); + // NOTE: the current web-vitals version (3.5.2) does not support long-animation-frame, so + // we should directly observe `long-animation-frame` events here instead of through the web-vitals + // `observe` helper function. + // addPerformanceInstrumentationHandler('long-animation-frame', ({ entries }) => { + // for (const entry of entries) { + // if (!getActiveSpan()) { + // return; + // } + // const startTime = msToSec((browserPerformanceTimeOrigin as number) + entry.startTime); + // const duration = msToSec(entry.duration); + // const span = startInactiveSpan({ + // name: 'Main UI thread blocked', + // op: 'ui.long-animation-frame', + // startTime, + // attributes: { + // [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', + // }, + // }); + // if (span) { + // span.end(startTime + duration); + // } + // } + // }); } - - /** * Start tracking interaction events. */ diff --git a/packages/browser-utils/src/metrics/instrument.ts b/packages/browser-utils/src/metrics/instrument.ts index 9ce58b86a65f..e22a345e3116 100644 --- a/packages/browser-utils/src/metrics/instrument.ts +++ b/packages/browser-utils/src/metrics/instrument.ts @@ -10,7 +10,6 @@ import { onTTFB } from './web-vitals/onTTFB'; type InstrumentHandlerTypePerformanceObserver = | 'longtask' - | 'long-animation-frame' | 'event' | 'navigation' | 'paint' @@ -37,12 +36,6 @@ interface PerformanceEventTiming extends PerformanceEntry { interactionId?: number; } -interface PerformanceScriptTiming extends PerformanceEntry { - sourceCharPosition: number; - sourceFunctionName: string; - sourceUrl: string; -} - interface Metric { /** * The name of the metric (in acronym form). @@ -169,10 +162,6 @@ export function addPerformanceInstrumentationHandler( type: 'event', callback: (data: { entries: ((PerformanceEntry & { target?: unknown | null }) | PerformanceEventTiming)[] }) => void, ): CleanupHandlerCallback; -export function addPerformanceInstrumentationHandler( - type: 'long-animation-frame', - callback: (data: { entries: (PerformanceEntry & {scripts?: PerformanceScriptTiming[]})[] }) => void, -): CleanupHandlerCallback; export function addPerformanceInstrumentationHandler( type: InstrumentHandlerTypePerformanceObserver, callback: (data: { entries: PerformanceEntry[] }) => void, From c187e01103a77a1f0210408b42fa31777993472c Mon Sep 17 00:00:00 2001 From: KevinL10 Date: Fri, 5 Jul 2024 16:46:24 -0400 Subject: [PATCH 04/14] add tests and cleanup --- .../assets/script.js | 12 ++ .../long-animation-frame-disabled/init.js | 11 ++ .../template.html | 10 ++ .../long-animation-frame-disabled/test.ts | 27 +++++ .../assets/script.js | 25 ++++ .../long-animation-frame-enabled/init.js | 15 +++ .../template.html | 13 ++ .../long-animation-frame-enabled/test.ts | 112 ++++++++++++++++++ .../src/metrics/browserMetrics.ts | 53 +++++---- .../browser-utils/src/metrics/instrument.ts | 11 ++ 10 files changed, 268 insertions(+), 21 deletions(-) create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/assets/script.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/template.html create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/test.ts create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/assets/script.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/template.html create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/assets/script.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/assets/script.js new file mode 100644 index 000000000000..9ac3d6fb33d2 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/assets/script.js @@ -0,0 +1,12 @@ +(() => { + const startTime = Date.now(); + + function getElasped() { + const time = Date.now(); + return time - startTime; + } + + while (getElasped() < 101) { + // + } +})(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/init.js new file mode 100644 index 000000000000..e1b3f6b13b01 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/init.js @@ -0,0 +1,11 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [ + Sentry.browserTracingIntegration({ enableLongTask: false, enableLongAnimationFrame: false, idleTimeout: 9000 }), + ], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/template.html new file mode 100644 index 000000000000..4cd015b16f51 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/template.html @@ -0,0 +1,10 @@ + + + + + + +
Rendered Before Long Animation Frame
+ + + diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/test.ts new file mode 100644 index 000000000000..2527d5a67302 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/test.ts @@ -0,0 +1,27 @@ +import type { Route } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; + +sentryTest( + 'should not capture long animation frame when flag is disabled.', + async ({ browserName, getLocalTestPath, page }) => { + // Long animation frames only work on chrome + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + await page.route('**/path/to/script.js', (route: Route) => + route.fulfill({ path: `${__dirname}/assets/script.js` }), + ); + + const url = await getLocalTestPath({ testDir: __dirname }); + + const eventData = await getFirstSentryEnvelopeRequest(page, url); + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); + + expect(uiSpans?.length).toBe(0); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/assets/script.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/assets/script.js new file mode 100644 index 000000000000..10552eeb5bd5 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/assets/script.js @@ -0,0 +1,25 @@ +function getElapsed(startTime) { + const time = Date.now(); + return time - startTime; +} + +function handleClick() { + const startTime = Date.now(); + while (getElapsed(startTime) < 105) { + // + } +} + +function start() { + const startTime = Date.now(); + while (getElapsed(startTime) < 105) { + // + } +} + +// trigger 2 long-animation-frame events +// one from the top-level and the other from an event-listener +start(); + +const button = document.getElementById('clickme'); +button.addEventListener('click', handleClick); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/init.js new file mode 100644 index 000000000000..4be408ceab7e --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/init.js @@ -0,0 +1,15 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [ + Sentry.browserTracingIntegration({ + idleTimeout: 9000, + enableLongTask: false, + enableLongAnimationFrame: true, + }), + ], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/template.html new file mode 100644 index 000000000000..ed02d1117097 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/template.html @@ -0,0 +1,13 @@ + + + + + + +
Rendered Before Long Animation Frame
+ + + + diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts new file mode 100644 index 000000000000..25fc9dada1b9 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts @@ -0,0 +1,112 @@ +import type { Route } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; + +import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/browser'; +import { sentryTest } from '../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; + +sentryTest( + 'should capture long animation frame for top-level script.', + async ({ browserName, getLocalTestPath, page }) => { + // Long animation frames only work on chrome + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + await page.route('**/path/to/script.js', (route: Route) => + route.fulfill({ path: `${__dirname}/assets/script.js` }), + ); + + const url = await getLocalTestPath({ testDir: __dirname }); + + const promise = getFirstSentryEnvelopeRequest(page); + + await page.goto(url); + + const eventData = await promise; + + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long-animation-frame')); + + expect(uiSpans?.length).toEqual(1); + + const [topLevelUISpan] = uiSpans || []; + expect(topLevelUISpan).toEqual( + expect.objectContaining({ + op: 'ui.long-animation-frame', + description: 'Main UI thread blocked', + parent_span_id: eventData.contexts?.trace?.span_id, + data: { + 'script.source_url': 'https://example.com/path/to/script.js', + 'script.source_function_name': '', + 'script.source_char_position': 0, + 'script.invoker': 'https://example.com/path/to/script.js', + 'script.invoker_type': 'classic-script', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.long-animation-frame', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', + }, + }), + ); + const start = topLevelUISpan.start_timestamp ?? 0; + const end = topLevelUISpan.timestamp ?? 0; + const duration = end - start; + + expect(duration).toBeGreaterThanOrEqual(0.1); + expect(duration).toBeLessThanOrEqual(0.15); + }, +); + +sentryTest( + 'should capture long animation frame for event listener.', + async ({ browserName, getLocalTestPath, page }) => { + // Long animation frames only work on chrome + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + await page.route('**/path/to/script.js', (route: Route) => + route.fulfill({ path: `${__dirname}/assets/script.js` }), + ); + + const url = await getLocalTestPath({ testDir: __dirname }); + + const promise = getFirstSentryEnvelopeRequest(page); + + await page.goto(url); + + // trigger long animation frame function + await page.getByRole('button').click(); + + const eventData = await promise; + + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long-animation-frame')); + + expect(uiSpans?.length).toEqual(2); + + // ignore the first ui span (top-level long animation frame) + const [, eventListenerUISpan] = uiSpans || []; + + expect(eventListenerUISpan).toEqual( + expect.objectContaining({ + op: 'ui.long-animation-frame', + description: 'Main UI thread blocked', + parent_span_id: eventData.contexts?.trace?.span_id, + data: { + 'script.source_url': '', + 'script.source_function_name': '', + 'script.source_char_position': -1, + 'script.invoker': 'BUTTON#clickme.onclick', + 'script.invoker_type': 'event-listener', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.long-animation-frame', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', + }, + }), + ); + const start = eventListenerUISpan.start_timestamp ?? 0; + const end = eventListenerUISpan.timestamp ?? 0; + const duration = end - start; + + expect(duration).toBeGreaterThanOrEqual(0.1); + expect(duration).toBeLessThanOrEqual(0.15); + }, +); diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index 8de274c2c1de..d9b3638a7863 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -8,6 +8,7 @@ import { spanToJSON } from '@sentry/core'; import { DEBUG_BUILD } from '../debug-build'; import { WINDOW } from '../types'; import { + PerformanceLongAnimationFrameTiming, addClsInstrumentationHandler, addFidInstrumentationHandler, addLcpInstrumentationHandler, @@ -125,28 +126,38 @@ export function startTrackingLongTasks(): void { */ export function startTrackingLongAnimationFrames(): void { // NOTE: the current web-vitals version (3.5.2) does not support long-animation-frame, so - // we should directly observe `long-animation-frame` events here instead of through the web-vitals + // we directly observe `long-animation-frame` events instead of through the web-vitals // `observe` helper function. - // addPerformanceInstrumentationHandler('long-animation-frame', ({ entries }) => { - // for (const entry of entries) { - // if (!getActiveSpan()) { - // return; - // } - // const startTime = msToSec((browserPerformanceTimeOrigin as number) + entry.startTime); - // const duration = msToSec(entry.duration); - // const span = startInactiveSpan({ - // name: 'Main UI thread blocked', - // op: 'ui.long-animation-frame', - // startTime, - // attributes: { - // [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', - // }, - // }); - // if (span) { - // span.end(startTime + duration); - // } - // } - // }); + if (PerformanceObserver.supportedEntryTypes.includes('long-animation-frame')) { + const observer = new PerformanceObserver(list => { + for (const entry of list.getEntries() as PerformanceLongAnimationFrameTiming[]) { + if (!getActiveSpan()) { + return; + } + const startTime = msToSec((browserPerformanceTimeOrigin as number) + entry.startTime); + const duration = msToSec(entry.duration); + + const span = startInactiveSpan({ + name: 'Main UI thread blocked', + op: 'ui.long-animation-frame', + startTime, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', + 'script.source_url': entry.scripts[0]?.sourceURL, + 'script.source_function_name': entry.scripts[0]?.sourceFunctionName, + 'script.source_char_position': entry.scripts[0]?.sourceCharPosition, + 'script.invoker': entry.scripts[0]?.invoker, + 'script.invoker_type': entry.scripts[0]?.invokerType, + }, + }); + if (span) { + span.end(startTime + duration); + } + } + }); + + observer.observe({ type: 'long-animation-frame', buffered: true }); + } } /** diff --git a/packages/browser-utils/src/metrics/instrument.ts b/packages/browser-utils/src/metrics/instrument.ts index e22a345e3116..39292fb19b83 100644 --- a/packages/browser-utils/src/metrics/instrument.ts +++ b/packages/browser-utils/src/metrics/instrument.ts @@ -36,6 +36,17 @@ interface PerformanceEventTiming extends PerformanceEntry { interactionId?: number; } +interface PerformanceScriptTiming extends PerformanceEntry { + sourceURL: string; + sourceFunctionName: string; + sourceCharPosition: number; + invoker: string; + invokerType: string; +} +export interface PerformanceLongAnimationFrameTiming extends PerformanceEntry { + scripts: PerformanceScriptTiming[]; +} + interface Metric { /** * The name of the metric (in acronym form). From cb38cbda08def12c27e84eb067f9e80ca387f122 Mon Sep 17 00:00:00 2001 From: KevinL10 Date: Fri, 5 Jul 2024 16:52:02 -0400 Subject: [PATCH 05/14] fix import order --- packages/browser/src/tracing/browserTracingIntegration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/browser/src/tracing/browserTracingIntegration.ts b/packages/browser/src/tracing/browserTracingIntegration.ts index 68988f6515aa..a11a61dd994c 100644 --- a/packages/browser/src/tracing/browserTracingIntegration.ts +++ b/packages/browser/src/tracing/browserTracingIntegration.ts @@ -5,8 +5,8 @@ import { registerInpInteractionListener, startTrackingINP, startTrackingInteractions, - startTrackingLongTasks, startTrackingLongAnimationFrames, + startTrackingLongTasks, startTrackingWebVitals, } from '@sentry-internal/browser-utils'; import { From 4335beaa25c355a00ecfd8b39ace047d34af7514 Mon Sep 17 00:00:00 2001 From: KevinL10 Date: Mon, 8 Jul 2024 14:32:30 -0400 Subject: [PATCH 06/14] cleanup --- .../long-animation-frame-enabled/test.ts | 20 +++++++++---------- .../src/metrics/browserMetrics.ts | 16 +++++++++------ .../src/tracing/browserTracingIntegration.ts | 3 ++- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts index 25fc9dada1b9..5290251a2707 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts @@ -37,11 +37,11 @@ sentryTest( description: 'Main UI thread blocked', parent_span_id: eventData.contexts?.trace?.span_id, data: { - 'script.source_url': 'https://example.com/path/to/script.js', - 'script.source_function_name': '', - 'script.source_char_position': 0, - 'script.invoker': 'https://example.com/path/to/script.js', - 'script.invoker_type': 'classic-script', + 'code.filepath': 'https://example.com/path/to/script.js', + 'code.function': '', + 'browser.script.source_char_position': 0, + 'browser.script.invoker': 'https://example.com/path/to/script.js', + 'browser.script.invoker_type': 'classic-script', [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.long-animation-frame', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', }, @@ -92,11 +92,11 @@ sentryTest( description: 'Main UI thread blocked', parent_span_id: eventData.contexts?.trace?.span_id, data: { - 'script.source_url': '', - 'script.source_function_name': '', - 'script.source_char_position': -1, - 'script.invoker': 'BUTTON#clickme.onclick', - 'script.invoker_type': 'event-listener', + 'code.filepath': '', + 'code.function': '', + 'browser.script.source_char_position': -1, + 'browser.script.invoker': 'BUTTON#clickme.onclick', + 'browser.script.invoker_type': 'event-listener', [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.long-animation-frame', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', }, diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index d9b3638a7863..88beb48c7060 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -8,7 +8,7 @@ import { spanToJSON } from '@sentry/core'; import { DEBUG_BUILD } from '../debug-build'; import { WINDOW } from '../types'; import { - PerformanceLongAnimationFrameTiming, + type PerformanceLongAnimationFrameTiming, addClsInstrumentationHandler, addFidInstrumentationHandler, addLcpInstrumentationHandler, @@ -134,6 +134,10 @@ export function startTrackingLongAnimationFrames(): void { if (!getActiveSpan()) { return; } + if (!entry.scripts[0]) { + return; + } + const startTime = msToSec((browserPerformanceTimeOrigin as number) + entry.startTime); const duration = msToSec(entry.duration); @@ -143,11 +147,11 @@ export function startTrackingLongAnimationFrames(): void { startTime, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', - 'script.source_url': entry.scripts[0]?.sourceURL, - 'script.source_function_name': entry.scripts[0]?.sourceFunctionName, - 'script.source_char_position': entry.scripts[0]?.sourceCharPosition, - 'script.invoker': entry.scripts[0]?.invoker, - 'script.invoker_type': entry.scripts[0]?.invokerType, + 'code.filepath': entry.scripts[0].sourceURL, + 'code.function': entry.scripts[0].sourceFunctionName, + 'browser.script.source_char_position': entry.scripts[0].sourceCharPosition, + 'browser.script.invoker': entry.scripts[0].invoker, + 'browser.script.invoker_type': entry.scripts[0].invokerType, }, }); if (span) { diff --git a/packages/browser/src/tracing/browserTracingIntegration.ts b/packages/browser/src/tracing/browserTracingIntegration.ts index a11a61dd994c..559edb5a6b6d 100644 --- a/packages/browser/src/tracing/browserTracingIntegration.ts +++ b/packages/browser/src/tracing/browserTracingIntegration.ts @@ -217,7 +217,8 @@ export const browserTracingIntegration = ((_options: Partial Date: Mon, 8 Jul 2024 14:46:48 -0400 Subject: [PATCH 07/14] send attributes conditionally --- .../long-animation-frame-enabled/test.ts | 4 ---- packages/browser-utils/src/metrics/browserMetrics.ts | 12 +++++++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts index 5290251a2707..d2dc2e45571b 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts @@ -38,7 +38,6 @@ sentryTest( parent_span_id: eventData.contexts?.trace?.span_id, data: { 'code.filepath': 'https://example.com/path/to/script.js', - 'code.function': '', 'browser.script.source_char_position': 0, 'browser.script.invoker': 'https://example.com/path/to/script.js', 'browser.script.invoker_type': 'classic-script', @@ -92,9 +91,6 @@ sentryTest( description: 'Main UI thread blocked', parent_span_id: eventData.contexts?.trace?.span_id, data: { - 'code.filepath': '', - 'code.function': '', - 'browser.script.source_char_position': -1, 'browser.script.invoker': 'BUTTON#clickme.onclick', 'browser.script.invoker_type': 'event-listener', [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.long-animation-frame', diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index 88beb48c7060..4bb5041d7da3 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -147,11 +147,17 @@ export function startTrackingLongAnimationFrames(): void { startTime, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', - 'code.filepath': entry.scripts[0].sourceURL, - 'code.function': entry.scripts[0].sourceFunctionName, - 'browser.script.source_char_position': entry.scripts[0].sourceCharPosition, 'browser.script.invoker': entry.scripts[0].invoker, 'browser.script.invoker_type': entry.scripts[0].invokerType, + ...(entry.scripts[0].sourceURL && { + 'code.filepath': entry.scripts[0].sourceURL, + }), + ...(entry.scripts[0].sourceFunctionName && { + 'code.function': entry.scripts[0].sourceFunctionName, + }), + ...(entry.scripts[0].sourceCharPosition !== -1 && { + 'browser.script.source_char_position': entry.scripts[0].sourceCharPosition, + }), }, }); if (span) { From 10091d8f1e42d5b6f519147edab1f2f2bc762971 Mon Sep 17 00:00:00 2001 From: KevinL10 Date: Mon, 8 Jul 2024 15:02:33 -0400 Subject: [PATCH 08/14] move undefined check later --- .../src/metrics/browserMetrics.ts | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index 4bb5041d7da3..ac4ef1be3871 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -141,24 +141,27 @@ export function startTrackingLongAnimationFrames(): void { const startTime = msToSec((browserPerformanceTimeOrigin as number) + entry.startTime); const duration = msToSec(entry.duration); + const attributes: SpanAttributes = { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics' }; + if (entry.scripts[0]) { + attributes['browser.script.invoker'] = entry.scripts[0].invoker; + attributes['browser.script.invoker_type'] = entry.scripts[0].invokerType; + + if (entry.scripts[0].sourceURL.length > 0) { + attributes['code.filepath'] = entry.scripts[0].sourceURL; + } + if (entry.scripts[0].sourceFunctionName.length > 0) { + attributes['code.function'] = entry.scripts[0].sourceFunctionName; + } + if (entry.scripts[0].sourceCharPosition !== -1) { + attributes['browser.script.source_char_position'] = entry.scripts[0].sourceCharPosition; + } + } + const span = startInactiveSpan({ name: 'Main UI thread blocked', op: 'ui.long-animation-frame', startTime, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', - 'browser.script.invoker': entry.scripts[0].invoker, - 'browser.script.invoker_type': entry.scripts[0].invokerType, - ...(entry.scripts[0].sourceURL && { - 'code.filepath': entry.scripts[0].sourceURL, - }), - ...(entry.scripts[0].sourceFunctionName && { - 'code.function': entry.scripts[0].sourceFunctionName, - }), - ...(entry.scripts[0].sourceCharPosition !== -1 && { - 'browser.script.source_char_position': entry.scripts[0].sourceCharPosition, - }), - }, + attributes, }); if (span) { span.end(startTime + duration); From 66f8f7d3ae7221859350692f4e192c6fadf2ded7 Mon Sep 17 00:00:00 2001 From: KevinL10 Date: Mon, 8 Jul 2024 15:22:30 -0400 Subject: [PATCH 09/14] fix flakiness --- .../long-animation-frame-enabled/test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts index d2dc2e45571b..1aa9b1543e34 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts @@ -24,6 +24,8 @@ sentryTest( await page.goto(url); + await new Promise(resolve => setTimeout(resolve, 200)); + const eventData = await promise; const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long-animation-frame')); @@ -76,6 +78,8 @@ sentryTest( // trigger long animation frame function await page.getByRole('button').click(); + await new Promise(resolve => setTimeout(resolve, 200)); + const eventData = await promise; const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long-animation-frame')); From de91078d116b1ed9c90a69fcaa65ea845ff261fe Mon Sep 17 00:00:00 2001 From: KevinL10 Date: Mon, 8 Jul 2024 16:30:58 -0400 Subject: [PATCH 10/14] avoid entry.scripts[0] duplication --- .../src/metrics/browserMetrics.ts | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index ac4ef1be3871..acc6439d468c 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -141,19 +141,22 @@ export function startTrackingLongAnimationFrames(): void { const startTime = msToSec((browserPerformanceTimeOrigin as number) + entry.startTime); const duration = msToSec(entry.duration); - const attributes: SpanAttributes = { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics' }; - if (entry.scripts[0]) { - attributes['browser.script.invoker'] = entry.scripts[0].invoker; - attributes['browser.script.invoker_type'] = entry.scripts[0].invokerType; - - if (entry.scripts[0].sourceURL.length > 0) { - attributes['code.filepath'] = entry.scripts[0].sourceURL; + const attributes: SpanAttributes = { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', + }; + const initialScript = entry.scripts[0]; + if (initialScript) { + const { invoker, invokerType, sourceURL, sourceFunctionName, sourceCharPosition } = initialScript; + attributes['browser.script.invoker'] = invoker; + attributes['browser.script.invoker_type'] = invokerType; + if (sourceURL) { + attributes['code.filepath'] = sourceURL; } - if (entry.scripts[0].sourceFunctionName.length > 0) { - attributes['code.function'] = entry.scripts[0].sourceFunctionName; + if (sourceFunctionName) { + attributes['code.function'] = sourceFunctionName; } - if (entry.scripts[0].sourceCharPosition !== -1) { - attributes['browser.script.source_char_position'] = entry.scripts[0].sourceCharPosition; + if (sourceCharPosition !== -1) { + attributes['browser.script.source_char_position'] = sourceCharPosition; } } From e8fc14371fcf6f1dd48f05d3e8a258e6f9827770 Mon Sep 17 00:00:00 2001 From: KevinL10 Date: Mon, 8 Jul 2024 16:34:33 -0400 Subject: [PATCH 11/14] fix enable loaf logic --- .../src/metrics/browserMetrics.ts | 78 +++++++++---------- .../src/tracing/browserTracingIntegration.ts | 9 +-- 2 files changed, 41 insertions(+), 46 deletions(-) diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index acc6439d468c..cb48c2e8b675 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -128,52 +128,50 @@ export function startTrackingLongAnimationFrames(): void { // NOTE: the current web-vitals version (3.5.2) does not support long-animation-frame, so // we directly observe `long-animation-frame` events instead of through the web-vitals // `observe` helper function. - if (PerformanceObserver.supportedEntryTypes.includes('long-animation-frame')) { - const observer = new PerformanceObserver(list => { - for (const entry of list.getEntries() as PerformanceLongAnimationFrameTiming[]) { - if (!getActiveSpan()) { - return; - } - if (!entry.scripts[0]) { - return; - } + const observer = new PerformanceObserver(list => { + for (const entry of list.getEntries() as PerformanceLongAnimationFrameTiming[]) { + if (!getActiveSpan()) { + return; + } + if (!entry.scripts[0]) { + return; + } - const startTime = msToSec((browserPerformanceTimeOrigin as number) + entry.startTime); - const duration = msToSec(entry.duration); + const startTime = msToSec((browserPerformanceTimeOrigin as number) + entry.startTime); + const duration = msToSec(entry.duration); - const attributes: SpanAttributes = { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', - }; - const initialScript = entry.scripts[0]; - if (initialScript) { - const { invoker, invokerType, sourceURL, sourceFunctionName, sourceCharPosition } = initialScript; - attributes['browser.script.invoker'] = invoker; - attributes['browser.script.invoker_type'] = invokerType; - if (sourceURL) { - attributes['code.filepath'] = sourceURL; - } - if (sourceFunctionName) { - attributes['code.function'] = sourceFunctionName; - } - if (sourceCharPosition !== -1) { - attributes['browser.script.source_char_position'] = sourceCharPosition; - } + const attributes: SpanAttributes = { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', + }; + const initialScript = entry.scripts[0]; + if (initialScript) { + const { invoker, invokerType, sourceURL, sourceFunctionName, sourceCharPosition } = initialScript; + attributes['browser.script.invoker'] = invoker; + attributes['browser.script.invoker_type'] = invokerType; + if (sourceURL) { + attributes['code.filepath'] = sourceURL; } - - const span = startInactiveSpan({ - name: 'Main UI thread blocked', - op: 'ui.long-animation-frame', - startTime, - attributes, - }); - if (span) { - span.end(startTime + duration); + if (sourceFunctionName) { + attributes['code.function'] = sourceFunctionName; + } + if (sourceCharPosition !== -1) { + attributes['browser.script.source_char_position'] = sourceCharPosition; } } - }); - observer.observe({ type: 'long-animation-frame', buffered: true }); - } + const span = startInactiveSpan({ + name: 'Main UI thread blocked', + op: 'ui.long-animation-frame', + startTime, + attributes, + }); + if (span) { + span.end(startTime + duration); + } + } + }); + + observer.observe({ type: 'long-animation-frame', buffered: true }); } /** diff --git a/packages/browser/src/tracing/browserTracingIntegration.ts b/packages/browser/src/tracing/browserTracingIntegration.ts index 559edb5a6b6d..0423831219e2 100644 --- a/packages/browser/src/tracing/browserTracingIntegration.ts +++ b/packages/browser/src/tracing/browserTracingIntegration.ts @@ -213,13 +213,10 @@ export const browserTracingIntegration = ((_options: Partial Date: Tue, 9 Jul 2024 14:19:51 -0400 Subject: [PATCH 12/14] add additional loaf tests --- .../assets/script.js | 12 ++++++ .../long-animation-frame-non-chromium/init.js | 11 ++++++ .../template.html | 10 +++++ .../long-animation-frame-non-chromium/test.ts | 27 ++++++++++++++ .../assets/script.js | 12 ++++++ .../long-tasks-no-animation-frame/init.js | 15 ++++++++ .../template.html | 10 +++++ .../long-tasks-no-animation-frame/test.ts | 37 +++++++++++++++++++ 8 files changed, 134 insertions(+) create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/assets/script.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/template.html create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/test.ts create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/assets/script.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/template.html create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/test.ts diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/assets/script.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/assets/script.js new file mode 100644 index 000000000000..9ac3d6fb33d2 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/assets/script.js @@ -0,0 +1,12 @@ +(() => { + const startTime = Date.now(); + + function getElasped() { + const time = Date.now(); + return time - startTime; + } + + while (getElasped() < 101) { + // + } +})(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/init.js new file mode 100644 index 000000000000..ca1bf10dcddd --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/init.js @@ -0,0 +1,11 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [ + Sentry.browserTracingIntegration({ enableLongTask: true, enableLongAnimationFrame: true, idleTimeout: 9000 }), + ], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/template.html new file mode 100644 index 000000000000..5c3a14114991 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/template.html @@ -0,0 +1,10 @@ + + + + + + +
Rendered Before Long Task
+ + + diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/test.ts new file mode 100644 index 000000000000..65fb6664ac82 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/test.ts @@ -0,0 +1,27 @@ +import type { Route } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; + +sentryTest( + 'should not capture long animation frame or long task when browser is non-chromium', + async ({ browserName, getLocalTestPath, page }) => { + // Only test non-chromium browsers + if (shouldSkipTracingTest() || browserName === 'chromium') { + sentryTest.skip(); + } + + await page.route('**/path/to/script.js', (route: Route) => + route.fulfill({ path: `${__dirname}/assets/script.js` }), + ); + + const url = await getLocalTestPath({ testDir: __dirname }); + + const eventData = await getFirstSentryEnvelopeRequest(page, url); + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); + + expect(uiSpans?.length).toBe(0); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/assets/script.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/assets/script.js new file mode 100644 index 000000000000..5a2aef02028d --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/assets/script.js @@ -0,0 +1,12 @@ +(() => { + const startTime = Date.now(); + + function getElasped() { + const time = Date.now(); + return time - startTime; + } + + while (getElasped() < 105) { + // + } +})(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/init.js new file mode 100644 index 000000000000..0e35db50764f --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/init.js @@ -0,0 +1,15 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [ + Sentry.browserTracingIntegration({ + idleTimeout: 9000, + enableLongTask: true, + enableLongAnimationFrame: false, + }), + ], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/template.html new file mode 100644 index 000000000000..5c3a14114991 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/template.html @@ -0,0 +1,10 @@ + + + + + + +
Rendered Before Long Task
+ + + diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/test.ts new file mode 100644 index 000000000000..6189758c0340 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/test.ts @@ -0,0 +1,37 @@ +import type { Route } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; + +sentryTest('should capture long task.', async ({ browserName, getLocalTestPath, page }) => { + // Long tasks only work on chrome + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` })); + + const url = await getLocalTestPath({ testDir: __dirname }); + + const eventData = await getFirstSentryEnvelopeRequest(page, url); + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); + + expect(uiSpans?.length).toBeGreaterThan(0); + + const [firstUISpan] = uiSpans || []; + expect(firstUISpan).toEqual( + expect.objectContaining({ + op: 'ui.long-task', + description: 'Main UI thread blocked', + parent_span_id: eventData.contexts?.trace?.span_id, + }), + ); + const start = firstUISpan.start_timestamp ?? 0; + const end = firstUISpan.timestamp ?? 0; + const duration = end - start; + + expect(duration).toBeGreaterThanOrEqual(0.1); + expect(duration).toBeLessThanOrEqual(0.15); +}); From 5d90e811787f555d98e7c1005fde01de27e15fa0 Mon Sep 17 00:00:00 2001 From: KevinL10 Date: Tue, 9 Jul 2024 14:26:04 -0400 Subject: [PATCH 13/14] add tests for both long task and loaf enabled --- .../long-animation-frame-enabled/test.ts | 4 +- .../assets/script.js | 25 ++++ .../init.js | 15 +++ .../template.html | 13 ++ .../test.ts | 114 ++++++++++++++++++ 5 files changed, 169 insertions(+), 2 deletions(-) create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/assets/script.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/template.html create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts index 1aa9b1543e34..850e75dbed1f 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts @@ -24,7 +24,7 @@ sentryTest( await page.goto(url); - await new Promise(resolve => setTimeout(resolve, 200)); + await new Promise(resolve => setTimeout(resolve, 1000)); const eventData = await promise; @@ -78,7 +78,7 @@ sentryTest( // trigger long animation frame function await page.getByRole('button').click(); - await new Promise(resolve => setTimeout(resolve, 200)); + await new Promise(resolve => setTimeout(resolve, 1000)); const eventData = await promise; diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/assets/script.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/assets/script.js new file mode 100644 index 000000000000..10552eeb5bd5 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/assets/script.js @@ -0,0 +1,25 @@ +function getElapsed(startTime) { + const time = Date.now(); + return time - startTime; +} + +function handleClick() { + const startTime = Date.now(); + while (getElapsed(startTime) < 105) { + // + } +} + +function start() { + const startTime = Date.now(); + while (getElapsed(startTime) < 105) { + // + } +} + +// trigger 2 long-animation-frame events +// one from the top-level and the other from an event-listener +start(); + +const button = document.getElementById('clickme'); +button.addEventListener('click', handleClick); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/init.js new file mode 100644 index 000000000000..d81b8932803c --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/init.js @@ -0,0 +1,15 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [ + Sentry.browserTracingIntegration({ + idleTimeout: 9000, + enableLongTask: true, + enableLongAnimationFrame: true, + }), + ], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/template.html new file mode 100644 index 000000000000..ed02d1117097 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/template.html @@ -0,0 +1,13 @@ + + + + + + +
Rendered Before Long Animation Frame
+ + + + diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts new file mode 100644 index 000000000000..1949e44bd398 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts @@ -0,0 +1,114 @@ +import type { Route } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; + +import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/browser'; +import { sentryTest } from '../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; + +sentryTest( + 'should capture long animation frame for top-level script.', + async ({ browserName, getLocalTestPath, page }) => { + // Long animation frames only work on chrome + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + // Long animation frame should take priority over long tasks + + await page.route('**/path/to/script.js', (route: Route) => + route.fulfill({ path: `${__dirname}/assets/script.js` }), + ); + + const url = await getLocalTestPath({ testDir: __dirname }); + + const promise = getFirstSentryEnvelopeRequest(page); + + await page.goto(url); + + await new Promise(resolve => setTimeout(resolve, 1000)); + + const eventData = await promise; + + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long-animation-frame')); + + expect(uiSpans?.length).toEqual(1); + + const [topLevelUISpan] = uiSpans || []; + expect(topLevelUISpan).toEqual( + expect.objectContaining({ + op: 'ui.long-animation-frame', + description: 'Main UI thread blocked', + parent_span_id: eventData.contexts?.trace?.span_id, + data: { + 'code.filepath': 'https://example.com/path/to/script.js', + 'browser.script.source_char_position': 0, + 'browser.script.invoker': 'https://example.com/path/to/script.js', + 'browser.script.invoker_type': 'classic-script', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.long-animation-frame', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', + }, + }), + ); + const start = topLevelUISpan.start_timestamp ?? 0; + const end = topLevelUISpan.timestamp ?? 0; + const duration = end - start; + + expect(duration).toBeGreaterThanOrEqual(0.1); + expect(duration).toBeLessThanOrEqual(0.15); + }, +); + +sentryTest( + 'should capture long animation frame for event listener.', + async ({ browserName, getLocalTestPath, page }) => { + // Long animation frames only work on chrome + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + await page.route('**/path/to/script.js', (route: Route) => + route.fulfill({ path: `${__dirname}/assets/script.js` }), + ); + + const url = await getLocalTestPath({ testDir: __dirname }); + + const promise = getFirstSentryEnvelopeRequest(page); + + await page.goto(url); + + // trigger long animation frame function + await page.getByRole('button').click(); + + await new Promise(resolve => setTimeout(resolve, 1000)); + + const eventData = await promise; + + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long-animation-frame')); + + expect(uiSpans?.length).toEqual(2); + + // ignore the first ui span (top-level long animation frame) + const [, eventListenerUISpan] = uiSpans || []; + + expect(eventListenerUISpan).toEqual( + expect.objectContaining({ + op: 'ui.long-animation-frame', + description: 'Main UI thread blocked', + parent_span_id: eventData.contexts?.trace?.span_id, + data: { + 'browser.script.invoker': 'BUTTON#clickme.onclick', + 'browser.script.invoker_type': 'event-listener', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.long-animation-frame', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', + }, + }), + ); + const start = eventListenerUISpan.start_timestamp ?? 0; + const end = eventListenerUISpan.timestamp ?? 0; + const duration = end - start; + + expect(duration).toBeGreaterThanOrEqual(0.1); + expect(duration).toBeLessThanOrEqual(0.15); + }, +); From 9f38f30af34751af984600544953632bc0fbd5f8 Mon Sep 17 00:00:00 2001 From: KevinL10 Date: Tue, 9 Jul 2024 16:57:46 -0400 Subject: [PATCH 14/14] bump size limit for cdn bundle --- .size-limit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limit.js b/.size-limit.js index 636b9c64413a..a5ce210ef737 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -177,7 +177,7 @@ module.exports = [ path: createCDNPath('bundle.tracing.replay.min.js'), gzip: false, brotli: false, - limit: '220 KB', + limit: '221 KB', }, { name: 'CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed',