Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tracing): add long animation frame tracing #12646

Merged
merged 15 commits into from
Jul 10, 2024
2 changes: 1 addition & 1 deletion .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
(() => {
const startTime = Date.now();

function getElasped() {
const time = Date.now();
return time - startTime;
}

while (getElasped() < 101) {
//
}
})();
Original file line number Diff line number Diff line change
@@ -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,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<div>Rendered Before Long Animation Frame</div>
<script src="https://example.com/path/to/script.js"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -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<Event>(page, url);
const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui'));

expect(uiSpans?.length).toBe(0);
},
);
Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
@@ -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,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<div>Rendered Before Long Animation Frame</div>
<button id="clickme">
click me to start the long animation!
</button>
<script src="https://example.com/path/to/script.js"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -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<Event>(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<Event>(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);
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
(() => {
const startTime = Date.now();

function getElasped() {
const time = Date.now();
return time - startTime;
}

while (getElasped() < 101) {
//
}
})();
Original file line number Diff line number Diff line change
@@ -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,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<div>Rendered Before Long Task</div>
<script src="https://example.com/path/to/script.js"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -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<Event>(page, url);
const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui'));

expect(uiSpans?.length).toBe(0);
},
);
Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
@@ -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,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<div>Rendered Before Long Animation Frame</div>
<button id="clickme">
click me to start the long animation!
</button>
<script src="https://example.com/path/to/script.js"></script>
</body>
</html>
Loading
Loading