Repository navigation
Commit 0201779
fix(core): Correct sleep clock drift on telemetry timestamps (#23054)
This PR fixes* clock drift that occurs when devices go to sleep. After
devices sleep for a few seconds, the browser's monotonic clock (accessed
via `performance.now()`) stops counting. Once the sleep stops, the
monotonic clock resumes right where it left off before the sleep,
causing significant discrepancies between the actual time (wall clock)
and the browser's monotonic clock time. Or in other words, there are two
ways to get an absolute timestamp:
- Using the monotonic clock via `peformance.timeOrigin +
performance.now()` - has high, sub-millisecond precision and a guarantee
that there are no time jumps or adjustments. But suffers from the sleep
problem described above
- Using the wall clock via `Date.now()` - lower millisecond precision,
is known to be corrected (NTP or via users) and can even cause back
jumps. But no sleep problem.
With this fix, we make the following adjustments, to kinda get the best
of both worlds:
- Every `timestampInSecondsCall` checks the monotonic against the wall
clock. If a threshold of difference is enocuntered, it corrects the
`timeOrigin` so that we can keep using the precise monotonic clock, but
anchor its relative time to a corrected time origin.
- On every time origin correction, we remember the previous origin (up
to 30 corrections). Needed for performance entries
- Makes `browserPerformanceTimeOrigin` a time origin corrected helper
function, where you pass in a relative time that comes from monotonic
clock relative timestamps and it returns the time origin that was most
accurate at that relative time point.
- All spans and other telemetry we create from browsers
`PerformanceEntry` objects carry relative times which are completely
sleep-drift uncorrected. We use `browserPerformanceTimeOrigin` to return
a corrected time stamp so that we can create an absolute timestamp and
bring the performance entries to their respective actual time.
### FAQ
If you think this sounds complicated, I agree. So let me answer the most
obvious questions, because I asked myself these a lot, too:
**Why not just always use `Date.now()` and avoid the complicated click
drift detection and correction logic?**
The main issue with this is that we still need to rely on relative
monotonic clock timestamps for performance entries. We cannot correct
them just with `Date.now()` alone but we still need to anchor them. So
either we rely on the original `window.timeOrigin` and therefore have
all performance entry telemetry happen much "earlier" than its
surrounding telemetry relying on `Date.now()`, or we keep the time
origin correction logic. But even in the second case (where we already
pay the tax for drift detection and correction), telemetry would still
have two different anchors: `Date.now()` for regular telemetry and the
corrected time origin + monotonic time for performance entry telemetry.
Another reason is that the monotonic clock gives us sun-millisecond
precision while the wall clock stops at a millisecond resolution.
**Why the reduction from a drift detection threshold of 5 minutes to
just one second?**
Because we keep everything centered around detection and origin offset
correction, these timestamps need to be as accurate as possible. On
phones, short frequent sleeps are very likely to happen. A lot of sleeps
can accumulate until that 5 minutes threshold is reached. So it's really
important we make these timestamps as accurate as possible.
Fwiw, I reproduced this locally and the drift is already noticeable
after just a few seconds of sleep. The 5 minutes threshold was arguably
far too big beforehand.
**Is there precedence for all of this stuff?**
Somewhat, but I'd argue we're doing a bit more: OTel uses a mixture of
`Date.now()` and `performance.now`:
- Spans start at `Date.now()` and at start time, they record a
`performance.now()` timestamp. On span end, they also take
`performance.now()`, compute the diff and convert that to an end
timestamp based on the start timestamp + diff. This is neat because it
guarantees monotony in span durations. I stole this in #24903
In other places, OTel relies purely on Date.now(), and for performance
entries, they simply take uncorrected timestamps. So our fix is more
complete.
\* **So... we're good now?**
Well, not perfectly and we never will. With this choice we make another
commitment (which we already did previously in less obvious cases) to
`Date.now()`. This value can drift as well, just not for sleeps:
- NTP adjustments: Can make hard correction of the wall clock, or make
the clock tick just a bit faster or slower until the device wall clock
synced with the network time.
- User adjustments: Users can adjust the device time at any time into
any direction.
I think we'll have to live with both and I'm not particularly worried
about them. My main objective is getting rid of the sleep drift.
Fixes #2590
Supersedes #22488, #22585, #23067, #23068
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>1 parent fcb6cdf commit 0201779
19 files changed
Lines changed: 1015 additions & 326 deletions
File tree
- packages
- browser-utils
- src
- performance
- web-vitals
- test
- performance
- web-vitals
- browser
- src
- profiling
- test
- core
- src
- utils
- test/lib/utils
- ember/src/utils
- replay-internal
- src/util
- test/unit/util
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
5 | | - | |
6 | 5 | | |
7 | 6 | | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| |||
108 | 108 | | |
109 | 109 | | |
110 | 110 | | |
111 | | - | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
112 | 115 | | |
113 | 116 | | |
114 | 117 | | |
| |||
143 | 146 | | |
144 | 147 | | |
145 | 148 | | |
146 | | - | |
| 149 | + | |
| 150 | + | |
147 | 151 | | |
148 | 152 | | |
149 | 153 | | |
150 | | - | |
151 | | - | |
152 | 154 | | |
153 | 155 | | |
154 | 156 | | |
| |||
209 | 211 | | |
210 | 212 | | |
211 | 213 | | |
212 | | - | |
213 | | - | |
| 214 | + | |
214 | 215 | | |
215 | 216 | | |
216 | 217 | | |
217 | 218 | | |
218 | 219 | | |
219 | 220 | | |
220 | | - | |
221 | | - | |
222 | 221 | | |
223 | 222 | | |
224 | 223 | | |
225 | 224 | | |
226 | 225 | | |
227 | | - | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
228 | 230 | | |
229 | 231 | | |
230 | 232 | | |
| |||
233 | 235 | | |
234 | 236 | | |
235 | 237 | | |
236 | | - | |
237 | | - | |
238 | | - | |
239 | | - | |
240 | | - | |
| 238 | + | |
241 | 239 | | |
242 | 240 | | |
243 | 241 | | |
244 | 242 | | |
245 | 243 | | |
246 | | - | |
| 244 | + | |
247 | 245 | | |
248 | 246 | | |
249 | 247 | | |
250 | | - | |
| 248 | + | |
251 | 249 | | |
252 | 250 | | |
253 | 251 | | |
254 | 252 | | |
255 | 253 | | |
256 | 254 | | |
257 | 255 | | |
258 | | - | |
| 256 | + | |
259 | 257 | | |
260 | | - | |
261 | 258 | | |
262 | 259 | | |
263 | 260 | | |
| |||
276 | 273 | | |
277 | 274 | | |
278 | 275 | | |
279 | | - | |
280 | | - | |
281 | | - | |
282 | | - | |
283 | | - | |
284 | | - | |
285 | | - | |
286 | | - | |
287 | | - | |
| 276 | + | |
288 | 277 | | |
289 | 278 | | |
290 | 279 | | |
| |||
304 | 293 | | |
305 | 294 | | |
306 | 295 | | |
307 | | - | |
308 | 296 | | |
309 | 297 | | |
310 | | - | |
311 | | - | |
312 | | - | |
313 | | - | |
314 | | - | |
315 | | - | |
316 | | - | |
317 | | - | |
318 | | - | |
319 | | - | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
320 | 317 | | |
321 | 318 | | |
322 | 319 | | |
| |||
354 | 351 | | |
355 | 352 | | |
356 | 353 | | |
357 | | - | |
358 | 354 | | |
359 | 355 | | |
360 | 356 | | |
| |||
364 | 360 | | |
365 | 361 | | |
366 | 362 | | |
367 | | - | |
| 363 | + | |
368 | 364 | | |
369 | 365 | | |
370 | 366 | | |
| |||
388 | 384 | | |
389 | 385 | | |
390 | 386 | | |
391 | | - | |
392 | | - | |
393 | | - | |
394 | | - | |
395 | | - | |
396 | | - | |
397 | | - | |
398 | | - | |
399 | | - | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
400 | 391 | | |
401 | 392 | | |
402 | 393 | | |
| |||
435 | 426 | | |
436 | 427 | | |
437 | 428 | | |
438 | | - | |
| 429 | + | |
439 | 430 | | |
440 | | - | |
441 | 431 | | |
442 | 432 | | |
443 | 433 | | |
| |||
497 | 487 | | |
498 | 488 | | |
499 | 489 | | |
500 | | - | |
501 | 490 | | |
502 | 491 | | |
503 | 492 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
20 | 19 | | |
21 | 20 | | |
22 | 21 | | |
23 | 22 | | |
24 | 23 | | |
25 | 24 | | |
| 25 | + | |
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
| |||
242 | 242 | | |
243 | 243 | | |
244 | 244 | | |
245 | | - | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
246 | 249 | | |
247 | 250 | | |
248 | 251 | | |
| |||
Lines changed: 10 additions & 10 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | | - | |
3 | | - | |
4 | | - | |
5 | | - | |
6 | | - | |
7 | | - | |
8 | | - | |
9 | | - | |
| 2 | + | |
| 3 | + | |
10 | 4 | | |
11 | 5 | | |
12 | 6 | | |
| |||
28 | 22 | | |
29 | 23 | | |
30 | 24 | | |
31 | | - | |
| 25 | + | |
32 | 26 | | |
33 | 27 | | |
34 | 28 | | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
| |||
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
61 | | - | |
| 61 | + | |
62 | 62 | | |
63 | 63 | | |
64 | 64 | | |
| |||
0 commit comments