Skip to content

Commit 07ed1fc

Browse files
committed
feat(browser): make heartbeat interval configurable
Signed-off-by: Outsider <[email protected]>
1 parent f5cbc4c commit 07ed1fc

File tree

5 files changed

+57
-23
lines changed

5 files changed

+57
-23
lines changed

packages/tracing/src/browser/browsertracing.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { EventProcessor, Integration, Transaction, TransactionContext } from '@s
44
import { baggageHeaderToDynamicSamplingContext, getDomElement, getGlobalObject, logger } from '@sentry/utils';
55

66
import { startIdleTransaction } from '../hubextensions';
7-
import { DEFAULT_FINAL_TIMEOUT, DEFAULT_IDLE_TIMEOUT } from '../idletransaction';
7+
import { DEFAULT_FINAL_TIMEOUT, DEFAULT_HEARTBEAT_INTERVAL, DEFAULT_IDLE_TIMEOUT } from '../idletransaction';
88
import { extractTraceparentData } from '../utils';
99
import { registerBackgroundTabDetection } from './backgroundtab';
1010
import { addPerformanceEntries, startTrackingLongTasks, startTrackingWebVitals } from './metrics';
@@ -40,6 +40,14 @@ export interface BrowserTracingOptions extends RequestInstrumentationOptions {
4040
*/
4141
finalTimeout: number;
4242

43+
/**
44+
* The heartbeat interval. If activities don't change in 3 heartbeats, a transaction will be finished.
45+
* Time is in ms.
46+
*
47+
* Default: 5000
48+
*/
49+
heartbeatInterval: number;
50+
4351
/**
4452
* Flag to enable/disable creation of `navigation` transaction on history changes.
4553
*
@@ -105,6 +113,7 @@ export interface BrowserTracingOptions extends RequestInstrumentationOptions {
105113
const DEFAULT_BROWSER_TRACING_OPTIONS = {
106114
idleTimeout: DEFAULT_IDLE_TIMEOUT,
107115
finalTimeout: DEFAULT_FINAL_TIMEOUT,
116+
heartbeatInterval: DEFAULT_HEARTBEAT_INTERVAL,
108117
markBackgroundTransactions: true,
109118
routingInstrumentation: instrumentRoutingWithDefaults,
110119
startTransactionOnLocationChange: true,
@@ -213,7 +222,7 @@ export class BrowserTracing implements Integration {
213222
}
214223

215224
// eslint-disable-next-line @typescript-eslint/unbound-method
216-
const { beforeNavigate, idleTimeout, finalTimeout } = this.options;
225+
const { beforeNavigate, idleTimeout, finalTimeout, heartbeatInterval } = this.options;
217226

218227
const isPageloadTransaction = context.op === 'pageload';
219228

@@ -262,6 +271,7 @@ export class BrowserTracing implements Integration {
262271
finalContext,
263272
idleTimeout,
264273
finalTimeout,
274+
heartbeatInterval,
265275
true,
266276
{ location }, // for use in the tracesSampler
267277
);

packages/tracing/src/hubextensions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,13 +186,14 @@ export function startIdleTransaction(
186186
transactionContext: TransactionContext,
187187
idleTimeout: number,
188188
finalTimeout: number,
189+
heartbeatInterval: number,
189190
onScope?: boolean,
190191
customSamplingContext?: CustomSamplingContext,
191192
): IdleTransaction {
192193
const client = hub.getClient();
193194
const options: Partial<ClientOptions> = (client && client.getOptions()) || {};
194195

195-
let transaction = new IdleTransaction(transactionContext, hub, idleTimeout, finalTimeout, onScope);
196+
let transaction = new IdleTransaction(transactionContext, hub, idleTimeout, finalTimeout, heartbeatInterval, onScope);
196197
transaction = sample(transaction, options, {
197198
parentSampled: transactionContext.parentSampled,
198199
transactionContext,

packages/tracing/src/idletransaction.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { Transaction } from './transaction';
88

99
export const DEFAULT_IDLE_TIMEOUT = 1000;
1010
export const DEFAULT_FINAL_TIMEOUT = 30000;
11-
export const HEARTBEAT_INTERVAL = 5000;
11+
export const DEFAULT_HEARTBEAT_INTERVAL = 5000;
1212

1313
/**
1414
* @inheritDoc
@@ -85,6 +85,7 @@ export class IdleTransaction extends Transaction {
8585
* The final value in ms that a transaction cannot exceed
8686
*/
8787
private readonly _finalTimeout: number = DEFAULT_FINAL_TIMEOUT,
88+
private readonly _heartbeatInterval: number = DEFAULT_HEARTBEAT_INTERVAL,
8889
// Whether or not the transaction should put itself on the scope when it starts and pop itself off when it ends
8990
private readonly _onScope: boolean = false,
9091
) {
@@ -287,7 +288,7 @@ export class IdleTransaction extends Transaction {
287288
__DEBUG_BUILD__ && logger.log(`pinging Heartbeat -> current counter: ${this._heartbeatCounter}`);
288289
setTimeout(() => {
289290
this._beat();
290-
}, HEARTBEAT_INTERVAL);
291+
}, this._heartbeatInterval);
291292
}
292293
}
293294

packages/tracing/test/browser/browsertracing.test.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@ import { BrowserTracing, BrowserTracingOptions, getMetaContent } from '../../src
88
import { defaultRequestInstrumentationOptions } from '../../src/browser/request';
99
import { instrumentRoutingWithDefaults } from '../../src/browser/router';
1010
import * as hubExtensions from '../../src/hubextensions';
11-
import { DEFAULT_FINAL_TIMEOUT, DEFAULT_IDLE_TIMEOUT, IdleTransaction } from '../../src/idletransaction';
11+
import {
12+
DEFAULT_FINAL_TIMEOUT,
13+
DEFAULT_HEARTBEAT_INTERVAL,
14+
DEFAULT_IDLE_TIMEOUT,
15+
IdleTransaction,
16+
} from '../../src/idletransaction';
1217
import { getActiveTransaction } from '../../src/utils';
1318
import { getDefaultBrowserClientOptions } from '../testutils';
1419

@@ -83,6 +88,7 @@ describe('BrowserTracing', () => {
8388
},
8489
idleTimeout: DEFAULT_IDLE_TIMEOUT,
8590
finalTimeout: DEFAULT_FINAL_TIMEOUT,
91+
heartbeatInterval: DEFAULT_HEARTBEAT_INTERVAL,
8692
markBackgroundTransactions: true,
8793
routingInstrumentation: instrumentRoutingWithDefaults,
8894
startTransactionOnLocationChange: true,
@@ -266,6 +272,7 @@ describe('BrowserTracing', () => {
266272
}),
267273
expect.any(Number),
268274
expect.any(Number),
275+
expect.any(Number),
269276
expect.any(Boolean),
270277
expect.any(Object),
271278
);

packages/tracing/test/idletransaction.test.ts

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { Hub } from '@sentry/hub';
33

44
import {
55
DEFAULT_FINAL_TIMEOUT,
6+
DEFAULT_HEARTBEAT_INTERVAL,
67
DEFAULT_IDLE_TIMEOUT,
7-
HEARTBEAT_INTERVAL,
88
IdleTransaction,
99
IdleTransactionSpanRecorder,
1010
} from '../src/idletransaction';
@@ -21,7 +21,14 @@ beforeEach(() => {
2121
describe('IdleTransaction', () => {
2222
describe('onScope', () => {
2323
it('sets the transaction on the scope on creation if onScope is true', () => {
24-
const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT, DEFAULT_FINAL_TIMEOUT, true);
24+
const transaction = new IdleTransaction(
25+
{ name: 'foo' },
26+
hub,
27+
DEFAULT_IDLE_TIMEOUT,
28+
DEFAULT_FINAL_TIMEOUT,
29+
DEFAULT_HEARTBEAT_INTERVAL,
30+
true,
31+
);
2532
transaction.initSpanRecorder(10);
2633

2734
hub.configureScope(s => {
@@ -39,7 +46,14 @@ describe('IdleTransaction', () => {
3946
});
4047

4148
it('removes sampled transaction from scope on finish if onScope is true', () => {
42-
const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT, DEFAULT_FINAL_TIMEOUT, true);
49+
const transaction = new IdleTransaction(
50+
{ name: 'foo' },
51+
hub,
52+
DEFAULT_IDLE_TIMEOUT,
53+
DEFAULT_FINAL_TIMEOUT,
54+
DEFAULT_HEARTBEAT_INTERVAL,
55+
true,
56+
);
4357
transaction.initSpanRecorder(10);
4458

4559
transaction.finish();
@@ -56,6 +70,7 @@ describe('IdleTransaction', () => {
5670
hub,
5771
DEFAULT_IDLE_TIMEOUT,
5872
DEFAULT_FINAL_TIMEOUT,
73+
DEFAULT_HEARTBEAT_INTERVAL,
5974
true,
6075
);
6176

@@ -248,17 +263,17 @@ describe('IdleTransaction', () => {
248263
expect(mockFinish).toHaveBeenCalledTimes(0);
249264

250265
// Beat 1
251-
jest.advanceTimersByTime(HEARTBEAT_INTERVAL);
266+
jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL);
252267
expect(transaction.status).not.toEqual('deadline_exceeded');
253268
expect(mockFinish).toHaveBeenCalledTimes(0);
254269

255270
// Beat 2
256-
jest.advanceTimersByTime(HEARTBEAT_INTERVAL);
271+
jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL);
257272
expect(transaction.status).not.toEqual('deadline_exceeded');
258273
expect(mockFinish).toHaveBeenCalledTimes(0);
259274

260275
// Beat 3
261-
jest.advanceTimersByTime(HEARTBEAT_INTERVAL);
276+
jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL);
262277
expect(transaction.status).not.toEqual('deadline_exceeded');
263278
expect(mockFinish).toHaveBeenCalledTimes(0);
264279
});
@@ -272,15 +287,15 @@ describe('IdleTransaction', () => {
272287
transaction.startChild({});
273288

274289
// Beat 1
275-
jest.advanceTimersByTime(HEARTBEAT_INTERVAL);
290+
jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL);
276291
expect(mockFinish).toHaveBeenCalledTimes(0);
277292

278293
// Beat 2
279-
jest.advanceTimersByTime(HEARTBEAT_INTERVAL);
294+
jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL);
280295
expect(mockFinish).toHaveBeenCalledTimes(0);
281296

282297
// Beat 3
283-
jest.advanceTimersByTime(HEARTBEAT_INTERVAL);
298+
jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL);
284299
expect(mockFinish).toHaveBeenCalledTimes(1);
285300
});
286301

@@ -293,42 +308,42 @@ describe('IdleTransaction', () => {
293308
transaction.startChild({});
294309

295310
// Beat 1
296-
jest.advanceTimersByTime(HEARTBEAT_INTERVAL);
311+
jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL);
297312
expect(mockFinish).toHaveBeenCalledTimes(0);
298313

299314
const span = transaction.startChild(); // push activity
300315

301316
// Beat 1
302-
jest.advanceTimersByTime(HEARTBEAT_INTERVAL);
317+
jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL);
303318
expect(mockFinish).toHaveBeenCalledTimes(0);
304319

305320
// Beat 2
306-
jest.advanceTimersByTime(HEARTBEAT_INTERVAL);
321+
jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL);
307322
expect(mockFinish).toHaveBeenCalledTimes(0);
308323

309324
transaction.startChild(); // push activity
310325
transaction.startChild(); // push activity
311326

312327
// Beat 1
313-
jest.advanceTimersByTime(HEARTBEAT_INTERVAL);
328+
jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL);
314329
expect(mockFinish).toHaveBeenCalledTimes(0);
315330

316331
// Beat 2
317-
jest.advanceTimersByTime(HEARTBEAT_INTERVAL);
332+
jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL);
318333
expect(mockFinish).toHaveBeenCalledTimes(0);
319334

320335
span.finish(); // pop activity
321336

322337
// Beat 1
323-
jest.advanceTimersByTime(HEARTBEAT_INTERVAL);
338+
jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL);
324339
expect(mockFinish).toHaveBeenCalledTimes(0);
325340

326341
// Beat 2
327-
jest.advanceTimersByTime(HEARTBEAT_INTERVAL);
342+
jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL);
328343
expect(mockFinish).toHaveBeenCalledTimes(0);
329344

330345
// Beat 3
331-
jest.advanceTimersByTime(HEARTBEAT_INTERVAL);
346+
jest.advanceTimersByTime(DEFAULT_HEARTBEAT_INTERVAL);
332347
expect(mockFinish).toHaveBeenCalledTimes(1);
333348

334349
// Heartbeat does not keep going after finish has been called

0 commit comments

Comments
 (0)