Skip to content

Commit f14ec02

Browse files
authored
feat: Add pageload transaction option + fixes (getsentry#2623)
* feat: Add pageload transaction option + fixes * fix: End timestamp * ref: CodeReview
1 parent fd1e320 commit f14ec02

File tree

3 files changed

+54
-33
lines changed

3 files changed

+54
-33
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
- [apm] feat: Transactions no longer go through `beforeSend` #2600
2121
- [browser] fix: Emit Sentry Request breadcrumbs from inside the client (#2615)
2222
- [apm] fix: No longer debounce IdleTransaction #2618
23+
- [apm] feat: Add pageload transaction option + fixes #2623
2324

2425
## 5.15.5
2526

packages/apm/src/integrations/tracing.ts

+52-32
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ interface TracingOptions {
5252
* Default: 500
5353
*/
5454
idleTimeout: number;
55+
5556
/**
5657
* Flag to enable/disable creation of `navigation` transaction on history changes. Useful for react applications with
5758
* a router.
@@ -60,6 +61,13 @@ interface TracingOptions {
6061
*/
6162
startTransactionOnLocationChange: boolean;
6263

64+
/**
65+
* Flag to enable/disable creation of `pageload` transaction on first pageload.
66+
*
67+
* Default: true
68+
*/
69+
startTransactionOnPageLoad: boolean;
70+
6371
/**
6472
* The maximum duration of a transaction before it will be marked as "deadline_exceeded".
6573
* If you never want to mark a transaction set it to 0.
@@ -137,8 +145,6 @@ export class Tracing implements Integration {
137145

138146
public static _activities: { [key: number]: Activity } = {};
139147

140-
private static _idleTransactionEndTimestamp: number = 0;
141-
142148
private readonly _emitOptionsWarning: boolean = false;
143149

144150
private static _performanceCursor: number = 0;
@@ -174,6 +180,7 @@ export class Tracing implements Integration {
174180
);
175181
},
176182
startTransactionOnLocationChange: true,
183+
startTransactionOnPageLoad: true,
177184
traceFetch: true,
178185
traceXHR: true,
179186
tracingOrigins: defaultTracingOrigins,
@@ -202,7 +209,7 @@ export class Tracing implements Integration {
202209
}
203210

204211
// Starting pageload transaction
205-
if (global.location && global.location.href) {
212+
if (global.location && global.location.href && Tracing.options && Tracing.options.startTransactionOnPageLoad) {
206213
// Use `${global.location.href}` as transaction name
207214
Tracing.startIdleTransaction({
208215
name: global.location.href,
@@ -285,7 +292,7 @@ export class Tracing implements Integration {
285292
);
286293
Tracing._activeTransaction.setStatus(SpanStatus.DeadlineExceeded);
287294
Tracing._activeTransaction.setTag('heartbeat', 'failed');
288-
Tracing.finishIdleTransaction();
295+
Tracing.finishIdleTransaction(timestampWithMs());
289296
}
290297
}
291298
Tracing._prevHeartbeatString = heartbeatString;
@@ -303,7 +310,7 @@ export class Tracing implements Integration {
303310
Tracing._log(`[Tracing] Transaction: ${SpanStatus.Cancelled} -> since tab moved to the background`);
304311
Tracing._activeTransaction.setStatus(SpanStatus.Cancelled);
305312
Tracing._activeTransaction.setTag('visibilitychange', 'document.hidden');
306-
Tracing.finishIdleTransaction();
313+
Tracing.finishIdleTransaction(timestampWithMs());
307314
}
308315
});
309316
}
@@ -403,7 +410,6 @@ export class Tracing implements Integration {
403410
message: safeJoin(args, ' '),
404411
type: 'debug',
405412
});
406-
return;
407413
}
408414
}
409415
logger.log(...args);
@@ -413,11 +419,6 @@ export class Tracing implements Integration {
413419
* Starts a Transaction waiting for activity idle to finish
414420
*/
415421
public static startIdleTransaction(transactionContext: TransactionContext): Transaction | undefined {
416-
// If we already have an active transaction it means one of two things
417-
// a) The user did rapid navigation changes and didn't wait until the transaction was finished
418-
// b) A activity wasn't popped correctly and therefore the transaction is stalling
419-
Tracing.finishIdleTransaction();
420-
421422
Tracing._log('[Tracing] startIdleTransaction');
422423

423424
const _getCurrentHub = Tracing._getCurrentHub;
@@ -448,27 +449,44 @@ export class Tracing implements Integration {
448449
/**
449450
* Finshes the current active transaction
450451
*/
451-
public static finishIdleTransaction(): void {
452+
public static finishIdleTransaction(endTimestamp: number): void {
452453
const active = Tracing._activeTransaction;
453454
if (active) {
455+
Tracing._log('[Tracing] finishing IdleTransaction', new Date(endTimestamp * 1000).toISOString());
454456
Tracing._addPerformanceEntries(active);
455-
Tracing._log('[Tracing] finishIdleTransaction');
456457

457458
if (active.spanRecorder) {
458-
const timeout = (Tracing.options && Tracing.options.idleTimeout) || 100;
459-
active.spanRecorder.spans = active.spanRecorder.spans.filter((finishedSpan: Span) => {
460-
const keepSpan = finishedSpan.startTimestamp < Tracing._idleTransactionEndTimestamp + timeout;
459+
active.spanRecorder.spans = active.spanRecorder.spans.filter((span: Span) => {
460+
// If we are dealing with the transaction itself, we just return it
461+
if (span.spanId === active.spanId) {
462+
return span;
463+
}
464+
465+
// We cancel all pending spans with status "cancelled" to indicate the idle transaction was finished early
466+
if (!span.endTimestamp) {
467+
span.endTimestamp = endTimestamp;
468+
span.setStatus(SpanStatus.Cancelled);
469+
Tracing._log('[Tracing] cancelling span since transaction ended early', JSON.stringify(span, undefined, 2));
470+
}
471+
472+
// We remove all spans that happend after the end of the transaction
473+
// This is here to prevent super long transactions and timing issues
474+
const keepSpan = span.startTimestamp < endTimestamp;
461475
if (!keepSpan) {
462476
Tracing._log(
463477
'[Tracing] discarding Span since it happened after Transaction was finished',
464-
finishedSpan.toJSON(),
478+
JSON.stringify(span, undefined, 2),
465479
);
466480
}
467481
return keepSpan;
468482
});
469483
}
484+
485+
Tracing._log('[Tracing] flushing IdleTransaction');
470486
active.finish();
471487
Tracing._resetActiveTransaction();
488+
} else {
489+
Tracing._log('[Tracing] No active IdleTransaction');
472490
}
473491
}
474492

@@ -491,29 +509,29 @@ export class Tracing implements Integration {
491509

492510
// tslint:disable-next-line: completed-docs
493511
function addPerformanceNavigationTiming(parent: Span, entry: { [key: string]: number }, event: string): void {
494-
const span = parent.startChild({
512+
parent.startChild({
495513
description: event,
514+
endTimestamp: timeOrigin + Tracing._msToSec(entry[`${event}End`]),
496515
op: 'browser',
516+
startTimestamp: timeOrigin + Tracing._msToSec(entry[`${event}Start`]),
497517
});
498-
span.startTimestamp = timeOrigin + Tracing._msToSec(entry[`${event}Start`]);
499-
span.endTimestamp = timeOrigin + Tracing._msToSec(entry[`${event}End`]);
500518
}
501519

502520
// tslint:disable-next-line: completed-docs
503521
function addRequest(parent: Span, entry: { [key: string]: number }): void {
504-
const request = parent.startChild({
522+
parent.startChild({
505523
description: 'request',
524+
endTimestamp: timeOrigin + Tracing._msToSec(entry.responseEnd),
506525
op: 'browser',
526+
startTimestamp: timeOrigin + Tracing._msToSec(entry.requestStart),
507527
});
508-
request.startTimestamp = timeOrigin + Tracing._msToSec(entry.requestStart);
509-
request.endTimestamp = timeOrigin + Tracing._msToSec(entry.responseEnd);
510528

511-
const response = parent.startChild({
529+
parent.startChild({
512530
description: 'response',
531+
endTimestamp: timeOrigin + Tracing._msToSec(entry.responseEnd),
513532
op: 'browser',
533+
startTimestamp: timeOrigin + Tracing._msToSec(entry.responseStart),
514534
});
515-
response.startTimestamp = timeOrigin + Tracing._msToSec(entry.responseStart);
516-
response.endTimestamp = timeOrigin + Tracing._msToSec(entry.responseEnd);
517535
}
518536

519537
let entryScriptSrc: string | undefined;
@@ -599,16 +617,15 @@ export class Tracing implements Integration {
599617
});
600618

601619
if (entryScriptStartEndTime !== undefined && tracingInitMarkStartTime !== undefined) {
602-
const evaluation = transactionSpan.startChild({
620+
transactionSpan.startChild({
603621
description: 'evaluation',
622+
endTimestamp: tracingInitMarkStartTime,
604623
op: `script`,
624+
startTimestamp: entryScriptStartEndTime,
605625
});
606-
evaluation.startTimestamp = entryScriptStartEndTime;
607-
evaluation.endTimestamp = tracingInitMarkStartTime;
608626
}
609627

610628
Tracing._performanceCursor = Math.max(performance.getEntries().length - 1, 0);
611-
612629
// tslint:enable: no-unsafe-any
613630
}
614631

@@ -756,9 +773,11 @@ export class Tracing implements Integration {
756773
if (count === 0 && Tracing._activeTransaction) {
757774
const timeout = Tracing.options && Tracing.options.idleTimeout;
758775
Tracing._log(`[Tracing] Flushing Transaction in ${timeout}ms`);
759-
Tracing._idleTransactionEndTimestamp = timestampWithMs();
776+
// We need to add the timeout here to have the real endtimestamp of the transaction
777+
// Remeber timestampWithMs is in seconds, timeout is in ms
778+
const end = timestampWithMs() + timeout / 1000;
760779
setTimeout(() => {
761-
Tracing.finishIdleTransaction();
780+
Tracing.finishIdleTransaction(end);
762781
}, timeout);
763782
}
764783
}
@@ -871,6 +890,7 @@ function fetchCallback(handlerData: { [key: string]: any }): void {
871890
*/
872891
function historyCallback(_: { [key: string]: any }): void {
873892
if (Tracing.options.startTransactionOnLocationChange && global && global.location) {
893+
Tracing.finishIdleTransaction(timestampWithMs());
874894
Tracing.startIdleTransaction({
875895
name: global.location.href,
876896
op: 'navigation',

packages/apm/src/span.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -285,9 +285,9 @@ export class Span implements SpanInterface, SpanContext {
285285
description: this.description,
286286
op: this.op,
287287
parent_span_id: this.parentSpanId,
288-
sampled: this.sampled,
289288
span_id: this.spanId,
290289
start_timestamp: this.startTimestamp,
290+
status: this.status,
291291
tags: Object.keys(this.tags).length > 0 ? this.tags : undefined,
292292
timestamp: this.endTimestamp,
293293
trace_id: this.traceId,

0 commit comments

Comments
 (0)