diff --git a/dev-packages/e2e-tests/test-applications/lighthouse-react/package.json b/dev-packages/e2e-tests/test-applications/lighthouse-react/package.json index 295c5d721e70..2daa5dd658a4 100644 --- a/dev-packages/e2e-tests/test-applications/lighthouse-react/package.json +++ b/dev-packages/e2e-tests/test-applications/lighthouse-react/package.json @@ -10,9 +10,12 @@ "build:no-sentry": "vite build --mode no-sentry", "build:init-only": "vite build --mode init-only", "build:errors-only": "vite build --mode errors-only", + "build:minimal-integrations": "vite build --mode minimal-integrations", "build:no-integrations": "vite build --mode no-integrations", "build:no-browser-api-errors": "vite build --mode no-browser-api-errors", + "build:no-breadcrumbs": "vite build --mode no-breadcrumbs", "build:tracing": "vite build --mode tracing", + "build:tracing-lazy-import": "vite build --mode tracing-lazy-import", "build:tracing-replay": "vite build --mode tracing-replay", "preview": "vite preview", "clean": "npx rimraf node_modules pnpm-lock.yaml dist", diff --git a/dev-packages/e2e-tests/test-applications/lighthouse-react/src/App.tsx b/dev-packages/e2e-tests/test-applications/lighthouse-react/src/App.tsx index 95eb8a9391b5..bf6e99e7419d 100644 --- a/dev-packages/e2e-tests/test-applications/lighthouse-react/src/App.tsx +++ b/dev-packages/e2e-tests/test-applications/lighthouse-react/src/App.tsx @@ -27,7 +27,7 @@ export default function App() {
- Lighthouse logo + Lighthouse logo

Lighthouse Fixture

This app exists to measure JavaScript bundle size and runtime cost across three Sentry instrumentation diff --git a/dev-packages/e2e-tests/test-applications/lighthouse-react/src/main.tsx b/dev-packages/e2e-tests/test-applications/lighthouse-react/src/main.tsx index c1ecc85ea148..0a8100c3e485 100644 --- a/dev-packages/e2e-tests/test-applications/lighthouse-react/src/main.tsx +++ b/dev-packages/e2e-tests/test-applications/lighthouse-react/src/main.tsx @@ -3,6 +3,13 @@ import { createRoot } from 'react-dom/client'; import App from './App'; const sentryInitStart = performance.now(); + +performance.measure('sentry-sdk-pre-init-duration', { + detail: { mode: import.meta.env.MODE ?? 'unknown_mode' }, + start: 0, + end: sentryInitStart, +}); + performance.mark('sentry-sdk-init-start', { detail: { mode: import.meta.env.MODE ?? 'unknown_mode' }, }); @@ -26,6 +33,20 @@ if (import.meta.env.MODE === 'tracing-replay') { integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1.0, }); +} else if (import.meta.env.MODE === 'tracing-lazy-import') { + // Tracing + errors, but browsertracing loaded lazily. + // (We don't recommend this setup anywhere and neither will it work well. + // this is purely for testing if it changes anything about overhead.) + Sentry.init({ + dsn: import.meta.env.VITE_E2E_TEST_DSN as string | undefined, + release: 'lighthouse-fixture', + environment: 'qa', + tracesSampleRate: 1.0, + }); + + import('@sentry/react').then(lazySentry => { + Sentry.addIntegration(lazySentry.browserTracingIntegration()); + }); } else if (import.meta.env.MODE === 'errors-only') { // Default integrations only — errors are always captured, no tracing or replay. Sentry.init({ @@ -33,6 +54,22 @@ if (import.meta.env.MODE === 'tracing-replay') { release: 'lighthouse-fixture', environment: 'qa', }); +} else if (import.meta.env.MODE === 'minimal-integrations') { + // Minimal integratoins setup only (everything necessary to automatically get errors) + Sentry.init({ + dsn: import.meta.env.VITE_E2E_TEST_DSN as string | undefined, + release: 'lighthouse-fixture', + environment: 'qa', + defaultIntegrations: false, + integrations: [ + Sentry.globalHandlersIntegration(), + Sentry.linkedErrorsIntegration(), + Sentry.dedupeIntegration(), + // for good measure, let's include event filters since noise reduction is usually desired + // 99% of this integration's work is done in an event processor, so outside the hot path + Sentry.eventFiltersIntegration(), + ], + }); } else if (import.meta.env.MODE === 'no-integrations') { // DSN set but every integration disabled. Isolates the cost of the enabled // client itself from the default instrumentation that wraps DOM/timer/network APIs. @@ -54,6 +91,15 @@ if (import.meta.env.MODE === 'tracing-replay') { integrations: defaultIntegrations => defaultIntegrations.filter(integration => integration.name !== 'BrowserApiErrors'), }); +} else if (import.meta.env.MODE === 'no-breadcrumbs') { + // Default integrations minus Breadcrumbs, which adds a lot of monkey patching to + // DOM and Network APIs as well as event targets and listeners + Sentry.init({ + dsn: import.meta.env.VITE_E2E_TEST_DSN as string | undefined, + release: 'lighthouse-fixture', + environment: 'qa', + integrations: defaultIntegrations => defaultIntegrations.filter(integration => integration.name !== 'Breadcrumbs'), + }); } else if (import.meta.env.MODE === 'init-only') { // enabled: false makes the SDK a guaranteed no-op (no transport allocation, // no DSN warning). We're measuring pure SDK-loading + tree-shaking cost. @@ -72,5 +118,30 @@ performance.mark('sentry-sdk-init-end', { // 'no-sentry' mode: all branches above are statically dead, so Vite drops // the @sentry/react import entirely from the bundle. +// Elements tagged with `elementtiming` (e.g. the hero logo) emit a +// PerformanceElementTiming entry when painted. Re-emit each as a +// performance.measure spanning time origin -> render so the element's render +// duration lands on the same timeline as the SDK init marks. +// `PerformanceElementTiming` isn't in this project's TS lib, so type it locally. +type ElementTimingEntry = PerformanceEntry & { + identifier: string; + renderTime: number; + loadTime: number; +}; + +const elementTimingObserver = new PerformanceObserver(list => { + for (const entry of list.getEntries() as ElementTimingEntry[]) { + const renderEnd = entry.renderTime || entry.loadTime; + + performance.measure(`element-timing-${entry.identifier}`, { + detail: { mode: import.meta.env.MODE ?? 'unknown_mode', identifier: entry.identifier }, + start: 0, + end: renderEnd, + }); + } +}); + +elementTimingObserver.observe({ type: 'element', buffered: true }); + const root = createRoot(document.getElementById('root')!); root.render(); diff --git a/scripts/lighthouse-bundle-and-upload.mjs b/scripts/lighthouse-bundle-and-upload.mjs index 4d0d3b995e52..982bf76c7ec4 100644 --- a/scripts/lighthouse-bundle-and-upload.mjs +++ b/scripts/lighthouse-bundle-and-upload.mjs @@ -1,12 +1,12 @@ /** * Bundle the `lighthouse-react` test app for each mode (no-sentry, init-only, - * errors-only, no-integrations, no-browser-api-errors, tracing, tracing-replay) - * and POST the tarballs to the Sentry Lighthouse lab - * (https://lighthouse.sentry.gg). The lab runs Lighthouse asynchronously and - * ships results to Sentry on its own schedule — this script exits as soon as - * the upload succeeds. + * errors-only, minimal-integrations, no-integrations, no-browser-api-errors, + * no-breadcrumbs, tracing, tracing-lazy-import, tracing-replay) and POST the + * tarballs to the Sentry Lighthouse lab (https://lighthouse.sentry.gg). The lab + * runs Lighthouse asynchronously and ships results to Sentry on its own + * schedule — this script exits as soon as the upload succeeds. * - * Single-app static matrix: 1 app × 7 modes = 7 cells. + * Single-app static matrix: 1 app × 10 modes = 10 cells. * * Zero runtime dependencies — uses Node 22 builtins (fetch, FormData, Blob) and * the system `tar`. Every external command is invoked via `execFileSync` with @@ -38,9 +38,12 @@ const MODES = [ 'no-sentry', 'init-only', 'errors-only', + 'minimal-integrations', 'no-integrations', 'no-browser-api-errors', + 'no-breadcrumbs', 'tracing', + 'tracing-lazy-import', 'tracing-replay', ]; const STATIC_DIR = 'dist';