diff --git a/packages/tracing/src/idletransaction.ts b/packages/tracing/src/idletransaction.ts index c30f8170769c..81319bcc91e0 100644 --- a/packages/tracing/src/idletransaction.ts +++ b/packages/tracing/src/idletransaction.ts @@ -7,6 +7,7 @@ import { SpanStatus } from './spanstatus'; import { Transaction } from './transaction'; export const DEFAULT_IDLE_TIMEOUT = 1000; +export const HEARTBEAT_INTERVAL = 5000; /** * @inheritDoc @@ -55,9 +56,6 @@ export class IdleTransaction extends Transaction { // Activities store a list of active spans public activities: Record = {}; - // Stores reference to the timeout that calls _beat(). - private _heartbeatTimer: number = 0; - // Track state of activities in previous heartbeat private _prevHeartbeatString: string | undefined; @@ -69,15 +67,19 @@ export class IdleTransaction extends Transaction { private readonly _beforeFinishCallbacks: BeforeFinishCallback[] = []; - // If a transaction is created and no activities are added, we want to make sure that - // it times out properly. This is cleared and not used when activities are added. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private _initTimeout: any; + /** + * If a transaction is created and no activities are added, we want to make sure that + * it times out properly. This is cleared and not used when activities are added. + */ + private _initTimeout: ReturnType | undefined; public constructor( transactionContext: TransactionContext, private readonly _idleHub?: Hub, - // The time to wait in ms until the idle transaction will be finished. Default: 1000 + /** + * The time to wait in ms until the idle transaction will be finished. + * @default 1000 + */ private readonly _idleTimeout: number = DEFAULT_IDLE_TIMEOUT, // If an idle transaction should be put itself on and off the scope automatically. private readonly _onScope: boolean = false, @@ -232,14 +234,12 @@ export class IdleTransaction extends Transaction { * If this occurs we finish the transaction. */ private _beat(): void { - clearTimeout(this._heartbeatTimer); // We should not be running heartbeat if the idle transaction is finished. if (this._finished) { return; } - const keys = Object.keys(this.activities); - const heartbeatString = keys.length ? keys.reduce((prev: string, current: string) => prev + current) : ''; + const heartbeatString = Object.keys(this.activities).join(''); if (heartbeatString === this._prevHeartbeatString) { this._heartbeatCounter += 1; @@ -264,9 +264,9 @@ export class IdleTransaction extends Transaction { */ private _pingHeartbeat(): void { logger.log(`pinging Heartbeat -> current counter: ${this._heartbeatCounter}`); - this._heartbeatTimer = (setTimeout(() => { + setTimeout(() => { this._beat(); - }, 5000) as unknown) as number; + }, HEARTBEAT_INTERVAL); } } diff --git a/packages/tracing/test/idletransaction.test.ts b/packages/tracing/test/idletransaction.test.ts index d9a4f4cbe791..f6d5323b5f53 100644 --- a/packages/tracing/test/idletransaction.test.ts +++ b/packages/tracing/test/idletransaction.test.ts @@ -1,7 +1,12 @@ import { BrowserClient } from '@sentry/browser'; import { Hub } from '@sentry/hub'; -import { DEFAULT_IDLE_TIMEOUT, IdleTransaction, IdleTransactionSpanRecorder } from '../src/idletransaction'; +import { + DEFAULT_IDLE_TIMEOUT, + HEARTBEAT_INTERVAL, + IdleTransaction, + IdleTransactionSpanRecorder, +} from '../src/idletransaction'; import { Span } from '../src/span'; import { SpanStatus } from '../src/spanstatus'; @@ -13,7 +18,7 @@ beforeEach(() => { describe('IdleTransaction', () => { describe('onScope', () => { it('sets the transaction on the scope on creation if onScope is true', () => { - const transaction = new IdleTransaction({ name: 'foo' }, hub, 1000, true); + const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT, true); transaction.initSpanRecorder(10); hub.configureScope(s => { @@ -22,7 +27,7 @@ describe('IdleTransaction', () => { }); it('does not set the transaction on the scope on creation if onScope is falsey', () => { - const transaction = new IdleTransaction({ name: 'foo' }, hub, 1000); + const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT); transaction.initSpanRecorder(10); hub.configureScope(s => { @@ -31,7 +36,7 @@ describe('IdleTransaction', () => { }); it('removes sampled transaction from scope on finish if onScope is true', () => { - const transaction = new IdleTransaction({ name: 'foo' }, hub, 1000, true); + const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT, true); transaction.initSpanRecorder(10); transaction.finish(); @@ -43,7 +48,7 @@ describe('IdleTransaction', () => { }); it('removes unsampled transaction from scope on finish if onScope is true', () => { - const transaction = new IdleTransaction({ name: 'foo', sampled: false }, hub, 1000, true); + const transaction = new IdleTransaction({ name: 'foo', sampled: false }, hub, DEFAULT_IDLE_TIMEOUT, true); transaction.finish(); jest.runAllTimers(); @@ -59,7 +64,7 @@ describe('IdleTransaction', () => { }); it('push and pops activities', () => { - const transaction = new IdleTransaction({ name: 'foo' }, hub, 1000); + const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT); const mockFinish = jest.spyOn(transaction, 'finish'); transaction.initSpanRecorder(10); expect(transaction.activities).toMatchObject({}); @@ -77,7 +82,7 @@ describe('IdleTransaction', () => { }); it('does not push activities if a span already has an end timestamp', () => { - const transaction = new IdleTransaction({ name: 'foo' }, hub, 1000); + const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT); transaction.initSpanRecorder(10); expect(transaction.activities).toMatchObject({}); @@ -86,7 +91,7 @@ describe('IdleTransaction', () => { }); it('does not finish if there are still active activities', () => { - const transaction = new IdleTransaction({ name: 'foo' }, hub, 1000); + const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT); const mockFinish = jest.spyOn(transaction, 'finish'); transaction.initSpanRecorder(10); expect(transaction.activities).toMatchObject({}); @@ -105,7 +110,7 @@ describe('IdleTransaction', () => { it('calls beforeFinish callback before finishing', () => { const mockCallback1 = jest.fn(); const mockCallback2 = jest.fn(); - const transaction = new IdleTransaction({ name: 'foo' }, hub, 1000); + const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT); transaction.initSpanRecorder(10); transaction.registerBeforeFinishCallback(mockCallback1); transaction.registerBeforeFinishCallback(mockCallback2); @@ -124,7 +129,7 @@ describe('IdleTransaction', () => { }); it('filters spans on finish', () => { - const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, 1000); + const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, DEFAULT_IDLE_TIMEOUT); transaction.initSpanRecorder(10); // regular child - should be kept @@ -158,7 +163,7 @@ describe('IdleTransaction', () => { describe('_initTimeout', () => { it('finishes if no activities are added to the transaction', () => { - const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, 1000); + const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, DEFAULT_IDLE_TIMEOUT); transaction.initSpanRecorder(10); jest.advanceTimersByTime(DEFAULT_IDLE_TIMEOUT); @@ -166,7 +171,7 @@ describe('IdleTransaction', () => { }); it('does not finish if a activity is started', () => { - const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, 1000); + const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, DEFAULT_IDLE_TIMEOUT); transaction.initSpanRecorder(10); transaction.startChild({}); @@ -177,7 +182,6 @@ describe('IdleTransaction', () => { describe('heartbeat', () => { it('does not mark transaction as `DeadlineExceeded` if idle timeout has not been reached', () => { - const HEARTBEAT_INTERVAL = 5000; // 20s to exceed 3 heartbeats const transaction = new IdleTransaction({ name: 'foo' }, hub, 20000); const mockFinish = jest.spyOn(transaction, 'finish'); @@ -202,7 +206,7 @@ describe('IdleTransaction', () => { }); it('finishes a transaction after 3 beats', () => { - const transaction = new IdleTransaction({ name: 'foo' }, hub, 1000); + const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT); const mockFinish = jest.spyOn(transaction, 'finish'); transaction.initSpanRecorder(10); @@ -223,7 +227,7 @@ describe('IdleTransaction', () => { }); it('resets after new activities are added', () => { - const transaction = new IdleTransaction({ name: 'foo' }, hub, 1000); + const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT); const mockFinish = jest.spyOn(transaction, 'finish'); transaction.initSpanRecorder(10); @@ -315,7 +319,7 @@ describe('IdleTransactionSpanRecorder', () => { const mockPushActivity = jest.fn(); const mockPopActivity = jest.fn(); - const transaction = new IdleTransaction({ name: 'foo' }, hub, 1000); + const transaction = new IdleTransaction({ name: 'foo' }, hub, DEFAULT_IDLE_TIMEOUT); const spanRecorder = new IdleTransactionSpanRecorder(mockPushActivity, mockPopActivity, transaction.spanId, 10); spanRecorder.add(transaction);