Skip to content

Commit b668242

Browse files
committed
ref(tracing): Add interaction experiment
This adds interactions as an experiment so we can try it out with Sentry SaaS before tuning it for general release. It's behind a rate as an experiment option and only makes idleTransactions on 'click' for now. Future considerations include us likely having differing idleTimeout settings, and being more clever with which data we include, but this should be enough to start experimenting.
1 parent 23c19bf commit b668242

File tree

5 files changed

+149
-4
lines changed

5 files changed

+149
-4
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() < 105) {
10+
//
11+
}
12+
})();
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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: 1000,
11+
_experiments: {
12+
interactionSampleRate: 1.0,
13+
},
14+
}),
15+
],
16+
tracesSampleRate: 1,
17+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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+
<button data-test-id="interaction-button">Click Me</button>
9+
</body>
10+
</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, getMultipleSentryEnvelopeRequests } from '../../../../utils/helpers';
6+
7+
sentryTest('should capture interaction transaction.', async ({ browserName, getLocalTestPath, page }) => {
8+
if (browserName !== 'chromium') {
9+
sentryTest.skip();
10+
}
11+
12+
await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` }));
13+
14+
const url = await getLocalTestPath({ testDir: __dirname });
15+
16+
await getFirstSentryEnvelopeRequest<Event>(page, url);
17+
18+
await page.locator('[data-test-id=interaction-button]').click();
19+
20+
const envelopes = await getMultipleSentryEnvelopeRequests<Event>(page, 2);
21+
const eventData = envelopes[1];
22+
23+
expect(eventData).toEqual(
24+
expect.objectContaining({
25+
contexts: expect.objectContaining({
26+
trace: expect.objectContaining({
27+
op: 'ui.action.click',
28+
}),
29+
}),
30+
platform: 'javascript',
31+
spans: [],
32+
tags: {},
33+
type: 'transaction',
34+
}),
35+
);
36+
});

packages/tracing/src/browser/browsertracing.ts

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
/* eslint-disable max-lines */
22
import { Hub } from '@sentry/core';
3-
import { EventProcessor, Integration, Transaction, TransactionContext } from '@sentry/types';
3+
import { EventProcessor, Integration, Transaction, TransactionContext, TransactionSource } from '@sentry/types';
44
import { baggageHeaderToDynamicSamplingContext, getDomElement, logger } from '@sentry/utils';
55

66
import { startIdleTransaction } from '../hubextensions';
7-
import { DEFAULT_FINAL_TIMEOUT, DEFAULT_HEARTBEAT_INTERVAL, DEFAULT_IDLE_TIMEOUT } from '../idletransaction';
7+
import {
8+
DEFAULT_FINAL_TIMEOUT,
9+
DEFAULT_HEARTBEAT_INTERVAL,
10+
DEFAULT_IDLE_TIMEOUT,
11+
IdleTransaction,
12+
} from '../idletransaction';
813
import { extractTraceparentData } from '../utils';
914
import { registerBackgroundTabDetection } from './backgroundtab';
1015
import { addPerformanceEntries, startTrackingLongTasks, startTrackingWebVitals } from './metrics';
@@ -87,7 +92,7 @@ export interface BrowserTracingOptions extends RequestInstrumentationOptions {
8792
*
8893
* Default: undefined
8994
*/
90-
_experiments?: Partial<{ enableLongTask: boolean }>;
95+
_experiments?: Partial<{ enableLongTask: boolean; interactionSampleRate: number }>;
9196

9297
/**
9398
* beforeNavigate is called before a pageload/navigation transaction is created and allows users to modify transaction
@@ -120,7 +125,7 @@ const DEFAULT_BROWSER_TRACING_OPTIONS = {
120125
routingInstrumentation: instrumentRoutingWithDefaults,
121126
startTransactionOnLocationChange: true,
122127
startTransactionOnPageLoad: true,
123-
_experiments: { enableLongTask: true },
128+
_experiments: { enableLongTask: true, interactionSampleRate: 0.0 },
124129
...defaultRequestInstrumentationOptions,
125130
};
126131

@@ -147,6 +152,9 @@ export class BrowserTracing implements Integration {
147152

148153
private _getCurrentHub?: () => Hub;
149154

155+
private _latestRouteName?: string;
156+
private _latestRouteSource?: TransactionSource;
157+
150158
public constructor(_options?: Partial<BrowserTracingOptions>) {
151159
this.options = {
152160
...DEFAULT_BROWSER_TRACING_OPTIONS,
@@ -177,6 +185,7 @@ export class BrowserTracing implements Integration {
177185
// eslint-disable-next-line deprecation/deprecation
178186
tracingOrigins,
179187
shouldCreateSpanForRequest,
188+
_experiments,
180189
} = this.options;
181190

182191
instrumentRouting(
@@ -189,6 +198,10 @@ export class BrowserTracing implements Integration {
189198
registerBackgroundTabDetection();
190199
}
191200

201+
if (_experiments?.interactionSampleRate ?? 0 > 0) {
202+
this._registerInteractionListener(_experiments?.interactionSampleRate ?? 0);
203+
}
204+
192205
instrumentOutgoingRequests({ traceFetch, traceXHR, tracingOrigins, shouldCreateSpanForRequest });
193206
}
194207

@@ -235,6 +248,9 @@ export class BrowserTracing implements Integration {
235248
? { ...finalContext.metadata, source: 'custom' }
236249
: finalContext.metadata;
237250

251+
this._latestRouteName = finalContext.name;
252+
this._latestRouteSource = finalContext.metadata?.source;
253+
238254
if (finalContext.sampled === false) {
239255
__DEBUG_BUILD__ &&
240256
logger.log(`[Tracing] Will not send ${finalContext.op} transaction because of beforeNavigate.`);
@@ -264,6 +280,60 @@ export class BrowserTracing implements Integration {
264280

265281
return idleTransaction as Transaction;
266282
}
283+
284+
/** Start listener for interaction transactions */
285+
private _registerInteractionListener(interactionSampleRate: number): void {
286+
let inflightInteractionTransaction: IdleTransaction | undefined;
287+
const registerInteractionTransaction = (): void => {
288+
const { idleTimeout, finalTimeout, heartbeatInterval } = this.options;
289+
if (Math.random() > interactionSampleRate) {
290+
return;
291+
}
292+
293+
const op = 'ui.action.click';
294+
if (inflightInteractionTransaction) {
295+
inflightInteractionTransaction.finish();
296+
inflightInteractionTransaction = undefined;
297+
}
298+
299+
if (!this._getCurrentHub) {
300+
__DEBUG_BUILD__ && logger.warn(`[Tracing] Did not create ${op} transaction because _getCurrentHub is invalid.`);
301+
return undefined;
302+
}
303+
304+
if (!this._latestRouteName) {
305+
__DEBUG_BUILD__ &&
306+
logger.warn(`[Tracing] Did not create ${op} transaction because _latestRouteName is missing.`);
307+
return undefined;
308+
}
309+
310+
const hub = this._getCurrentHub();
311+
const { location } = WINDOW;
312+
313+
const context: TransactionContext = {
314+
name: this._latestRouteName,
315+
op,
316+
trimEnd: true,
317+
metadata: {
318+
source: this._latestRouteSource ?? 'url',
319+
},
320+
};
321+
322+
inflightInteractionTransaction = startIdleTransaction(
323+
hub,
324+
context,
325+
idleTimeout,
326+
finalTimeout,
327+
true,
328+
{ location }, // for use in the tracesSampler
329+
heartbeatInterval,
330+
);
331+
};
332+
333+
['click'].forEach(type => {
334+
addEventListener(type, registerInteractionTransaction, { once: false, capture: true });
335+
});
336+
}
267337
}
268338

269339
/** Returns the value of a meta tag */

0 commit comments

Comments
 (0)