Skip to content
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

Proposal: clock-drift correction, potentially with opt-in #206

Open
noamr opened this issue Feb 5, 2023 · 25 comments
Open

Proposal: clock-drift correction, potentially with opt-in #206

noamr opened this issue Feb 5, 2023 · 25 comments

Comments

@noamr
Copy link
Contributor

noamr commented Feb 5, 2023

The problem:

The monotonic clock is accurate for short measurements. But as time goes by, e.g. in long-session web apps, it slowly starts to be less and less aligned with the wall clock. The problem exacerbates when the system throttles or pauses the monotonic clock when a process sleeps. This means that the equation performance.timeOrigin + performance.now() === new Date() becomes less and less reliable over time.

See w3c/hr-time#141 and several related discussions.

The proposal:

Compute a clock offset value per performance entry, at the time of adding the entry to the timeline. This would be something along the lines of clockOffset = new Date() - performance.timeOrigin - performance.now(). This value can help produce wall-clock-aligned timeline out of the standard monotonic-based timeline. The differences between values inside an entry would remain monotonically aligned with each other.
This keeps the benefit of monotonic clocks: accurate offsets, while mitigating the main issue with it: drift from the wall clock.

We can use this offset in several ways, that are somewhat interchangeable/bikesheddable:

  1. Add a clockOffset or drift or so to performance timeline entries and let users of the API correct the clocks themselves if they wish.
  2. Automatically align entries to the wall clock (while maintaining purely monotonic deltas inside an entry)
  3. Make this an opt-in for getEntries{ByFoo} functions or for performance observers.

/cc @yoavweiss @jyasskin @nicjansma

@yoavweiss
Copy link
Contributor

That's interesting!! I wonder if (2) is feasible without creating confusion if/when folks compare timestamps across different entries. Or should we provide helpers for such cross-entry comparisons?

(1) would offload the complexity of dealing with these 2 different clocks to users, but maybe that's ok, as most users don't care about wallclock offsets (although arguably, maybe they should).

@noamr
Copy link
Contributor Author

noamr commented Feb 6, 2023

That's interesting!! I wonder if (2) is feasible without creating confusion if/when folks compare timestamps across different entries. Or should we provide helpers for such cross-entry comparisons?

I think the main issue with (2) is that it would stop correlating with other things like the devtools and perfetto timelines.
For overlapping entries I'm less concerned because it's usually short timespans where the drift is not significant.

(1) would offload the complexity of dealing with these 2 different clocks to users

Doesn't (3) address this? Users would opt-in to align to the wall clock for a particular query.

@jyasskin
Copy link
Member

jyasskin commented Feb 8, 2023

Is there a reason to report the clock offset instead of the wall clock time of the performance entry? I suspect developers will mix up the sign of an offset.

The rate at which client machines' time drifts is probably useful for fingerprinting, but I think developers can already compute that by just calling Date.now() themselves.

@noamr
Copy link
Contributor Author

noamr commented Feb 9, 2023

Is there a reason to report the clock offset instead of the wall clock time of the performance entry? I suspect developers will mix up the sign of an offset.

Either is fine. All those values derive from each other, we can choose what to expose.

The rate at which client machines' time drifts is probably useful for fingerprinting, but I think developers can already compute that by just calling Date.now() themselves.

Yes, it's exactly like looking at performance.now() - Date.now() over time manually.
What would help with fingerprinting is fixing this on the hr-time level where the monotonic clock tries to do this automatically and not expose the system's monotonic clock value.

@i-m-abbhay
Copy link

Are there any examples or use cases where the clock offset value has been implemented and used effectively?

@Forchapeatl
Copy link

@noamr , May I please, handle this issue ? Or has it been reserved for the GSOC 2023 Contribution period ?

@noamr
Copy link
Contributor Author

noamr commented Feb 28, 2023

@noamr , May I please, handle this issue ? Or has it been reserved for the GSOC 2023 Contribution period ?

Give it a shot! When it's time to pick GSoC projects we can see how far along you are.

@Forchapeatl
Copy link

For more time precision over task which take long amount of time to perform (long session web apps),
A developer may compute a more accurate monotonic time by adding the performance.now() to the performance.timeOrigin attribute.

            var mark_start = performance.timeOrigin + performance.now();
            doTask(); // Some task
            var duration = performance.timeOrigin + performance.now() - mark_start;  

Hello @noamr , please am I on the right track here ?

@noamr
Copy link
Contributor Author

noamr commented Mar 1, 2023

For more time precision over task which take long amount of time to perform (long session web apps), A developer may compute a more accurate monotonic time by adding the performance.now() to the performance.timeOrigin attribute.

            var mark_start = performance.timeOrigin + performance.now();
            doTask(); // Some task
            var duration = performance.timeOrigin + performance.now() - mark_start;  

Hello @noamr , please am I on the right track here ?

Hmm not really :)
The above wouldn't give you different results from simply using entry.duration.

I wouldn't use the term more accurate when it comes to clocks.
"More accurate" is context-specific, as in, which clocks are you trying to compare? Entries to each other? Entries to entries in a different timeline? Entries to the time in UTC?

The current situation is that when we start a window, we start a new "monotonic clock session". Think of it as flipping an hourglass. Within that session, comparisons are accurate enough. But when you want to compare it to session started in other windows, you have to synchronize them somehow. A lot of this is written in the OP...

@Forchapeatl
Copy link

Forchapeatl commented Mar 1, 2023

Thank you @noamr , I find it difficult to contextualize the expected result. Please are we talking of w3c/performance-timeline or w3c/hr-time/ , may I have some documentation on how these two are related ?

A lot of this is written in the OP

Please , is the OP some documentation ?

In essence , the clock-drift correction , is the comparison between multiple clock sessions on different windows ?

@noamr
Copy link
Contributor Author

noamr commented Mar 1, 2023

The OP means "the original post" (the description of this issue).
"the comparison between multiple clock sessions on different windows" is part of the problem.

@Forchapeatl
Copy link

okay , thank you

@noamr
Copy link
Contributor Author

noamr commented Mar 1, 2023

Thank you @noamr , I find it difficult to contextualize the expected result. Please are we talking of w3c/performance-timeline or w3c/hr-time/ , may I have some documentation on how these two are related ?

Sorry, I just saw that I only answered the second part.
The two issues are related. It's not too simple!
The main problem is that we're trying to synchronize two inaccurate clocks, in different contexts.
What I'm proposing here is to fix this on the timeline level rather than the clock level: give people who use the timelime indication of the clock-drift, and perhaps helpers to correct the drift in a way that fits their expectations, rather than try to adjust the clock.

@GarretTomlin
Copy link

GarretTomlin commented Mar 4, 2023

To correct the clock drift issue, I propose computing the clock offset value per performance entry, as suggested in the discussion. This value can be computed as follows

clockOffset = new Date() - performance.timeOrigin - performance.now();

This value can be added as a property to performance timeline entries, allowing users of the API to correct the clocks themselves if they wish. Additionally, the clock offset value can be used to automatically align entries to the wall clock, while maintaining purely monotonic deltas inside an entry.

To implement this, I suggest creating a new performanceEntry object that includes the clockOffset property. This object can be created when a new performance entry is added to the timeline, and the clockOffset property can be set using the above formula. This performanceEntry object can then be added to the timeline as usual.

I also suggest making this an opt-in for getEntries{ByFoo} functions or for performance observers. This will give users the choice to align their entries to the wall clock or not.

I believe this approach is straightforward and easy to implement. It offloads the complexity of dealing with the two different clocks to users while still providing the option to align entries to the wall clock if desired. I plan to use this approach as part of my Google Summer of Code proposal for clock-drift correction. Any feedback would be greatly appreciated thank you

@noamr
Copy link
Contributor Author

noamr commented Mar 4, 2023

Hi Garret, thanks for your proposal and for your interest in this issue!

To correct the clock drift issue, I propose computing the clock offset value per performance entry, as suggested in the discussion. This value can be computed as follows

clockOffset = new Date() - performance.timeOrigin - performance.now();

This value can be added as a property to performance timeline entries, allowing users of the API to correct the clocks themselves if they wish. Additionally, the clock offset value can be used to automatically align entries to the wall clock, while maintaining purely monotonic deltas inside an entry.

To implement this, I suggest creating a new performanceEntry object that includes the clockOffset property. This object can be created when a new performance entry is added to the timeline, and the clockOffset property can be set using the above formula. This performanceEntry object can then be added to the timeline as usual.

Wouldn't this double the amount of entries in the timeline though?

@GarretTomlin
Copy link

Hi Garret, thanks for your proposal and for your interest in this issue!

To correct the clock drift issue, I propose computing the clock offset value per performance entry, as suggested in the discussion. This value can be computed as follows
clockOffset = new Date() - performance.timeOrigin - performance.now();
This value can be added as a property to performance timeline entries, allowing users of the API to correct the clocks themselves if they wish. Additionally, the clock offset value can be used to automatically align entries to the wall clock, while maintaining purely monotonic deltas inside an entry.
To implement this, I suggest creating a new performanceEntry object that includes the clockOffset property. This object can be created when a new performance entry is added to the timeline, and the clockOffset property can be set using the above formula. This performanceEntry object can then be added to the timeline as usual.

Wouldn't this double the amount of entries in the timeline though?

Yes it would adding a start and end event for each event would double the number of entries in the timeline but it would also provide a more detailed view of the events by showing the exact start and end times, which could be helpful for analysis and planning purposes an alternative solution though could be to add a duration column to the timeline this would allow for the duration of each event to be recorded without adding additional entries to the timeline

@GarretTomlin
Copy link

GarretTomlin commented Mar 4, 2023

@noamr which solution do you think is most suitable and can I email you my proposal for reviewing would greatly appreciate it thank you

@Forchapeatl
Copy link

For option (1)

  1. Add a clockOffset or drift or so to performance timeline entries and let users of the API correct the clocks themselves if they wish.

This is less feasible to adopt because:

  1. performance timeline entries as performance Entry attributes are Read Only Attributes. These attributes are protected and can be modified only by PerformanceTiming events. Hence giving users the ability correct the clocks will will will disrupt the integrity of the performance Entry attributes.

https://github.com/chromium/chromium/blob/260f34381e685b6ce673838a5929aacf57ceb7c4/third_party/blink/renderer/core/timing/performance_entry.idl#L31-L42

  1. Secondly the notion of clock drift is not that popular as some developers may not know it accounts for the monotonic adjustments across different contexts. Thus understanding the differences between clock drift and the start_time might be difficult for inexperienced developers .

  2. i support the point raised by @yoavweiss at comment #2

That's interesting!! I wonder if (2) is feasible without creating confusion if/when folks compare timestamps across different entries. Or should we provide helpers for such cross-entry comparisons?

(1) would offload the complexity of dealing with these 2 different clocks to users, but maybe that's ok, as most users don't care about wallclock offsets (although arguably, maybe they should).

As for option (3)

Doesn't (3) address this? Users would opt-in to align to the wall clock for a particular query.

They way I see it we would have to create 2 new entrie attributes monotonic_startTime and monotonic_duration , we also set a boolean flag to hold the opt-in of the user (true / flase) . But we have to account for inexperienced programmers who are new to the concept of the monotonic increasing clock thus defaulting the opt-in value to true.

As for option (2)

  1. Automatically align entries to the wall clock (while maintaining purely monotonic deltas inside an entry)
  • i see this as a good compromise to adding new 2 entries entries of option (3) and reducing the security of protected (read only) entries of option (1).

  • This will also align all new and old sessions the same wall clock , hence solving the problem multiple clocks synchronization on different contexts.

  • In experienced developers will also have a smooth performance timeline matrix which accounts for system throttles or pauses the monotonic clock when a process sleeps.

@noamr
Copy link
Contributor Author

noamr commented Mar 6, 2023

For option (1)

  1. Add a clockOffset or drift or so to performance timeline entries and let users of the API correct the clocks themselves if they wish.

This is less feasible to adopt because:

  1. performance timeline entries as performance Entry attributes are Read Only Attributes. These attributes are protected and can be modified only by PerformanceTiming events. Hence giving users the ability correct the clocks will will will disrupt the integrity of the performance Entry attributes.

https://github.com/chromium/chromium/blob/260f34381e685b6ce673838a5929aacf57ceb7c4/third_party/blink/renderer/core/timing/performance_entry.idl#L31-L42

  1. Secondly the notion of clock drift is not that popular as some developers may not know it accounts for the monotonic adjustments across different contexts. Thus understanding the differences between clock drift and the start_time might be difficult for inexperienced developers .
  2. i support the point raised by @yoavweiss at comment #2

That's interesting!! I wonder if (2) is feasible without creating confusion if/when folks compare timestamps across different entries. Or should we provide helpers for such cross-entry comparisons?
(1) would offload the complexity of dealing with these 2 different clocks to users, but maybe that's ok, as most users don't care about wallclock offsets (although arguably, maybe they should).

As for option (3)

Doesn't (3) address this? Users would opt-in to align to the wall clock for a particular query.

They way I see it we would have to create 2 new entrie attributes monotonic_startTime and monotonic_duration , we also set a boolean flag to hold the opt-in of the user (true / flase) . But we have to account for inexperienced programmers who are new to the concept of the monotonic increasing clock thus defaulting the opt-in value to true.

Duration should be close enough between monotonic clock/wall-clock.

@noamr
Copy link
Contributor Author

noamr commented Mar 6, 2023

@Forchapeatl @GarretTomlin and whoever is interested in this topic, I propose understanding the context better before diving in to the solution. Try to use the performance APIs, try to reproduce the issue (e.g. with a long running context). Dive deep into the issues in Github & in Chromium. I believe that the solution will end up being simple but to get it right would require a lot of context and attention.

@Forchapeatl
Copy link

Forchapeatl commented Mar 8, 2023

image

Please , correct me if I am wrong / mislead

when it comes to measuring the time taken for a task to accomplish results we can use the system clock Date.now()

var mark_start = Date.now();
doTask(); // Some task
var duration = Date.now() - mark_start;

But the difference between start-end using Date.now() can't be trusted as a change in the user's system time would skew the results.

var mark_start = performance.now();
doTask(); // Some task
var duration = performance.now() - mark_start;

Similarly we can use the monotonic clock Performance.now() - which relative to page load . But a difference between start-end of performance.now() can't be used as the underlying clock potentially stops if the user sleeps their device.

Hence we need to account for time which is reliable across sleep / wake cycles during task execution.

clockOffset = new Date() - performance.timeOrigin - performance.now()

Options (1) is fine if you ask me. Since we are accounting for two different clocks option (2) might cost confusion as the drift behavior is different on platforms. Option (3) seems to require the some replication of entry attributes and seems more complex than it sounds.

Just to clarify , option (1) means the PerformanceEntry will have 5 attributes: name, entryType, startTime, duration and clockOffset ?

@Yougesh978
Copy link

@noamr I would like to contribute to this issue as part of my GSOC contribution .
To correct the clock drift issue, The approach that I am proposing aims to mitigate the issue of clock drift in performance measurement by aligning performance timeline entries with the wall clock. The approach involves calculating a clock offset value per performance entry when adding the entry to the timeline. This value is then added to performance timeline entries to align them with the wall clock.

The proposed approach has some benefits. By aligning performance timeline entries with the wall clock, we can more accurately synchronize events across multiple systems that rely on the wall clock. This makes it easier to identify and troubleshoot issues in web applications. Additionally, by maintaining the monotonic nature of the deltas within an entry, we retain the benefits of the monotonic clock, which provides accurate offsets.

I plan to use this approach as part of my Google Summer of Code proposal for clock-drift correction. Any feedback would be greatly appreciated thank you.

There may be other approaches that could be used to mitigate the issue of clock drift in performance measurement. One such approach could be to use a hybrid clock that combines the benefits of both a monotonic clock and a wall clock. A hybrid clock could provide accurate offsets while still being aligned with the wall clock. This would eliminate the need for clock offset values and could potentially provide more accurate performance measurements over longer periods.

@jyasskin
Copy link
Member

jyasskin commented Mar 9, 2023

I'd like to remind potential GSOC contributors that the Chromium GSoC 2023 Project Ideas and Info document says, "Please be mindful of [the] mentor’s time and do some homework before reaching out to them directly." That means you should read all of the comments here before commenting, and make sure any comments you leave say new things instead of repeating parts of other people's comments. Your GSOC proposal belongs in the GSOC form after March 20, not in comments on this issue. If you need help figuring out whether it's helpful to leave a comment on the public issue, which notifies everyone who's ever participated here, you can email the mentor directly.

@Nandini99-git
Copy link

I would like to contribute on this issue for GSOC 2023.

@ayushgupta9906
Copy link

ayushgupta9906 commented Apr 2, 2023

When it comes to measuring the time taken for a task to accomplish results we can use the system clock Date.now()

var mark_start = Date.now();
doTask(); // Some task
var duration = Date.now() - mark_start;

But the difference between start-end using Date.now() can't be trusted as a change in the user's system time would skew the results.

var mark_start = performance.now();
doTask(); // Some task
var duration = performance.now() - mark_start;

clockOffset = new Date() - performance.timeOrigin - performance.now()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants