-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(apm): Heartbeat #2478
feat(apm): Heartbeat #2478
Changes from all commits
8e999d7
2de233c
ba47120
f591831
6bdd6d9
3161108
b6f4b68
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -147,6 +147,12 @@ export class Tracing implements Integration { | |
|
||
private static _performanceCursor: number = 0; | ||
|
||
private static _heartbeatTimer: number = 0; | ||
|
||
private static _prevHeartbeatString: string | undefined; | ||
|
||
private static _heartbeatCounter: number = 0; | ||
|
||
/** | ||
* Constructor for Tracing | ||
* | ||
|
@@ -200,27 +206,7 @@ export class Tracing implements Integration { | |
return; | ||
} | ||
|
||
if (Tracing.options.traceXHR) { | ||
addInstrumentationHandler({ | ||
callback: xhrCallback, | ||
type: 'xhr', | ||
}); | ||
} | ||
|
||
if (Tracing.options.traceFetch && supportsNativeFetch()) { | ||
addInstrumentationHandler({ | ||
callback: fetchCallback, | ||
type: 'fetch', | ||
}); | ||
} | ||
|
||
if (Tracing.options.startTransactionOnLocationChange) { | ||
addInstrumentationHandler({ | ||
callback: historyCallback, | ||
type: 'history', | ||
}); | ||
} | ||
|
||
// Starting our inital pageload transaction | ||
if (global.location && global.location.href) { | ||
// `${global.location.href}` will be used a temp transaction name | ||
Tracing.startIdleTransaction(global.location.href, { | ||
|
@@ -229,36 +215,17 @@ export class Tracing implements Integration { | |
}); | ||
} | ||
|
||
/** | ||
* If an error or unhandled promise occurs, we mark the active transaction as failed | ||
*/ | ||
// tslint:disable-next-line: completed-docs | ||
function errorCallback(): void { | ||
if (Tracing._activeTransaction) { | ||
logger.log(`[Tracing] Global error occured, setting status in transaction: ${SpanStatus.InternalError}`); | ||
(Tracing._activeTransaction as SpanClass).setStatus(SpanStatus.InternalError); | ||
} | ||
} | ||
this._setupXHRTracing(); | ||
|
||
addInstrumentationHandler({ | ||
callback: errorCallback, | ||
type: 'error', | ||
}); | ||
this._setupFetchTracing(); | ||
|
||
addInstrumentationHandler({ | ||
callback: errorCallback, | ||
type: 'unhandledrejection', | ||
}); | ||
this._setupHistory(); | ||
|
||
if (Tracing.options.discardBackgroundSpans && global.document) { | ||
document.addEventListener('visibilitychange', () => { | ||
if (document.hidden && Tracing._activeTransaction) { | ||
logger.log('[Tracing] Discarded active transaction incl. activities since tab moved to the background'); | ||
Tracing._activeTransaction = undefined; | ||
Tracing._activities = {}; | ||
} | ||
}); | ||
} | ||
this._setupErrorHandling(); | ||
|
||
this._setupBackgroundTabDetection(); | ||
|
||
Tracing._pingHeartbeat(); | ||
|
||
// This EventProcessor makes sure that the transaction is not longer than maxTransactionDuration | ||
addGlobalEventProcessor((event: Event) => { | ||
|
@@ -284,6 +251,119 @@ export class Tracing implements Integration { | |
}); | ||
} | ||
|
||
/** | ||
* Pings the heartbeat | ||
*/ | ||
private static _pingHeartbeat(): void { | ||
Tracing._heartbeatTimer = (setTimeout(() => { | ||
Tracing._beat(); | ||
}, 5000) as any) as number; | ||
} | ||
|
||
/** | ||
* Checks when entries of Tracing._activities are not changing for 3 beats. If this occurs we finish the transaction | ||
* | ||
*/ | ||
private static _beat(): void { | ||
clearTimeout(Tracing._heartbeatTimer); | ||
const keys = Object.keys(Tracing._activities); | ||
if (keys.length) { | ||
const heartbeatString = keys.reduce((prev: string, current: string) => prev + current); | ||
if (heartbeatString === Tracing._prevHeartbeatString) { | ||
Tracing._heartbeatCounter++; | ||
} else { | ||
Tracing._heartbeatCounter = 0; | ||
} | ||
if (Tracing._heartbeatCounter >= 3) { | ||
if (Tracing._activeTransaction) { | ||
logger.log( | ||
"[Tracing] Heartbeat safeguard kicked in, finishing transaction since activities content hasn't changed for 3 beats", | ||
); | ||
Tracing._activeTransaction.setStatus(SpanStatus.DeadlineExceeded); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This status is the same as set by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Won't it be confusing the UI? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, you are not wrong but I thought we need to mark this transaction somehow and we also do it already for spans where |
||
Tracing._activeTransaction.setTag('heartbeat', 'failed'); | ||
Tracing.finishIdleTransaction(); | ||
} | ||
} | ||
Tracing._prevHeartbeatString = heartbeatString; | ||
} | ||
Tracing._pingHeartbeat(); | ||
} | ||
|
||
/** | ||
* Discards active transactions if tab moves to background | ||
*/ | ||
private _setupBackgroundTabDetection(): void { | ||
if (Tracing.options.discardBackgroundSpans && global.document) { | ||
document.addEventListener('visibilitychange', () => { | ||
if (document.hidden && Tracing._activeTransaction) { | ||
logger.log('[Tracing] Discarded active transaction incl. activities since tab moved to the background'); | ||
Tracing._activeTransaction = undefined; | ||
Tracing._activities = {}; | ||
} | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
* Registers to History API to detect navigation changes | ||
*/ | ||
private _setupHistory(): void { | ||
if (Tracing.options.startTransactionOnLocationChange) { | ||
addInstrumentationHandler({ | ||
callback: historyCallback, | ||
type: 'history', | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
* Attaches to fetch to add sentry-trace header + creating spans | ||
*/ | ||
private _setupFetchTracing(): void { | ||
if (Tracing.options.traceFetch && supportsNativeFetch()) { | ||
addInstrumentationHandler({ | ||
callback: fetchCallback, | ||
type: 'fetch', | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
* Attaches to XHR to add sentry-trace header + creating spans | ||
*/ | ||
private _setupXHRTracing(): void { | ||
if (Tracing.options.traceXHR) { | ||
addInstrumentationHandler({ | ||
callback: xhrCallback, | ||
type: 'xhr', | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
* Configures global error listeners | ||
*/ | ||
private _setupErrorHandling(): void { | ||
// tslint:disable-next-line: completed-docs | ||
function errorCallback(): void { | ||
if (Tracing._activeTransaction) { | ||
/** | ||
* If an error or unhandled promise occurs, we mark the active transaction as failed | ||
*/ | ||
logger.log(`[Tracing] Global error occured, setting status in transaction: ${SpanStatus.InternalError}`); | ||
(Tracing._activeTransaction as SpanClass).setStatus(SpanStatus.InternalError); | ||
} | ||
} | ||
addInstrumentationHandler({ | ||
callback: errorCallback, | ||
type: 'error', | ||
}); | ||
addInstrumentationHandler({ | ||
callback: errorCallback, | ||
type: 'unhandledrejection', | ||
}); | ||
} | ||
|
||
/** | ||
* Is tracing enabled | ||
*/ | ||
|
@@ -376,8 +456,8 @@ export class Tracing implements Integration { | |
public static finishIdleTransaction(): void { | ||
const active = Tracing._activeTransaction as SpanClass; | ||
if (active) { | ||
logger.log('[Tracing] finishIdleTransaction', active.transaction); | ||
Tracing._addPerformanceEntries(active); | ||
logger.log('[Tracing] finishIdleTransaction', active.transaction); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this moving down? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So debug prints are not confusing, that finish is the last thing we print before flushing the transaction. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
// true = use timestamp of last span | ||
active.finish(true); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't it be too fragile?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure was looking for something reasonable of "hashing" this whole thing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oooh, it's based on keys, so
id
, not type of activity. It shooooould be good enough for now then.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be unnecessarily large (it is a string of unbounded length). Plus it sort of relies on key order. Looking for something better...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, isn't this generating a lot of garbage for the GC every 5s?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might not be it yet, but found this
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
from https://stackoverflow.com/questions/11578377/detecting-change-in-a-javascript-object