w3c / performance-timeline

Performance Timeline
https://w3c.github.io/performance-timeline/
Other
111 stars 34 forks source link

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

Open noamr opened 1 year ago

noamr commented 1 year ago

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 https://github.com/w3c/hr-time/issues/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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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

Forchapeatl commented 1 year ago

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

noamr commented 1 year ago

@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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

okay , thank you

noamr commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

@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 commented 1 year ago

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)
noamr commented 1 year ago

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 commented 1 year ago

@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 commented 1 year ago

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 commented 1 year ago

@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 commented 1 year ago

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 commented 1 year ago

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

ayushgupta9906 commented 1 year ago

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()