Skip to content

Commit dbd8340

Browse files
authored
feat(tracing): Add long task collection (#5529)
* rfc(perf): Add long task collection This adds long task collection to represent long running ui behaviour as spans in your frontend transactions. Can be disabled by passing an experimental flag for now.
1 parent 1718e98 commit dbd8340

File tree

13 files changed

+174
-5
lines changed

13 files changed

+174
-5
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
(() => {
2+
const startTime = Date.now();
3+
4+
function getElasped() {
5+
const time = Date.now();
6+
return time - startTime;
7+
}
8+
9+
while (getElasped() < 101) {
10+
//
11+
}
12+
})();
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import * as Sentry from '@sentry/browser';
2+
import { Integrations } from '@sentry/tracing';
3+
4+
window.Sentry = Sentry;
5+
6+
Sentry.init({
7+
dsn: 'https://[email protected]/1337',
8+
integrations: [new Integrations.BrowserTracing({ _experiments: { enableLongTasks: false }, idleTimeout: 9000 })],
9+
tracesSampleRate: 1,
10+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<html>
2+
<head>
3+
<meta charset="utf-8" />
4+
</head>
5+
<body>
6+
<div>Rendered Before Long Task</div>
7+
<script src="https://example.com/path/to/script.js"></script>
8+
</body>
9+
</html>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { expect, Route } from '@playwright/test';
2+
import { Event } from '@sentry/types';
3+
4+
import { sentryTest } from '../../../../utils/fixtures';
5+
import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers';
6+
7+
sentryTest('should not capture long task when flag is disabled.', async ({ browserName, getLocalTestPath, page }) => {
8+
// Long tasks only work on chrome
9+
if (browserName !== 'chromium') {
10+
sentryTest.skip();
11+
}
12+
13+
await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` }));
14+
15+
const url = await getLocalTestPath({ testDir: __dirname });
16+
17+
const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);
18+
const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui'));
19+
20+
expect(uiSpans?.length).toBe(0);
21+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
(() => {
2+
const startTime = Date.now();
3+
4+
function getElasped() {
5+
const time = Date.now();
6+
return time - startTime;
7+
}
8+
9+
while (getElasped() < 105) {
10+
//
11+
}
12+
})();
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import * as Sentry from '@sentry/browser';
2+
import { Integrations } from '@sentry/tracing';
3+
4+
window.Sentry = Sentry;
5+
6+
Sentry.init({
7+
dsn: 'https://[email protected]/1337',
8+
integrations: [
9+
new Integrations.BrowserTracing({
10+
idleTimeout: 9000,
11+
}),
12+
],
13+
tracesSampleRate: 1,
14+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<html>
2+
<head>
3+
<meta charset="utf-8" />
4+
</head>
5+
<body>
6+
<div>Rendered Before Long Task</div>
7+
<script src="https://example.com/path/to/script.js"></script>
8+
</body>
9+
</html>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { expect, Route } from '@playwright/test';
2+
import { Event } from '@sentry/types';
3+
4+
import { sentryTest } from '../../../../utils/fixtures';
5+
import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers';
6+
7+
sentryTest('should capture long task.', async ({ browserName, getLocalTestPath, page }) => {
8+
// Long tasks only work on chrome
9+
if (browserName !== 'chromium') {
10+
sentryTest.skip();
11+
}
12+
13+
await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` }));
14+
15+
const url = await getLocalTestPath({ testDir: __dirname });
16+
17+
const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);
18+
const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui'));
19+
20+
expect(uiSpans?.length).toBe(1);
21+
22+
const [firstUISpan] = uiSpans || [];
23+
expect(firstUISpan).toEqual(
24+
expect.objectContaining({
25+
op: 'ui.long-task',
26+
description: 'Long Task',
27+
parent_span_id: eventData.contexts?.trace.span_id,
28+
}),
29+
);
30+
const start = firstUISpan['start_timestamp'] ?? 0;
31+
const end = firstUISpan['timestamp'] ?? 0;
32+
const duration = end - start;
33+
34+
expect(duration).toBeGreaterThanOrEqual(0.1);
35+
expect(duration).toBeLessThanOrEqual(0.15);
36+
});

packages/nextjs/test/integration/test/client/tracingFetch.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
const { expectRequestCount, isTransactionRequest, expectTransaction } = require('../utils/client');
1+
const {
2+
expectRequestCount,
3+
isTransactionRequest,
4+
expectTransaction,
5+
extractEnvelopeFromRequest,
6+
} = require('../utils/client');
27

38
module.exports = async ({ page, url, requests }) => {
49
await page.goto(`${url}/fetch`);
@@ -21,6 +26,5 @@ module.exports = async ({ page, url, requests }) => {
2126
},
2227
],
2328
});
24-
2529
await expectRequestCount(requests, { transactions: 1 });
2630
};

packages/nextjs/test/integration/test/utils/client.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const { strictEqual } = require('assert');
2+
const expect = require('expect');
23
const { logIf, parseEnvelope } = require('./common');
34

45
const VALID_REQUEST_PAYLOAD = {
@@ -105,8 +106,10 @@ const assertObjectMatches = (actual, expected) => {
105106
for (const key in expected) {
106107
const expectedValue = expected[key];
107108

108-
if (Object.prototype.toString.call(expectedValue) === '[object Object]' || Array.isArray(expectedValue)) {
109+
if (Object.prototype.toString.call(expectedValue) === '[object Object]') {
109110
assertObjectMatches(actual[key], expectedValue);
111+
} else if (Array.isArray(expectedValue)) {
112+
expect(actual[key]).toEqual(expect.arrayContaining(expectedValue.map(expect.objectContaining)));
110113
} else {
111114
strictEqual(actual[key], expectedValue);
112115
}

0 commit comments

Comments
 (0)