-
-
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
ref(tracing): Reset IdleTimeout based on activities count #5044
Conversation
39be641
to
2c09d87
Compare
@@ -266,13 +261,3 @@ export function getMetaContent(metaName: string): string | null { | |||
const el = getGlobalObject<Window>().document.querySelector(`meta[name=${metaName}]`); | |||
return el ? el.getAttribute('content') : null; | |||
} | |||
|
|||
/** Adjusts transaction value based on max transaction duration */ | |||
function adjustTransactionDuration(maxDuration: number, transaction: IdleTransaction, endTimestamp: number): void { |
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.
we can remove maxTransactionDuration
because it will be effectively replaced by finalTimeout
.
size-limit report 📦
|
8a00af0
to
22d0094
Compare
22d0094
to
fdde150
Compare
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.
I left a lot of comments but this PR is actually good to go as-is. 👍 Comments are mostly minor stuff and hypotheticals.
@@ -69,25 +72,27 @@ 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. | |||
* Timer that tracks a |
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.
Let's finish this comment :D
@@ -29,7 +28,6 @@ export function registerBackgroundTabDetection(): void { | |||
activeTransaction.setStatus(statusType); | |||
} | |||
activeTransaction.setTag('visibilitychange', 'document.hidden'); | |||
activeTransaction.setTag(FINISH_REASON_TAG, IDLE_TRANSACTION_FINISH_REASONS[2]); |
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.
Just because I am interested: What prompted this change? Was it just unnecessary?
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.
We had these in when we were investigating ways to improve LCP accuracy, but now that the investigative work is over, we can remove these tags (and save bundle accordingly).
See: #4251
@@ -97,16 +102,19 @@ export class IdleTransaction extends Transaction { | |||
_idleHub.configureScope(scope => scope.setSpan(this)); | |||
} | |||
|
|||
this._initTimeout = setTimeout(() => { | |||
this._startIdleTimeout(); | |||
global.setTimeout(() => { |
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.
Are we sure we want to go through with global.setTimeout
? getGlobalObject()
may very well be just an empty object in some environments, whereas we can pretty much assume that setTimeout
is just available in the global scope of all environments.
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.
Yeah fair, I'll revert.
export const HEARTBEAT_INTERVAL = 5000; | ||
|
||
const global = getGlobalObject<Window>(); |
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.
Can we rename this to globalObject
? Just to get rid of ambiguity with node's global
object.
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.
Removed the getGlobalObject
call.
// Whether or not the transaction should put itself on the scope when it starts and pop itself off when it ends | ||
private readonly _onScope: boolean = false, | ||
) { | ||
super(transactionContext, _idleHub); | ||
|
||
if (_idleHub && _onScope) { | ||
if (_onScope) { | ||
// There should only be one active transaction on the scope | ||
clearActiveTransaction(_idleHub); |
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.
I think we can make the hub parameter of clearActiveTransaction()
down at the bottom non-optional now.
|
||
public constructor( | ||
transactionContext: TransactionContext, | ||
private readonly _idleHub?: Hub, | ||
private readonly _idleHub: Hub, | ||
/** | ||
* The time to wait in ms until the idle transaction will be finished. |
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.
Doesn't have to be worded this way but it took me a while to understand when this timer should be started and stopped.
* The time to wait in ms until the idle transaction will be finished. | |
* The time to wait in ms until the idle transaction will be finished. This timer is started each time there are no active spans on this transaction. |
} | ||
|
||
/** {@inheritDoc} */ | ||
public finish(endTimestamp: number = timestampWithMs()): string | undefined { | ||
this._finished = true; | ||
this._cancelIdleTimeout(); |
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.
Do we actually need to cancel here? _startIdleTimeout
already checks for _finished
before calling finish()
.
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.
We do not, updated.
* Creates an idletimeout | ||
*/ | ||
private _startIdleTimeout(endTimestamp?: Parameters<IdleTransaction['finish']>[0]): void { | ||
this._cancelIdleTimeout(); |
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.
Even though I get why we would call cancel here (just to be safe), I think we could get away with not calling it, because everywhere we call start() there is no way a idletimeout is running. I'll leave it up to you to keep or remove it.
this._cancelIdleTimeout(); | ||
this._idleTimeoutID = global.setTimeout(() => { | ||
if (!this._finished && Object.keys(this.activities).length === 0) { | ||
this.finish(endTimestamp); |
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.
Do we maybe want to add a this.setStatus('deadline_exceeded');
here so that transactions that are cancelled because of the idleTimeout also have that status?
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.
No - because idletimeout should be how all idle transactions end regularly.
9441106
to
fb3ea96
Compare
Previously, when the activities count of an idle transaction hit 0, it would trigger a timeout for `idleTimeout` ms, and then always end the transaction. This means that if a span (like fetch/xhr request) started right after the activities count went to 0 (1 -> 0 -> 1), the transaction would always finish after `idleTimeout` ms, instead of waiting for the newest activity to finish. This was primarily done to prevent polling conditions and to not artificially inflate duration. Nowadays though, web vitals like LCP are a lot more important as a measurement in transactions than the strict duration (as with activities, they are a bit arbitrary). By making `idleTimeout` be strict about finish after activities go to 0, we often times would miss the LCP value that the browser would record, leading to many transactions not having proper LCPs. To get the LCP value close to browser quality as possible, we now reset the `idleTimeout` timeout if there are still existing activities. The concern here is with polling. To prevent polling issues, we now also have an additional `finalTimeout` that is started when the idle transaction is created. This double timeout system (alongside the heartbeat), should always enforce that the transaction is finished.
Ports changes from #4531 into v7
Previously, when the activities count of an idle transaction hit 0, it would trigger a timeout for
idleTimeout
ms, and then always end the transaction. This means that if a span (like fetch/xhr request) started right after the activities count went to 0 (1 -> 0 -> 1), the transaction would always finish afteridleTimeout
ms, instead of waiting for the newest activity to finish.This was primarily done to prevent polling conditions and to not artificially inflate duration. Nowadays though, web vitals like LCP are a lot more important as a measurement in transactions than the strict duration (as with activities, they are a bit arbitrary). By making
idleTimeout
be strict about finish after activities go to 0, we often times would miss the LCP value that the browser would record, leading to many transactions not having proper LCPs.To get the LCP value close to browser quality as possible, we now reset the
idleTimeout
timeout if there are still existing activities. The concern here is with polling. To prevent polling issues, we now also have an additionalfinalTimeout
that is started when the idle transaction is created. This double timeout system (alongside the heartbeat), should always enforce that the transaction is finished.An image explanation (opt-in)
Resolves https://getsentry.atlassian.net/browse/WEB-828