Skip to content

fix(core): Correct sleep clock drift on telemetry timestamps - #23054

Open
Lms24 wants to merge 20 commits into
developfrom
lms/fix-core-browser-timestampInSeconds-offset-clockdrift
Open

Lms24 wants to merge 20 commits into
developfrom
lms/fix-core-browser-timestampInSeconds-offset-clockdrift

Conversation

@Lms24

@Lms24 Lms24 commented Aug 5, 2026 •

Copy link
Copy Markdown
Member

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 fix(core): Compute span end time from performance.now() duration #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

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/core/src/utils/time.ts
@github-actions

github-actions Bot commented Aug 5, 2026 •

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 29.72 kB +0.41% +120 B 🔺
@sentry/browser - with treeshaking flags 27.86 kB +0.39% +107 B 🔺
@sentry/browser - with treeshaking flags tracing without tracing 27.75 kB +0.38% +103 B 🔺
@sentry/browser (incl. Tracing) 51.61 kB +0.18% +91 B 🔺
@sentry/browser (incl. Tracing + Span Streaming) 51.62 kB +0.19% +97 B 🔺
@sentry/browser (incl. Tracing, Profiling) 54.58 kB +0.14% +75 B 🔺
@sentry/browser (incl. Tracing, Replay) 91.29 kB +0.08% +64 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 80.23 kB +0.06% +43 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas) 96 kB +0.07% +67 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback) 109 kB +0.11% +110 B 🔺
@sentry/browser (incl. Feedback) 47.24 kB +0.26% +122 B 🔺
@sentry/browser (incl. sendFeedback) 34.77 kB +0.34% +117 B 🔺
@sentry/browser (incl. FeedbackAsync) 39.85 kB +0.26% +100 B 🔺
@sentry/browser (incl. Metrics) 30.72 kB +0.37% +112 B 🔺
@sentry/browser (incl. Logs) 31.01 kB +0.39% +119 B 🔺
@sentry/browser (incl. Metrics & Logs) 31.67 kB +0.38% +117 B 🔺
@sentry/react 31.54 kB +0.35% +108 B 🔺
@sentry/react (incl. Tracing) 53.95 kB +0.22% +116 B 🔺
@sentry/vue 37.66 kB +0.3% +112 B 🔺
@sentry/vue (incl. Tracing) 54.51 kB +0.21% +114 B 🔺
@sentry/svelte 29.74 kB +0.39% +114 B 🔺
@sentry/remix (Remix 3 client bundle) 56.64 kB +0.18% +97 B 🔺
CDN Bundle 31.43 kB +0.31% +97 B 🔺
CDN Bundle (incl. Tracing) 52.17 kB +0.21% +105 B 🔺
CDN Bundle (incl. Logs, Metrics) 33.62 kB +0.19% +62 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) 54.09 kB +0.13% +66 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) 74.5 kB +0.17% +122 B 🔺
CDN Bundle (incl. Tracing, Replay) 89.81 kB +0.08% +70 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 91.77 kB +0.08% +71 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) 95.97 kB +0.08% +75 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 97.97 kB +0.11% +100 B 🔺
CDN Bundle - uncompressed 92.72 kB +0.28% +253 B 🔺
CDN Bundle (incl. Tracing) - uncompressed 154.97 kB +0.14% +202 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed 99.2 kB +0.16% +158 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 160.93 kB +0.13% +202 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 229.16 kB +0.09% +184 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed 275.07 kB +0.07% +177 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 281.01 kB +0.07% +177 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 288.77 kB +0.07% +177 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 294.7 kB +0.07% +177 B 🔺
@sentry/nextjs (client) 56.31 kB +0.23% +124 B 🔺
@sentry/sveltekit (client) 52.01 kB +0.2% +103 B 🔺
@sentry/core/server 40.76 kB +0.29% +114 B 🔺
@sentry/core/browser 13.63 kB +0.83% +112 B 🔺
@sentry/node 145.31 kB +0.08% +116 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 83.33 kB +0.14% +109 B 🔺
@sentry/node - without tracing 93.56 kB +0.12% +108 B 🔺
@sentry/node - without channel injection 123.46 kB +0.09% +104 B 🔺
@sentry/aws-serverless 101.77 kB +0.09% +84 B 🔺
@sentry/cloudflare (withSentry) - minified 209.21 kB +0.13% +262 B 🔺
@sentry/cloudflare (withSentry) 518.62 kB +0.22% +1.1 kB 🔺

View base workflow run

@Lms24

Lms24 commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

The two-origin divergence Bugbot flagged is real: timestampInSeconds corrects its origin while browserPerformanceTimeOrigin keeps the one cached at init.

Fixed in #23067, stacked on top of this PR, which exposes the corrected origin and switches over the consumers where the divergence persists (INP, replay, and profiling's adjustForOriginChange, which was compensating against the stale value). #23068 then handles the case where replay observes an entry before a correction but converts it after.

Keeping it out of this PR so each change stays independently reviewable — this one is limited to how timestampInSeconds itself behaves.

@bezata

bezata commented Aug 5, 2026

Copy link
Copy Markdown

Confirmed on a real iPhone running React Native 0.86 with @sentry/core@10.67.0.

One structured log embedded its emission wall time in the message body:

  • embedded Date.now(): 1785945903901 (2026-08-05T16:05:03.901Z)
  • Sentry-stored log timestamp: 2026-08-03T07:19:17Z
  • displacement: approximately 204,346,901 ms (2.365 days)

The full structured-log stream was present under the older window with the same displacement, while error events remained wall-clock-correct. This matches the React Native Apple clock change in react-native#55977 and the symptom reported in getsentry/sentry-react-native#6510.

We applied the same per-call re-anchoring shape downstream as a version-pinned patch. Package-level regression tests against the real @sentry/core package cover an already-skewed first call, drift re-accumulating after initialization, the sub-threshold path, and no-thrash behavior. Live post-patch device verification is still pending, so I am not claiming field recovery yet.

One potentially useful addition to this PR's test suite: the current sleep test starts with an aligned first call and then accumulates drift. Our device also exercised the other entry condition, where timeOrigin + performance.now() was already about 2.37 days behind Date.now() before the first observed timestampInSeconds() call. A first-call pre-existing-skew case would pin that production shape directly.

@github-actions

Copy link
Copy Markdown
Contributor

This pull request has gone three weeks without activity. In another week, I will close it.

But! If you comment or otherwise update it, I will reset the clock, and if you apply the label PR: no-auto-close I will leave it alone ... forever!

@plgrazon

plgrazon commented Sep 1, 2026

Copy link
Copy Markdown

Getting the same issue, has this been closed permanently?

@Lms24

Lms24 commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

@plgrazon no, this is still WIP but the change is non-trivial. I'm currently completely booked on the new JS major but I hope to get some time to pick this up next week again.

@plgrazon

plgrazon commented Sep 2, 2026

Copy link
Copy Markdown

@Lms24 no worries. thank you!

@Lms24
Lms24 force-pushed the lms/fix-core-browser-timestampInSeconds-offset-clockdrift branch from 456dd81 to ded4f36 Compare September 28, 2026 13:10
@Lms24
Lms24 removed this pull request from stack #23069 September 28, 2026 14:25

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/browser-utils/src/web-vitals/spans.ts
Comment thread packages/replay-internal/src/util/createPerformanceEntries.ts Outdated
@Lms24
Lms24 added this pull request to stack #24904 September 30, 2026 15:21
@Lms24
Lms24 marked this pull request as ready for review October 1, 2026 11:41
@Lms24
Lms24 requested a review from a team as a code owner October 1, 2026 11:41
@Lms24
Lms24 requested review from logaretm and msonnb and removed request for a team October 1, 2026 11:41

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/core/src/utils/time.ts
@Lms24 Lms24 self-assigned this Oct 1, 2026
@antonis
antonis self-requested a review October 1, 2026 13:03
@Lms24
Lms24 force-pushed the lms/fix-core-browser-timestampInSeconds-offset-clockdrift branch from 4ced6e1 to 3b14c70 Compare October 1, 2026 13:40

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/replay-internal/src/util/createPerformanceEntries.ts Outdated
Comment thread packages/core/src/utils/time.ts
@Lms24
Lms24 force-pushed the lms/fix-core-browser-timestampInSeconds-offset-clockdrift branch from d2a74d9 to a6e009b Compare October 2, 2026 14:13
@Lms24 Lms24 changed the title fix(core): Correct clock drift on every timestampInSeconds call fix(core): Correct sleep clock drift on telemetry timestamps Oct 2, 2026

@JPeer264 JPeer264 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quite intense, not entirely sure if I parsed and understood everything to 100%, but from what I've grasped it looks good.

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 that reproduction somewhere online to have a before / after comparison? Or something we have in the future?

const getAbsoluteTime = (time: number | undefined): number | undefined =>
// falsy values should be preserved so that we can later on drop undefined values and
// preserve 0 vals for cross-origin resources without proper `Timing-Allow-Origin` header.
time ? msToSec(timeOrigin + time) : time;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: There is another getAbsoluteTime in ember (https://github.com/getsentry/sentry-javascript/pull/23054/changes#diff-73252b4c01e4f647d51fe02b507126d7af869092c786db502d96a6b3225fc4ecR88), but usesbrowserPerformanceTimeOrigin, like the previous implementation here. Is that on purpose?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is another getAbsoluteTime in ember

Sorry, maybe I'm missing something but I don't think there's a getAbsoluteTime in Ember at the moment (?)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@antonis antonis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🚀
I had a look at the changes and also tested that this would make the current React Native workaround obsolete.

@logaretm logaretm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, Just a suggestion and a note.

Comment thread packages/browser/src/client.ts
Comment thread packages/core/src/utils/time.ts
// See: https://github.com/getsentry/sentry-javascript/issues/2590
// See: https://github.com/mdn/content/issues/4713
// See: https://dev.to/noamr/when-a-millisecond-is-not-a-millisecond-3h6
if (Math.abs(correctedTimeOrigin + performanceNow - dateNow) > CLOCK_DRIFT_THRESHOLD_MS) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this is resolved to an absolute number, it can move the origin backward. A span could end before it starts.

Example (I used Claude to help me visualize this if condition):

  • page is loaded at 10:00:00
  • the example shows a page that is already open for 60s, but the time is set back 5 seconds at some point
                     ① span.start()   ② clock set back 5s   ③ span.end()
performance.now()    60s              61s                   62s
Date.now()           10:01:00         10:00:56              10:00:57
origin               10:00:00         09:59:55 (corrected)  09:59:55
timestamp            10:01:00         10:00:56              10:00:57

timestamp is: correctedTimeOrigin + performance.now()

duration (with this if condition) = 10:00:57 − 10:01:00 = −3s   (real: 2s)

If we update the condition to: dateNow - (correctedTimeOrigin + performanceNow) > CLOCK_DRIFT_THRESHOLD_MS, we have this:

                          ① span.start()   ② clock set back 5s   ③ span.end()
performance.now()         60s              61s                   62s
Date.now()                10:01:00         10:00:56              10:00:57
drift (Date.now() −       0s               −5s (no correction)   −5s (no correction)
  timestamp)
correctedTimeOrigin       10:00:00         10:00:00              10:00:00
timestamp                 10:01:00         10:01:01              10:01:02

duration = 10:01:02 − 10:01:00 = 2s   (real: 2s)

I think this would also fix what was fixed in #24903
But we should keep the tests.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this is resolved to an absolute number, it can move the origin backward. A span could end before it starts.

With just this PR, that's correct but with #24903 in the stack, we guarantee that span end is calculated via the monotonic clock. That is, as long as span.end() is called without an explicit end timestamp. Which I think is a fair tradeoff.

The reason why this check uses Math.abs is because we want to detect drift in both directions. As you said, the wall clock (Date.now()) can be adjusted backwards as well, so ideally we can take both into account. Unless you see a concrete reason not to correct backwards, I'd leave this as-is. WDYT?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a test though for backwards drift correction (5acd31d), since this wasn't covered before. good flag!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should correct backward, it's an edge case but the clock could be set back during timesaving-clock-shifts (or however this is called :D)

But with Math.abs here, you would get the wrong value because it's always a positive one (even when we have a backward shift). But I think it's also fine to just make sure we properly test this and maybe I'm missing something and the other PR is good enough for that.

@Lms24 Lms24 Oct 6, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only use Math.abs to detect the drift, which should work in both directions. When we then make the correction, (correctedTimeOrigin = dateNow - performanceNow) we also go back since we don't use the absolute value here.

The clock can get set back by NTP (though clankers tell me the case where it actually jumps back a few seconds is rare) and by users manually setting an earlier than actual time on their device. Daylight saving time should be fine since Date.now() returns a UTC timestamp.

I might still be missing something though if you have a concrete case in mind that fails here.

Comment thread packages/browser-utils/src/performance/entries.ts Outdated
const dateNow = safeDateNow();
export function browserPerformanceTimeOrigin(monotonicTimeInMs = 0): number | undefined {
// Makes sure `_timeOriginSegments` is set up.
timestampInSeconds();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance-related: Previously, this function used a cached value. This is useful e.g. in profiling because you loop through every sample and call this function.

Now, timestampInSeconds() is called every time, and will execute what createUnixTimestampInSecondsFunc() returns (it only creates it once because of the closure, but now also calls it every time). And this function is quite heavy as it does the drift check and reads different clocks.

We could split up setting up the time origin segments (code below, inserted here) and doing the drift check timestampInSeconds() only when needed?

Suggested change
timestampInSeconds();
if (!_cachedTimestampInSecondsFn) {
_cachedTimestampInSecondsFn = createUnixTimestampInSecondsFunc();
}

Also feel free to put this in a resuable function, then we would need to do some 🍛-ing in timestampInSeconds():

function timestampInSeconds() {
  return getTimestampInSecondsFn()();
}

Lms24 and others added 18 commits October 6, 2026 12:00
…red with

`timestampInSeconds` re-derives its time origin when it detects clock drift, so
a single origin is only valid for part of a page's lifetime. Consumers that
convert a `PerformanceEntry`'s monotonic `startTime` to wall clock time have no
way to know which one applied to a given entry, and `browserPerformanceTimeOrigin`
caches the origin resolved at SDK init and never revisits it.

Adds `performanceTimeToSeconds`, which keeps the superseded origins around and
picks the one that was in effect when the passed time was measured. Entries
reported long after the fact — INP on pagehide, replay entries buffered until
flush — therefore stay on the timeline they were recorded on instead of being
retroactively shifted by a drift that happened afterwards.

The correction boundary is the `performance.now()` value the drift was detected
at, which is an upper bound on where it actually happened; that is as close as
it can be pinned down without a second clock.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… own time origin

Routes the consumers that convert a monotonic `PerformanceEntry` time long after
the entry was recorded through `performanceTimeToSeconds`, so a clock drift
correction no longer shifts entries that were timed correctly:

- INP and CLS report on pagehide, potentially hours after the interaction or
  layout shift they describe. LCP keeps the cached origin: it starts *at* the
  origin by construction, so moving it would detach it from its pageload parent.
- Replay buffers raw entries and only converts them on flush, which for a
  long-running session can be minutes later.
- Continuous profiling samples are `performance.timeOrigin`-relative like any
  other monotonic time.

Also drops `adjustForOriginChange` from `convertJSSelfProfileToSampledFormat`.
`elapsed_since_start_ns` is a difference between two raw monotonic values, so no
origin belongs in it at all - the profile is anchored to the wall clock by the
enclosing payload's `timestamp`. The term was harmless only because it evaluated
to ~0 whenever the SDK origin matched `performance.timeOrigin`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…igin

`browserPerformanceTimeOrigin` was cached at init, so after a drift correction
resource, long task, long animation frame, click and user timing spans landed a
whole sleep before the navigation they belong to, and most were dropped by the
"started before the navigation" checks. It now takes the monotonic time being
converted and returns the origin in effect then, defaulting to the page load.

Also:
- Soft navigation LCP resolves against the origin at the navigation start.
- A correction now applies from the previous check on, so the interaction that
  wakes the SDK up after a sleep lands on the corrected timeline.
- The page load origin survives the segment cap.
- Without `performance.timeOrigin`, monotonic times convert against
  `Date.now() - performance.now()` again instead of producing `NaN`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A drift correction applies from the last check at which both clocks agreed, so
entries recorded between that check and the device going to sleep land on the
wrong side of it. Devices usually hide the page before sleeping and show it
again on wake, so checking the clocks on `visibilitychange` pins the correction
to the sleep itself rather than to whenever the SDK next takes a timestamp.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
At 5 minutes, any sleep shorter than that left spans, logs and metrics behind
the wall clock (and errors) for the rest of the page's life. 15 seconds is still
far above `Date.now()` jitter and typical NTP adjustments, so it only catches
real drift.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Drift from timer precision or NTP slewing stays well below 1s, so a lower
threshold catches more real clock jumps without false resets. Also
simplify the comments in the time utils.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…gment

If performance.timeOrigin is already wrong on the first timestamp call,
we replace it. Pushing a second segment that also starts at 0 lets the
segment cap drop the corrected origin later and keep the wrong one, so
late-converted page load times would be off by the initial skew.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The initial load span used the page load time origin. Use the origin that
was valid when the measure started instead, like all other performance
entries, so a time origin correction in between does not shift it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A page needs many sleeps or clock jumps to reach the cap. The list is
small, so a higher cap costs little and keeps more late entries correct.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Lms24
Lms24 force-pushed the lms/fix-core-browser-timestampInSeconds-offset-clockdrift branch from 6f69246 to 60ae687 Compare October 6, 2026 10:01
…ersion

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5acd31d. Configure here.

start_timestamp: (pageloadOriginMs + sleepDurationMs + resourceStartTime) / 1000,
end_timestamp: (pageloadOriginMs + sleepDurationMs + resourceStartTime + 100) / 1000,
attributes: expect.objectContaining({
'sentry.op': 'resource.script',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test uses hard-coded span attribute

Low Severity

The new addPerformanceEntries test asserts 'sentry.op' as a string literal. Testing conventions require span attribute keys to use the @sentry/conventions constant (SENTRY_OP) so assertions stay aligned with production code. This was flagged because it was mentioned in the review rules file.

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Reviewed by Cursor Bugbot for commit 5acd31d. Configure here.

…oSeconds

Add an optional entryStartTimeInMs parameter to performanceTimeToSeconds,
so all timings of one entry can use the time origin from the entry's
start. Use it instead of looking up the origin and converting by hand.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Timing issues using Performance API

7 participants