Skip to content

Commit eea6d54

Browse files
authored
ref(tracing): Idle transaction refactoring (#3988)
* simplify heartbeatString creation * fix initTimeout return type + jsdoc for better discoverability in IDE * remove unused reference heart beat timeout * dedupe HEARTBEAT_INTERVAL between src and test * replace 1000 with DEFAULT_IDLE_TIMEOUT
1 parent 4ea7b5d commit eea6d54

File tree

2 files changed

+33
-29
lines changed

2 files changed

+33
-29
lines changed

packages/tracing/src/idletransaction.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { SpanStatus } from './spanstatus';
77
import { Transaction } from './transaction';
88

99
export const DEFAULT_IDLE_TIMEOUT = 1000;
10+
export const HEARTBEAT_INTERVAL = 5000;
1011

1112
/**
1213
* @inheritDoc
@@ -55,9 +56,6 @@ export class IdleTransaction extends Transaction {
5556
// Activities store a list of active spans
5657
public activities: Record<string, boolean> = {};
5758

58-
// Stores reference to the timeout that calls _beat().
59-
private _heartbeatTimer: number = 0;
60-
6159
// Track state of activities in previous heartbeat
6260
private _prevHeartbeatString: string | undefined;
6361

@@ -69,15 +67,19 @@ export class IdleTransaction extends Transaction {
6967

7068
private readonly _beforeFinishCallbacks: BeforeFinishCallback[] = [];
7169

72-
// If a transaction is created and no activities are added, we want to make sure that
73-
// it times out properly. This is cleared and not used when activities are added.
74-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
75-
private _initTimeout: any;
70+
/**
71+
* If a transaction is created and no activities are added, we want to make sure that
72+
* it times out properly. This is cleared and not used when activities are added.
73+
*/
74+
private _initTimeout: ReturnType<typeof setTimeout> | undefined;
7675

7776
public constructor(
7877
transactionContext: TransactionContext,
7978
private readonly _idleHub?: Hub,
80-
// The time to wait in ms until the idle transaction will be finished. Default: 1000
79+
/**
80+
* The time to wait in ms until the idle transaction will be finished.
81+
* @default 1000
82+
*/
8183
private readonly _idleTimeout: number = DEFAULT_IDLE_TIMEOUT,
8284
// If an idle transaction should be put itself on and off the scope automatically.
8385
private readonly _onScope: boolean = false,
@@ -232,14 +234,12 @@ export class IdleTransaction extends Transaction {
232234
* If this occurs we finish the transaction.
233235
*/
234236
private _beat(): void {
235-
clearTimeout(this._heartbeatTimer);
236237
// We should not be running heartbeat if the idle transaction is finished.
237238
if (this._finished) {
238239
return;
239240
}
240241

241-
const keys = Object.keys(this.activities);
242-
const heartbeatString = keys.length ? keys.reduce((prev: string, current: string) => prev + current) : '';
242+
const heartbeatString = Object.keys(this.activities).join('');
243243

244244
if (heartbeatString === this._prevHeartbeatString) {
245245
this._heartbeatCounter += 1;
@@ -264,9 +264,9 @@ export class IdleTransaction extends Transaction {
264264
*/
265265
private _pingHeartbeat(): void {
266266
logger.log(`pinging Heartbeat -> current counter: ${this._heartbeatCounter}`);
267-
this._heartbeatTimer = (setTimeout(() => {
267+
setTimeout(() => {
268268
this._beat();
269-
}, 5000) as unknown) as number;
269+
}, HEARTBEAT_INTERVAL);
270270
}
271271
}
272272

packages/tracing/test/idletransaction.test.ts

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { BrowserClient } from '@sentry/browser';
22
import { Hub } from '@sentry/hub';
33

4-
import { DEFAULT_IDLE_TIMEOUT, IdleTransaction, IdleTransactionSpanRecorder } from '../src/idletransaction';
4+
import {
5+
DEFAULT_IDLE_TIMEOUT,
6+
HEARTBEAT_INTERVAL,
7+
IdleTransaction,
8+
IdleTransactionSpanRecorder,
9+
} from '../src/idletransaction';
510
import { Span } from '../src/span';
611
import { SpanStatus } from '../src/spanstatus';
712

@@ -13,7 +18,7 @@ beforeEach(() => {
1318
describe('IdleTransaction', () => {
1419
describe('onScope', () => {
1520
it('sets the transaction on the scope on creation if onScope is true', () => {
16-
const transaction = new IdleTransaction({ name: 'foo' }, hub, 1000, true);
21+
const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT, true);
1722
transaction.initSpanRecorder(10);
1823

1924
hub.configureScope(s => {
@@ -22,7 +27,7 @@ describe('IdleTransaction', () => {
2227
});
2328

2429
it('does not set the transaction on the scope on creation if onScope is falsey', () => {
25-
const transaction = new IdleTransaction({ name: 'foo' }, hub, 1000);
30+
const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT);
2631
transaction.initSpanRecorder(10);
2732

2833
hub.configureScope(s => {
@@ -31,7 +36,7 @@ describe('IdleTransaction', () => {
3136
});
3237

3338
it('removes sampled transaction from scope on finish if onScope is true', () => {
34-
const transaction = new IdleTransaction({ name: 'foo' }, hub, 1000, true);
39+
const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT, true);
3540
transaction.initSpanRecorder(10);
3641

3742
transaction.finish();
@@ -43,7 +48,7 @@ describe('IdleTransaction', () => {
4348
});
4449

4550
it('removes unsampled transaction from scope on finish if onScope is true', () => {
46-
const transaction = new IdleTransaction({ name: 'foo', sampled: false }, hub, 1000, true);
51+
const transaction = new IdleTransaction({ name: 'foo', sampled: false }, hub, DEFAULT_IDLE_TIMEOUT, true);
4752

4853
transaction.finish();
4954
jest.runAllTimers();
@@ -59,7 +64,7 @@ describe('IdleTransaction', () => {
5964
});
6065

6166
it('push and pops activities', () => {
62-
const transaction = new IdleTransaction({ name: 'foo' }, hub, 1000);
67+
const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT);
6368
const mockFinish = jest.spyOn(transaction, 'finish');
6469
transaction.initSpanRecorder(10);
6570
expect(transaction.activities).toMatchObject({});
@@ -77,7 +82,7 @@ describe('IdleTransaction', () => {
7782
});
7883

7984
it('does not push activities if a span already has an end timestamp', () => {
80-
const transaction = new IdleTransaction({ name: 'foo' }, hub, 1000);
85+
const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT);
8186
transaction.initSpanRecorder(10);
8287
expect(transaction.activities).toMatchObject({});
8388

@@ -86,7 +91,7 @@ describe('IdleTransaction', () => {
8691
});
8792

8893
it('does not finish if there are still active activities', () => {
89-
const transaction = new IdleTransaction({ name: 'foo' }, hub, 1000);
94+
const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT);
9095
const mockFinish = jest.spyOn(transaction, 'finish');
9196
transaction.initSpanRecorder(10);
9297
expect(transaction.activities).toMatchObject({});
@@ -105,7 +110,7 @@ describe('IdleTransaction', () => {
105110
it('calls beforeFinish callback before finishing', () => {
106111
const mockCallback1 = jest.fn();
107112
const mockCallback2 = jest.fn();
108-
const transaction = new IdleTransaction({ name: 'foo' }, hub, 1000);
113+
const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT);
109114
transaction.initSpanRecorder(10);
110115
transaction.registerBeforeFinishCallback(mockCallback1);
111116
transaction.registerBeforeFinishCallback(mockCallback2);
@@ -124,7 +129,7 @@ describe('IdleTransaction', () => {
124129
});
125130

126131
it('filters spans on finish', () => {
127-
const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, 1000);
132+
const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, DEFAULT_IDLE_TIMEOUT);
128133
transaction.initSpanRecorder(10);
129134

130135
// regular child - should be kept
@@ -158,15 +163,15 @@ describe('IdleTransaction', () => {
158163

159164
describe('_initTimeout', () => {
160165
it('finishes if no activities are added to the transaction', () => {
161-
const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, 1000);
166+
const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, DEFAULT_IDLE_TIMEOUT);
162167
transaction.initSpanRecorder(10);
163168

164169
jest.advanceTimersByTime(DEFAULT_IDLE_TIMEOUT);
165170
expect(transaction.endTimestamp).toBeDefined();
166171
});
167172

168173
it('does not finish if a activity is started', () => {
169-
const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, 1000);
174+
const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, DEFAULT_IDLE_TIMEOUT);
170175
transaction.initSpanRecorder(10);
171176
transaction.startChild({});
172177

@@ -177,7 +182,6 @@ describe('IdleTransaction', () => {
177182

178183
describe('heartbeat', () => {
179184
it('does not mark transaction as `DeadlineExceeded` if idle timeout has not been reached', () => {
180-
const HEARTBEAT_INTERVAL = 5000;
181185
// 20s to exceed 3 heartbeats
182186
const transaction = new IdleTransaction({ name: 'foo' }, hub, 20000);
183187
const mockFinish = jest.spyOn(transaction, 'finish');
@@ -202,7 +206,7 @@ describe('IdleTransaction', () => {
202206
});
203207

204208
it('finishes a transaction after 3 beats', () => {
205-
const transaction = new IdleTransaction({ name: 'foo' }, hub, 1000);
209+
const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT);
206210
const mockFinish = jest.spyOn(transaction, 'finish');
207211
transaction.initSpanRecorder(10);
208212

@@ -223,7 +227,7 @@ describe('IdleTransaction', () => {
223227
});
224228

225229
it('resets after new activities are added', () => {
226-
const transaction = new IdleTransaction({ name: 'foo' }, hub, 1000);
230+
const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT);
227231
const mockFinish = jest.spyOn(transaction, 'finish');
228232
transaction.initSpanRecorder(10);
229233

@@ -315,7 +319,7 @@ describe('IdleTransactionSpanRecorder', () => {
315319
const mockPushActivity = jest.fn();
316320
const mockPopActivity = jest.fn();
317321

318-
const transaction = new IdleTransaction({ name: 'foo' }, hub, 1000);
322+
const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT);
319323
const spanRecorder = new IdleTransactionSpanRecorder(mockPushActivity, mockPopActivity, transaction.spanId, 10);
320324

321325
spanRecorder.add(transaction);

0 commit comments

Comments
 (0)