diff options
author | Martin Robinson <mrobinson@igalia.com> | 2024-09-05 11:50:09 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-05 18:50:09 +0000 |
commit | 312cf0df08e8a5044d286734bfdf3d6f0caff8dd (patch) | |
tree | b84b61412d85cc3b36640ab0d96510ceda0c189f /components | |
parent | 35baf056f6feb9eccfe36854da88d4fc454b654d (diff) | |
download | servo-312cf0df08e8a5044d286734bfdf3d6f0caff8dd.tar.gz servo-312cf0df08e8a5044d286734bfdf3d6f0caff8dd.zip |
script: Create a `CrossProcessInstant` to enable serializable monotonic time (#33282)
Up until now, Servo was using a very old version of time to get a
cross-process monotonic timestamp (using `time::precise_time_ns()`).
This change replaces the usage of old time with a new serializable
monotonic time called `CrossProcessInstant` and uses it where `u64`
timestamps were stored before. The standard library doesn't provide this
functionality because it isn't something you can do reliably on all
platforms. The idea is that we do our best and then fall back
gracefully.
This is a big change, because Servo was using `u64` timestamps all over
the place some as raw values taken from `time::precise_time_ns()` and
some as relative offsets from the "navigation start," which is a concept
similar to DOM's `timeOrigin` (but not exactly the same). It's very
difficult to fix this situation without fixing it everywhere as the
`Instant` concept is supposed to be opaque. The good thing is that this
change clears up all ambiguity when passing times as a `time::Duration`
is unit agnostic and a `CrossProcessInstant` represents an absolute
moment in time.
The `time` version of `Duration` is used because it can both be negative
and is also serializable.
Good things:
- No need too pass around `time` and `time_precise` any longer.
`CrossProcessInstant` is also precise and monotonic.
- The distinction between a time that is unset or at `0` (at some kind
of timer epoch) is now gone.
There still a lot of work to do to clean up timing, but this is the
first step. In general, I've tried to preserve existing behavior, even
when not spec compliant, as much as possible. I plan to submit followup
PRs fixing some of the issues I've noticed.
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Diffstat (limited to 'components')
39 files changed, 746 insertions, 602 deletions
diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index 931095e38b6..2b072481f9a 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -11,6 +11,7 @@ use std::iter::once; use std::rc::Rc; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +use base::cross_process_instant::CrossProcessInstant; use base::id::{PipelineId, TopLevelBrowsingContextId, WebViewId}; use base::{Epoch, WebRenderEpochToU16}; use compositing_traits::{ @@ -2147,10 +2148,7 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> { // ones that the paint metrics recorder is expecting. In that case, we get the current // time, inform layout about it and remove the pending metric from the list. if !self.pending_paint_metrics.is_empty() { - let paint_time = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_nanos() as u64; + let paint_time = CrossProcessInstant::now(); let mut to_remove = Vec::new(); // For each pending paint metrics pipeline id for (id, pending_epoch) in &self.pending_paint_metrics { diff --git a/components/devtools/actors/network_event.rs b/components/devtools/actors/network_event.rs index c090b0f2ce1..a0ba5cb0c15 100644 --- a/components/devtools/actors/network_event.rs +++ b/components/devtools/actors/network_event.rs @@ -6,7 +6,7 @@ //! Handles interaction with the remote web console on network events (HTTP requests, responses) in Servo. use std::net::TcpStream; -use std::time::{SystemTime, UNIX_EPOCH}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use chrono::{Local, LocalResult, TimeZone}; use devtools_traits::{HttpRequest as DevtoolsHttpRequest, HttpResponse as DevtoolsHttpResponse}; @@ -26,8 +26,8 @@ struct HttpRequest { body: Option<Vec<u8>>, started_date_time: SystemTime, time_stamp: i64, - connect_time: u64, - send_time: u64, + connect_time: Duration, + send_time: Duration, } struct HttpResponse { @@ -300,8 +300,8 @@ impl Actor for NetworkEventActor { let timings_obj = Timings { blocked: 0, dns: 0, - connect: self.request.connect_time, - send: self.request.send_time, + connect: self.request.connect_time.as_millis() as u64, + send: self.request.send_time.as_millis() as u64, wait: 0, receive: 0, }; @@ -345,8 +345,8 @@ impl NetworkEventActor { .duration_since(UNIX_EPOCH) .unwrap_or_default() .as_secs() as i64, - send_time: 0, - connect_time: 0, + send_time: Duration::ZERO, + connect_time: Duration::ZERO, }, response: HttpResponse { headers: None, @@ -493,7 +493,7 @@ impl NetworkEventActor { } } - pub fn total_time(&self) -> u64 { + pub fn total_time(&self) -> Duration { self.request.connect_time + self.request.send_time } } diff --git a/components/devtools/lib.rs b/components/devtools/lib.rs index 00690ee485f..d5802ad7154 100644 --- a/components/devtools/lib.rs +++ b/components/devtools/lib.rs @@ -515,7 +515,7 @@ fn run_server( update_type: "eventTimings".to_owned(), }; let extra = EventTimingsUpdateMsg { - total_time: actor.total_time(), + total_time: actor.total_time().as_millis() as u64, }; for stream in &mut connections { let _ = stream.write_merged_json_packet(&msg, &extra); diff --git a/components/fonts/font.rs b/components/fonts/font.rs index 809d852ef26..02cb6da8077 100644 --- a/components/fonts/font.rs +++ b/components/fonts/font.rs @@ -54,6 +54,7 @@ pub const BASE: u32 = ot_tag!('B', 'A', 'S', 'E'); pub const LAST_RESORT_GLYPH_ADVANCE: FractionalPixel = 10.0; +/// Nanoseconds spent shaping text across all layout threads. static TEXT_SHAPING_PERFORMANCE_COUNTER: AtomicUsize = AtomicUsize::new(0); // PlatformFont encapsulates access to the platform's font API, @@ -800,6 +801,7 @@ impl RunMetrics { } } +/// Get the number of nanoseconds spent shaping text across all threads. pub fn get_and_reset_text_shaping_performance_counter() -> usize { TEXT_SHAPING_PERFORMANCE_COUNTER.swap(0, Ordering::SeqCst) } diff --git a/components/layout_thread/Cargo.toml b/components/layout_thread/Cargo.toml index 14d4b263d5f..66f7b0d2a95 100644 --- a/components/layout_thread/Cargo.toml +++ b/components/layout_thread/Cargo.toml @@ -40,6 +40,7 @@ servo_config = { path = "../config" } servo_url = { path = "../url" } style = { workspace = true } style_traits = { workspace = true } +time_03 = { workspace = true } url = { workspace = true } webrender_api = { workspace = true } webrender_traits = { workspace = true } diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 4f677655bdc..fbf2621c8a9 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -15,6 +15,7 @@ use std::process; use std::sync::{Arc, LazyLock, Mutex}; use app_units::Au; +use base::cross_process_instant::CrossProcessInstant; use base::id::{BrowsingContextId, PipelineId}; use base::Epoch; use embedder_traits::resources::{self, Resource}; @@ -101,6 +102,7 @@ use style::values::computed::font::GenericFontFamily; use style::values::computed::{FontSize, Length, NonNegativeLength}; use style::values::specified::font::KeywordInfo; use style_traits::{CSSPixel, DevicePixel, SpeculativePainter}; +use time_03::Duration; use url::Url; use webrender_api::{units, ColorF, HitTestFlags}; use webrender_traits::WebRenderScriptApi; @@ -541,7 +543,7 @@ impl Layout for LayoutThread { .collect(); } - fn set_epoch_paint_time(&mut self, epoch: Epoch, paint_time: u64) { + fn set_epoch_paint_time(&mut self, epoch: Epoch, paint_time: CrossProcessInstant) { self.paint_time_metrics.maybe_set_metric(epoch, paint_time); } } @@ -1112,13 +1114,15 @@ impl LayoutThread { }, ); // TODO(pcwalton): Measure energy usage of text shaping, perhaps? - let text_shaping_time = get_and_reset_text_shaping_performance_counter() / num_threads; + let text_shaping_time = Duration::nanoseconds( + (get_and_reset_text_shaping_performance_counter() / num_threads) as i64, + ); profile_time::send_profile_data( profile_time::ProfilerCategory::LayoutTextShaping, self.profiler_metadata(), &self.time_profiler_chan, - 0, - text_shaping_time as u64, + CrossProcessInstant::epoch(), + CrossProcessInstant::epoch() + text_shaping_time, ); // Retrieve the (possibly rebuilt) root flow. diff --git a/components/layout_thread_2020/lib.rs b/components/layout_thread_2020/lib.rs index 4038fa92952..321de57788d 100644 --- a/components/layout_thread_2020/lib.rs +++ b/components/layout_thread_2020/lib.rs @@ -16,6 +16,7 @@ use std::process; use std::sync::{Arc, LazyLock}; use app_units::Au; +use base::cross_process_instant::CrossProcessInstant; use base::id::{BrowsingContextId, PipelineId}; use base::Epoch; use embedder_traits::resources::{self, Resource}; @@ -473,7 +474,7 @@ impl Layout for LayoutThread { .collect(); } - fn set_epoch_paint_time(&mut self, epoch: Epoch, paint_time: u64) { + fn set_epoch_paint_time(&mut self, epoch: Epoch, paint_time: CrossProcessInstant) { self.paint_time_metrics.maybe_set_metric(epoch, paint_time); } } diff --git a/components/metrics/lib.rs b/components/metrics/lib.rs index 3effde8cac0..3186e35c216 100644 --- a/components/metrics/lib.rs +++ b/components/metrics/lib.rs @@ -5,8 +5,9 @@ use std::cell::{Cell, RefCell}; use std::cmp::Ordering; use std::collections::HashMap; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::time::Duration; +use base::cross_process_instant::CrossProcessInstant; use base::id::PipelineId; use base::Epoch; use ipc_channel::ipc::IpcSender; @@ -22,10 +23,14 @@ pub trait ProfilerMetadataFactory { } pub trait ProgressiveWebMetric { - fn get_navigation_start(&self) -> Option<u64>; - fn set_navigation_start(&mut self, time: u64); + fn get_navigation_start(&self) -> Option<CrossProcessInstant>; + fn set_navigation_start(&mut self, time: CrossProcessInstant); fn get_time_profiler_chan(&self) -> &ProfilerChan; - fn send_queued_constellation_msg(&self, name: ProgressiveWebMetricType, time: u64); + fn send_queued_constellation_msg( + &self, + name: ProgressiveWebMetricType, + time: CrossProcessInstant, + ); fn get_url(&self) -> &ServoUrl; } @@ -50,40 +55,36 @@ fn set_metric<U: ProgressiveWebMetric>( metadata: Option<TimerMetadata>, metric_type: ProgressiveWebMetricType, category: ProfilerCategory, - attr: &Cell<Option<u64>>, - metric_time: Option<u64>, + attr: &Cell<Option<CrossProcessInstant>>, + metric_time: Option<CrossProcessInstant>, url: &ServoUrl, ) { - let navigation_start = match pwm.get_navigation_start() { - Some(time) => time, - None => { - warn!("Trying to set metric before navigation start"); - return; - }, - }; - let now = match metric_time { - Some(time) => time, - None => SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_nanos() as u64, - }; - let time = now - navigation_start; - attr.set(Some(time)); + let metric_time = metric_time.unwrap_or_else(CrossProcessInstant::now); + attr.set(Some(metric_time)); // Queue performance observer notification. - pwm.send_queued_constellation_msg(metric_type, time); + pwm.send_queued_constellation_msg(metric_type, metric_time); // Send the metric to the time profiler. - send_profile_data(category, metadata, pwm.get_time_profiler_chan(), time, time); + send_profile_data( + category, + metadata, + pwm.get_time_profiler_chan(), + metric_time, + metric_time, + ); // Print the metric to console if the print-pwm option was given. if opts::get().print_pwm { + let navigation_start = pwm + .get_navigation_start() + .unwrap_or_else(CrossProcessInstant::epoch); println!( - "Navigation start: {}", - pwm.get_navigation_start().unwrap().to_ms() + "{:?} {:?} {:?}", + url, + metric_type, + (metric_time - navigation_start).as_seconds_f64() ); - println!("{:?} {:?} {:?}", url, metric_type, time.to_ms()); } } @@ -96,13 +97,13 @@ fn set_metric<U: ProgressiveWebMetric>( #[derive(MallocSizeOf)] pub struct InteractiveMetrics { /// when we navigated to the page - navigation_start: Option<u64>, + navigation_start: Option<CrossProcessInstant>, /// indicates if the page is visually ready - dom_content_loaded: Cell<Option<u64>>, + dom_content_loaded: Cell<Option<CrossProcessInstant>>, /// main thread is available -- there's been a 10s window with no tasks longer than 50ms - main_thread_available: Cell<Option<u64>>, + main_thread_available: Cell<Option<CrossProcessInstant>>, // max(main_thread_available, dom_content_loaded) - time_to_interactive: Cell<Option<u64>>, + time_to_interactive: Cell<Option<CrossProcessInstant>>, #[ignore_malloc_size_of = "can't measure channels"] time_profiler_chan: ProfilerChan, url: ServoUrl, @@ -110,13 +111,13 @@ pub struct InteractiveMetrics { #[derive(Clone, Copy, Debug, MallocSizeOf)] pub struct InteractiveWindow { - start: SystemTime, + start: CrossProcessInstant, } impl Default for InteractiveWindow { fn default() -> Self { Self { - start: SystemTime::now(), + start: CrossProcessInstant::now(), } } } @@ -127,29 +128,23 @@ impl InteractiveWindow { // restart: there was a task > 50ms // not all documents are interactive pub fn start_window(&mut self) { - self.start = SystemTime::now(); + self.start = CrossProcessInstant::now(); } /// check if 10s has elapsed since start pub fn needs_check(&self) -> bool { - SystemTime::now() - .duration_since(self.start) - .unwrap_or_default() >= - INTERACTIVE_WINDOW_SECONDS + CrossProcessInstant::now() - self.start > INTERACTIVE_WINDOW_SECONDS } - pub fn get_start(&self) -> u64 { + pub fn get_start(&self) -> CrossProcessInstant { self.start - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_nanos() as u64 } } #[derive(Debug)] pub enum InteractiveFlag { DOMContentLoaded, - TimeToInteractive(u64), + TimeToInteractive(CrossProcessInstant), } impl InteractiveMetrics { @@ -166,26 +161,22 @@ impl InteractiveMetrics { pub fn set_dom_content_loaded(&self) { if self.dom_content_loaded.get().is_none() { - self.dom_content_loaded.set(Some( - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_nanos() as u64, - )); + self.dom_content_loaded + .set(Some(CrossProcessInstant::now())); } } - pub fn set_main_thread_available(&self, time: u64) { + pub fn set_main_thread_available(&self, time: CrossProcessInstant) { if self.main_thread_available.get().is_none() { self.main_thread_available.set(Some(time)); } } - pub fn get_dom_content_loaded(&self) -> Option<u64> { + pub fn get_dom_content_loaded(&self) -> Option<CrossProcessInstant> { self.dom_content_loaded.get() } - pub fn get_main_thread_available(&self) -> Option<u64> { + pub fn get_main_thread_available(&self) -> Option<CrossProcessInstant> { self.main_thread_available.get() } @@ -225,7 +216,7 @@ impl InteractiveMetrics { ); } - pub fn get_tti(&self) -> Option<u64> { + pub fn get_tti(&self) -> Option<CrossProcessInstant> { self.time_to_interactive.get() } @@ -235,15 +226,20 @@ impl InteractiveMetrics { } impl ProgressiveWebMetric for InteractiveMetrics { - fn get_navigation_start(&self) -> Option<u64> { + fn get_navigation_start(&self) -> Option<CrossProcessInstant> { self.navigation_start } - fn set_navigation_start(&mut self, time: u64) { + fn set_navigation_start(&mut self, time: CrossProcessInstant) { self.navigation_start = Some(time); } - fn send_queued_constellation_msg(&self, _name: ProgressiveWebMetricType, _time: u64) {} + fn send_queued_constellation_msg( + &self, + _name: ProgressiveWebMetricType, + _time: CrossProcessInstant, + ) { + } fn get_time_profiler_chan(&self) -> &ProfilerChan { &self.time_profiler_chan @@ -257,9 +253,9 @@ impl ProgressiveWebMetric for InteractiveMetrics { // https://w3c.github.io/paint-timing/ pub struct PaintTimeMetrics { pending_metrics: RefCell<HashMap<Epoch, (Option<TimerMetadata>, bool)>>, - navigation_start: u64, - first_paint: Cell<Option<u64>>, - first_contentful_paint: Cell<Option<u64>>, + navigation_start: CrossProcessInstant, + first_paint: Cell<Option<CrossProcessInstant>>, + first_contentful_paint: Cell<Option<CrossProcessInstant>>, pipeline_id: PipelineId, time_profiler_chan: ProfilerChan, constellation_chan: IpcSender<LayoutMsg>, @@ -274,7 +270,7 @@ impl PaintTimeMetrics { constellation_chan: IpcSender<LayoutMsg>, script_chan: IpcSender<ConstellationControlMsg>, url: ServoUrl, - navigation_start: u64, + navigation_start: CrossProcessInstant, ) -> PaintTimeMetrics { PaintTimeMetrics { pending_metrics: RefCell::new(HashMap::new()), @@ -338,7 +334,7 @@ impl PaintTimeMetrics { } } - pub fn maybe_set_metric(&self, epoch: Epoch, paint_time: u64) { + pub fn maybe_set_metric(&self, epoch: Epoch, paint_time: CrossProcessInstant) { if self.first_paint.get().is_some() && self.first_contentful_paint.get().is_some() { // If we already set all paint metrics we just bail out. return; @@ -370,25 +366,29 @@ impl PaintTimeMetrics { } } - pub fn get_first_paint(&self) -> Option<u64> { + pub fn get_first_paint(&self) -> Option<CrossProcessInstant> { self.first_paint.get() } - pub fn get_first_contentful_paint(&self) -> Option<u64> { + pub fn get_first_contentful_paint(&self) -> Option<CrossProcessInstant> { self.first_contentful_paint.get() } } impl ProgressiveWebMetric for PaintTimeMetrics { - fn get_navigation_start(&self) -> Option<u64> { + fn get_navigation_start(&self) -> Option<CrossProcessInstant> { Some(self.navigation_start) } - fn set_navigation_start(&mut self, time: u64) { + fn set_navigation_start(&mut self, time: CrossProcessInstant) { self.navigation_start = time; } - fn send_queued_constellation_msg(&self, name: ProgressiveWebMetricType, time: u64) { + fn send_queued_constellation_msg( + &self, + name: ProgressiveWebMetricType, + time: CrossProcessInstant, + ) { let msg = ConstellationControlMsg::PaintMetric(self.pipeline_id, name, time); if let Err(e) = self.script_chan.send(msg) { warn!("Sending metric to script thread failed ({}).", e); diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index ce74fdaba78..fcf0857dbb5 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -9,6 +9,7 @@ use std::sync::{Arc as StdArc, Condvar, Mutex, RwLock}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use async_recursion::async_recursion; +use base::cross_process_instant::CrossProcessInstant; use base::id::{HistoryStateId, PipelineId}; use crossbeam_channel::Sender; use devtools_traits::{ @@ -116,10 +117,6 @@ impl Default for HttpState { } } -fn precise_time_ms() -> u64 { - time::precise_time_ns() / (1000 * 1000) -} - // Step 3 of https://fetch.spec.whatwg.org/#concept-fetch. pub fn set_default_accept(destination: Destination, headers: &mut HeaderMap) { if headers.contains_key(header::ACCEPT) { @@ -353,19 +350,22 @@ fn prepare_devtools_request( headers: HeaderMap, body: Option<Vec<u8>>, pipeline_id: PipelineId, - now: SystemTime, - connect_time: u64, - send_time: u64, + connect_time: Duration, + send_time: Duration, is_xhr: bool, ) -> ChromeToDevtoolsControlMsg { + let started_date_time = SystemTime::now(); let request = DevtoolsHttpRequest { url, method, headers, body, pipeline_id, - started_date_time: now, - time_stamp: now.duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() as i64, + started_date_time, + time_stamp: started_date_time + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_secs() as i64, connect_time, send_time, is_xhr, @@ -614,7 +614,7 @@ async fn obtain_response( // TODO(#21261) connect_start: set if a persistent connection is *not* used and the last non-redirected // fetch passes the timing allow check - let connect_start = precise_time_ms(); + let connect_start = CrossProcessInstant::now(); context .timing .lock() @@ -638,7 +638,7 @@ async fn obtain_response( }; *request.headers_mut() = headers.clone(); - let connect_end = precise_time_ms(); + let connect_end = CrossProcessInstant::now(); context .timing .lock() @@ -649,7 +649,7 @@ async fn obtain_response( let pipeline_id = *pipeline_id; let closure_url = url.clone(); let method = method.clone(); - let send_start = precise_time_ms(); + let send_start = CrossProcessInstant::now(); let host = request.uri().host().unwrap_or("").to_owned(); let override_manager = context.state.override_manager.clone(); @@ -658,7 +658,7 @@ async fn obtain_response( client .request(request) .and_then(move |res| { - let send_end = precise_time_ms(); + let send_end = CrossProcessInstant::now(); // TODO(#21271) response_start: immediately after receiving first byte of response @@ -671,9 +671,8 @@ async fn obtain_response( headers, Some(devtools_bytes.lock().unwrap().clone()), pipeline_id, - SystemTime::now(), - connect_end - connect_start, - send_end - send_start, + (connect_end - connect_start).unsigned_abs(), + (send_end - send_start).unsigned_abs(), is_xhr, )) // TODO: ^This is not right, connect_start is taken before contructing the diff --git a/components/profile/Cargo.toml b/components/profile/Cargo.toml index eacd0b55d0e..ce8c801d108 100644 --- a/components/profile/Cargo.toml +++ b/components/profile/Cargo.toml @@ -11,11 +11,13 @@ name = "profile" path = "lib.rs" [dependencies] +base = { workspace = true } ipc-channel = { workspace = true } profile_traits = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } servo_config = { path = "../config" } +time_03 = { workspace = true } [target.'cfg(target_os = "macos")'.dependencies] task_info = { path = "../../support/rust-task_info" } diff --git a/components/profile/mem.rs b/components/profile/mem.rs index 5d791d5e6e2..0471aadbe0f 100644 --- a/components/profile/mem.rs +++ b/components/profile/mem.rs @@ -8,7 +8,7 @@ use std::borrow::ToOwned; use std::cmp::Ordering; use std::collections::HashMap; use std::thread; -use std::time::Instant; +use std::time::{Duration, Instant}; use ipc_channel::ipc::{self, IpcReceiver}; use ipc_channel::router::ROUTER; @@ -17,8 +17,6 @@ use profile_traits::mem::{ }; use profile_traits::path; -use crate::time::duration_from_seconds; - pub struct Profiler { /// The port through which messages are received. pub port: IpcReceiver<ProfilerMsg>, @@ -43,7 +41,7 @@ impl Profiler { thread::Builder::new() .name("MemoryProfTimer".to_owned()) .spawn(move || loop { - thread::sleep(duration_from_seconds(period)); + thread::sleep(Duration::from_secs_f64(period)); if chan.send(ProfilerMsg::Print).is_err() { break; } diff --git a/components/profile/time.rs b/components/profile/time.rs index 836cded25d4..9aaa4ef0023 100644 --- a/components/profile/time.rs +++ b/components/profile/time.rs @@ -10,7 +10,6 @@ use std::fs::File; use std::io::{self, Write}; use std::path::Path; use std::thread; -use std::time::Duration; use ipc_channel::ipc::{self, IpcReceiver}; use profile_traits::time::{ @@ -18,6 +17,7 @@ use profile_traits::time::{ TimerMetadataFrameType, TimerMetadataReflowType, }; use servo_config::opts::OutputOptions; +use time_03::Duration; use crate::trace_dump::TraceDump; @@ -156,7 +156,7 @@ impl Formattable for ProfilerCategory { } } -type ProfilerBuckets = BTreeMap<(ProfilerCategory, Option<TimerMetadata>), Vec<f64>>; +type ProfilerBuckets = BTreeMap<(ProfilerCategory, Option<TimerMetadata>), Vec<Duration>>; // back end of the profiler that handles data aggregation and performance metrics pub struct Profiler { @@ -192,7 +192,7 @@ impl Profiler { thread::Builder::new() .name("TimeProfTimer".to_owned()) .spawn(move || loop { - thread::sleep(duration_from_seconds(period)); + thread::sleep(std::time::Duration::from_secs_f64(period)); if chan.send(ProfilerMsg::Print).is_err() { break; } @@ -258,18 +258,17 @@ impl Profiler { } } - fn find_or_insert(&mut self, k: (ProfilerCategory, Option<TimerMetadata>), t: f64) { - self.buckets.entry(k).or_default().push(t); + fn find_or_insert(&mut self, k: (ProfilerCategory, Option<TimerMetadata>), duration: Duration) { + self.buckets.entry(k).or_default().push(duration); } fn handle_msg(&mut self, msg: ProfilerMsg) -> bool { match msg.clone() { - ProfilerMsg::Time(k, t) => { + ProfilerMsg::Time(category_and_metadata, (start_time, end_time)) => { if let Some(ref mut trace) = self.trace { - trace.write_one(&k, t); + trace.write_one(&category_and_metadata, start_time, end_time); } - let ms = (t.1 - t.0) as f64 / 1000000f64; - self.find_or_insert(k, ms); + self.find_or_insert(category_and_metadata, end_time - start_time); }, ProfilerMsg::Print => { if let Some(ProfilerMsg::Time(..)) = self.last_msg { @@ -300,16 +299,16 @@ impl Profiler { } /// Get tuple (mean, median, min, max) for profiler statistics. - pub fn get_statistics(data: &[f64]) -> (f64, f64, f64, f64) { - data.iter().fold(-f64::INFINITY, |a, &b| { - debug_assert!(a <= b, "Data must be sorted"); - b - }); + pub fn get_statistics(data: &[Duration]) -> (Duration, Duration, Duration, Duration) { + debug_assert!( + data.windows(2).all(|window| window[0] <= window[1]), + "Data must be sorted" + ); let data_len = data.len(); debug_assert!(data_len > 0); let (mean, median, min, max) = ( - data.iter().sum::<f64>() / (data_len as f64), + data.iter().sum::<Duration>() / data_len as u32, data[data_len / 2], data[0], data[data_len - 1], @@ -341,10 +340,10 @@ impl Profiler { "{}\t{}\t{:15.4}\t{:15.4}\t{:15.4}\t{:15.4}\t{:15}", category.format(&self.output), meta.format(&self.output), - mean, - median, - min, - max, + mean.as_seconds_f64() * 1000., + median.as_seconds_f64() * 1000., + min.as_seconds_f64() * 1000., + max.as_seconds_f64() * 1000., data_len ) .unwrap(); @@ -384,10 +383,10 @@ impl Profiler { "{:-35}{} {:15.4} {:15.4} {:15.4} {:15.4} {:15}", category.format(&self.output), meta.format(&self.output), - mean, - median, - min, - max, + mean.as_seconds_f64() * 1000., + median.as_seconds_f64() * 1000., + min.as_seconds_f64() * 1000., + max.as_seconds_f64() * 1000., data_len ) .unwrap(); @@ -405,17 +404,3 @@ impl Profiler { }; } } - -pub fn duration_from_seconds(secs: f64) -> Duration { - pub const NANOS_PER_SEC: u32 = 1_000_000_000; - - // Get number of seconds and check that it fits in a u64. - let whole_secs = secs.trunc(); - assert!(whole_secs >= 0.0 && whole_secs <= u64::MAX as f64); - - // Get number of nanoseconds. This should always fit in a u32, but check anyway. - let nanos = (secs.fract() * (NANOS_PER_SEC as f64)).trunc(); - assert!(nanos >= 0.0 && nanos <= u32::MAX as f64); - - Duration::new(whole_secs as u64, nanos as u32) -} diff --git a/components/profile/trace_dump.rs b/components/profile/trace_dump.rs index 66614a28b98..07c5569eb43 100644 --- a/components/profile/trace_dump.rs +++ b/components/profile/trace_dump.rs @@ -7,6 +7,7 @@ use std::io::{self, Write}; use std::{fs, path}; +use base::cross_process_instant::CrossProcessInstant; use profile_traits::time::{ProfilerCategory, TimerMetadata}; use serde::Serialize; @@ -44,13 +45,14 @@ impl TraceDump { pub fn write_one( &mut self, category: &(ProfilerCategory, Option<TimerMetadata>), - time: (u64, u64), + start_time: CrossProcessInstant, + end_time: CrossProcessInstant, ) { let entry = TraceEntry { category: category.0, metadata: category.1.clone(), - start_time: time.0, - end_time: time.1, + start_time: (start_time - CrossProcessInstant::epoch()).whole_nanoseconds() as u64, + end_time: (end_time - CrossProcessInstant::epoch()).whole_nanoseconds() as u64, }; serde_json::to_writer(&mut self.file, &entry).unwrap(); writeln!(&mut self.file, ",").unwrap(); diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index d30147adea5..e6384f69606 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -105,6 +105,7 @@ swapper = "0.1" tempfile = "3" tendril = { version = "0.4.1", features = ["encoding_rs"] } time = { workspace = true } +time_03 = { workspace = true } unicode-bidi = { workspace = true } unicode-segmentation = { workspace = true } url = { workspace = true } diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 0b616866245..77cc9e534f7 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -14,6 +14,7 @@ use std::slice::from_ref; use std::sync::LazyLock; use std::time::{Duration, Instant}; +use base::cross_process_instant::CrossProcessInstant; use base::id::BrowsingContextId; use canvas_traits::webgl::{self, WebGLContextId, WebGLMsg}; use content_security_policy::{self as csp, CspList}; @@ -342,16 +343,24 @@ pub struct Document { active_touch_points: DomRefCell<Vec<Dom<Touch>>>, /// Navigation Timing properties: /// <https://w3c.github.io/navigation-timing/#sec-PerformanceNavigationTiming> - dom_loading: Cell<u64>, - dom_interactive: Cell<u64>, - dom_content_loaded_event_start: Cell<u64>, - dom_content_loaded_event_end: Cell<u64>, - dom_complete: Cell<u64>, - top_level_dom_complete: Cell<u64>, - load_event_start: Cell<u64>, - load_event_end: Cell<u64>, - unload_event_start: Cell<u64>, - unload_event_end: Cell<u64>, + #[no_trace] + dom_interactive: Cell<Option<CrossProcessInstant>>, + #[no_trace] + dom_content_loaded_event_start: Cell<Option<CrossProcessInstant>>, + #[no_trace] + dom_content_loaded_event_end: Cell<Option<CrossProcessInstant>>, + #[no_trace] + dom_complete: Cell<Option<CrossProcessInstant>>, + #[no_trace] + top_level_dom_complete: Cell<Option<CrossProcessInstant>>, + #[no_trace] + load_event_start: Cell<Option<CrossProcessInstant>>, + #[no_trace] + load_event_end: Cell<Option<CrossProcessInstant>>, + #[no_trace] + unload_event_start: Cell<Option<CrossProcessInstant>>, + #[no_trace] + unload_event_end: Cell<Option<CrossProcessInstant>>, /// <https://html.spec.whatwg.org/multipage/#concept-document-https-state> #[no_trace] https_state: Cell<HttpsState>, @@ -1068,15 +1077,14 @@ impl Document { self.send_to_embedder(EmbedderMsg::LoadStart); self.send_to_embedder(EmbedderMsg::Status(None)); } - update_with_current_time_ms(&self.dom_loading); }, DocumentReadyState::Complete => { if self.window().is_top_level() { self.send_to_embedder(EmbedderMsg::LoadComplete); } - update_with_current_time_ms(&self.dom_complete); + update_with_current_instant(&self.dom_complete); }, - DocumentReadyState::Interactive => update_with_current_time_ms(&self.dom_interactive), + DocumentReadyState::Interactive => update_with_current_instant(&self.dom_interactive), }; self.ready_state.set(state); @@ -2153,8 +2161,8 @@ impl Document { let loader = self.loader.borrow(); // Servo measures when the top-level content (not iframes) is loaded. - if (self.top_level_dom_complete.get() == 0) && loader.is_only_blocked_by_iframes() { - update_with_current_time_ms(&self.top_level_dom_complete); + if self.top_level_dom_complete.get().is_none() && loader.is_only_blocked_by_iframes() { + update_with_current_instant(&self.top_level_dom_complete); } if loader.is_blocked() || loader.events_inhibited() { @@ -2347,7 +2355,7 @@ impl Document { event.set_trusted(true); // http://w3c.github.io/navigation-timing/#widl-PerformanceNavigationTiming-loadEventStart - update_with_current_time_ms(&document.load_event_start); + update_with_current_instant(&document.load_event_start); debug!("About to dispatch load for {:?}", document.url()); // FIXME(nox): Why are errors silenced here? @@ -2356,7 +2364,7 @@ impl Document { ); // http://w3c.github.io/navigation-timing/#widl-PerformanceNavigationTiming-loadEventEnd - update_with_current_time_ms(&document.load_event_end); + update_with_current_instant(&document.load_event_end); if let Some(fragment) = document.url().fragment() { document.check_and_scroll_fragment(fragment); @@ -2592,7 +2600,7 @@ impl Document { "Complete before DOMContentLoaded?" ); - update_with_current_time_ms(&self.dom_content_loaded_event_start); + update_with_current_instant(&self.dom_content_loaded_event_start); // Step 4.1. let window = self.window(); @@ -2604,7 +2612,7 @@ impl Document { task!(fire_dom_content_loaded_event: move || { let document = document.root(); document.upcast::<EventTarget>().fire_bubbling_event(atom!("DOMContentLoaded")); - update_with_current_time_ms(&document.dom_content_loaded_event_end); + update_with_current_instant(&document.dom_content_loaded_event_end); }), window.upcast(), ) @@ -2690,15 +2698,11 @@ impl Document { .find(|node| node.browsing_context_id() == Some(browsing_context_id)) } - pub fn get_dom_loading(&self) -> u64 { - self.dom_loading.get() - } - - pub fn get_dom_interactive(&self) -> u64 { + pub fn get_dom_interactive(&self) -> Option<CrossProcessInstant> { self.dom_interactive.get() } - pub fn set_navigation_start(&self, navigation_start: u64) { + pub fn set_navigation_start(&self, navigation_start: CrossProcessInstant) { self.interactive_time .borrow_mut() .set_navigation_start(navigation_start); @@ -2712,35 +2716,35 @@ impl Document { self.get_interactive_metrics().get_tti().is_some() } - pub fn get_dom_content_loaded_event_start(&self) -> u64 { + pub fn get_dom_content_loaded_event_start(&self) -> Option<CrossProcessInstant> { self.dom_content_loaded_event_start.get() } - pub fn get_dom_content_loaded_event_end(&self) -> u64 { + pub fn get_dom_content_loaded_event_end(&self) -> Option<CrossProcessInstant> { self.dom_content_loaded_event_end.get() } - pub fn get_dom_complete(&self) -> u64 { + pub fn get_dom_complete(&self) -> Option<CrossProcessInstant> { self.dom_complete.get() } - pub fn get_top_level_dom_complete(&self) -> u64 { + pub fn get_top_level_dom_complete(&self) -> Option<CrossProcessInstant> { self.top_level_dom_complete.get() } - pub fn get_load_event_start(&self) -> u64 { + pub fn get_load_event_start(&self) -> Option<CrossProcessInstant> { self.load_event_start.get() } - pub fn get_load_event_end(&self) -> u64 { + pub fn get_load_event_end(&self) -> Option<CrossProcessInstant> { self.load_event_end.get() } - pub fn get_unload_event_start(&self) -> u64 { + pub fn get_unload_event_start(&self) -> Option<CrossProcessInstant> { self.unload_event_start.get() } - pub fn get_unload_event_end(&self) -> u64 { + pub fn get_unload_event_end(&self) -> Option<CrossProcessInstant> { self.unload_event_end.get() } @@ -3236,7 +3240,6 @@ impl Document { pending_restyles: DomRefCell::new(HashMap::new()), needs_paint: Cell::new(false), active_touch_points: DomRefCell::new(Vec::new()), - dom_loading: Cell::new(Default::default()), dom_interactive: Cell::new(Default::default()), dom_content_loaded_event_start: Cell::new(Default::default()), dom_content_loaded_event_end: Cell::new(Default::default()), @@ -4101,11 +4104,8 @@ impl Document { self.visibility_state.set(visibility_state); // Step 3 Queue a new VisibilityStateEntry whose visibility state is visibilityState and whose timestamp is // the current high resolution time given document's relevant global object. - let entry = VisibilityStateEntry::new( - &self.global(), - visibility_state, - *self.global().performance().Now(), - ); + let entry = + VisibilityStateEntry::new(&self.global(), visibility_state, CrossProcessInstant::now()); self.window .Performance() .queue_entry(entry.upcast::<PerformanceEntry>()); @@ -5441,11 +5441,9 @@ impl DocumentMethods for Document { } } -fn update_with_current_time_ms(marker: &Cell<u64>) { - if marker.get() == 0 { - let time = time::get_time(); - let current_time_ms = time.sec * 1000 + time.nsec as i64 / 1000000; - marker.set(current_time_ms as u64); +fn update_with_current_instant(marker: &Cell<Option<CrossProcessInstant>>) { + if marker.get().is_none() { + marker.set(Some(CrossProcessInstant::now())) } } diff --git a/components/script/dom/event.rs b/components/script/dom/event.rs index e156a07ff68..83f34d3316a 100644 --- a/components/script/dom/event.rs +++ b/components/script/dom/event.rs @@ -5,10 +5,10 @@ use std::cell::Cell; use std::default::Default; +use base::cross_process_instant::CrossProcessInstant; use devtools_traits::{TimelineMarker, TimelineMarkerType}; use dom_struct::dom_struct; use js::rust::HandleObject; -use metrics::ToMs; use servo_atoms::Atom; use crate::dom::bindings::callback::ExceptionHandling; @@ -16,7 +16,6 @@ use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::EventBinding; use crate::dom::bindings::codegen::Bindings::EventBinding::{EventConstants, EventMethods}; use crate::dom::bindings::codegen::Bindings::PerformanceBinding::DOMHighResTimeStamp; -use crate::dom::bindings::codegen::Bindings::PerformanceBinding::Performance_Binding::PerformanceMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use crate::dom::bindings::error::Fallible; use crate::dom::bindings::inheritance::Castable; @@ -31,7 +30,6 @@ use crate::dom::globalscope::GlobalScope; use crate::dom::htmlinputelement::InputActivationState; use crate::dom::mouseevent::MouseEvent; use crate::dom::node::{Node, ShadowIncluding}; -use crate::dom::performance::reduce_timing_resolution; use crate::dom::virtualmethods::vtable_for; use crate::dom::window::Window; use crate::script_runtime::CanGc; @@ -53,7 +51,8 @@ pub struct Event { trusted: Cell<bool>, dispatching: Cell<bool>, initialized: Cell<bool>, - precise_time_ns: u64, + #[no_trace] + time_stamp: CrossProcessInstant, } impl Event { @@ -72,7 +71,7 @@ impl Event { trusted: Cell::new(false), dispatching: Cell::new(false), initialized: Cell::new(false), - precise_time_ns: time::precise_time_ns(), + time_stamp: CrossProcessInstant::now(), } } @@ -498,10 +497,9 @@ impl EventMethods for Event { /// <https://dom.spec.whatwg.org/#dom-event-timestamp> fn TimeStamp(&self) -> DOMHighResTimeStamp { - reduce_timing_resolution( - (self.precise_time_ns - (*self.global().performance().TimeOrigin()).round() as u64) - .to_ms(), - ) + self.global() + .performance() + .to_dom_high_res_time_stamp(self.time_stamp) } /// <https://dom.spec.whatwg.org/#dom-event-initevent> diff --git a/components/script/dom/macros.rs b/components/script/dom/macros.rs index 30bb6bcfe4f..6bece48b720 100644 --- a/components/script/dom/macros.rs +++ b/components/script/dom/macros.rs @@ -579,6 +579,9 @@ macro_rules! rooted_vec { /// DOM struct implementation for simple interfaces inheriting from PerformanceEntry. macro_rules! impl_performance_entry_struct( ($binding:ident, $struct:ident, $type:expr) => ( + use base::cross_process_instant::CrossProcessInstant; + use time_03::Duration; + use crate::dom::bindings::reflector::reflect_dom_object; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; @@ -592,12 +595,12 @@ macro_rules! impl_performance_entry_struct( } impl $struct { - fn new_inherited(name: DOMString, start_time: f64, duration: f64) + fn new_inherited(name: DOMString, start_time: CrossProcessInstant, duration: Duration) -> $struct { $struct { entry: PerformanceEntry::new_inherited(name, DOMString::from($type), - start_time, + Some(start_time), duration) } } @@ -605,8 +608,8 @@ macro_rules! impl_performance_entry_struct( #[allow(crown::unrooted_must_root)] pub fn new(global: &GlobalScope, name: DOMString, - start_time: f64, - duration: f64) -> DomRoot<$struct> { + start_time: CrossProcessInstant, + duration: Duration) -> DomRoot<$struct> { let entry = $struct::new_inherited(name, start_time, duration); reflect_dom_object(Box::new(entry), global) } diff --git a/components/script/dom/performance.rs b/components/script/dom/performance.rs index 277662ccb42..983e40ca23a 100644 --- a/components/script/dom/performance.rs +++ b/components/script/dom/performance.rs @@ -6,8 +6,9 @@ use std::cell::Cell; use std::cmp::Ordering; use std::collections::VecDeque; +use base::cross_process_instant::CrossProcessInstant; use dom_struct::dom_struct; -use metrics::ToMs; +use time_03::Duration; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::PerformanceBinding::{ @@ -105,16 +106,12 @@ impl PerformanceEntryList { &self, name: DOMString, entry_type: DOMString, - ) -> f64 { - match self - .entries + ) -> Option<CrossProcessInstant> { + self.entries .iter() .rev() .find(|e| *e.entry_type() == *entry_type && *e.name() == *name) - { - Some(entry) => entry.start_time(), - None => 0., - } + .and_then(|entry| entry.start_time()) } } @@ -139,7 +136,10 @@ pub struct Performance { buffer: DomRefCell<PerformanceEntryList>, observers: DomRefCell<Vec<PerformanceObserver>>, pending_notification_observers_task: Cell<bool>, - navigation_start_precise: u64, + #[no_trace] + /// The `timeOrigin` as described in + /// <https://html.spec.whatwg.org/multipage/#concept-settings-object-time-origin>. + time_origin: CrossProcessInstant, /// <https://w3c.github.io/performance-timeline/#dfn-maxbuffersize> /// The max-size of the buffer, set to 0 once the pipeline exits. /// TODO: have one max-size per entry type. @@ -150,13 +150,13 @@ pub struct Performance { } impl Performance { - fn new_inherited(navigation_start_precise: u64) -> Performance { + fn new_inherited(time_origin: CrossProcessInstant) -> Performance { Performance { eventtarget: EventTarget::new_inherited(), buffer: DomRefCell::new(PerformanceEntryList::new(Vec::new())), observers: DomRefCell::new(Vec::new()), pending_notification_observers_task: Cell::new(false), - navigation_start_precise, + time_origin, resource_timing_buffer_size_limit: Cell::new(250), resource_timing_buffer_current_size: Cell::new(0), resource_timing_buffer_pending_full_event: Cell::new(false), @@ -164,13 +164,30 @@ impl Performance { } } - pub fn new(global: &GlobalScope, navigation_start_precise: u64) -> DomRoot<Performance> { + pub fn new( + global: &GlobalScope, + navigation_start: CrossProcessInstant, + ) -> DomRoot<Performance> { reflect_dom_object( - Box::new(Performance::new_inherited(navigation_start_precise)), + Box::new(Performance::new_inherited(navigation_start)), global, ) } + pub(crate) fn to_dom_high_res_time_stamp( + &self, + instant: CrossProcessInstant, + ) -> DOMHighResTimeStamp { + (instant - self.time_origin).to_dom_high_res_time_stamp() + } + + pub(crate) fn maybe_to_dom_high_res_time_stamp( + &self, + instant: Option<CrossProcessInstant>, + ) -> DOMHighResTimeStamp { + self.to_dom_high_res_time_stamp(instant.unwrap_or(self.time_origin)) + } + /// Clear all buffered performance entries, and disable the buffer. /// Called as part of the window's "clear_js_runtime" workflow, /// performed when exiting a pipeline. @@ -329,10 +346,6 @@ impl Performance { } } - fn now(&self) -> f64 { - (time::precise_time_ns() - self.navigation_start_precise).to_ms() - } - fn can_add_resource_timing_entry(&self) -> bool { self.resource_timing_buffer_current_size.get() <= self.resource_timing_buffer_size_limit.get() @@ -423,12 +436,12 @@ impl PerformanceMethods for Performance { // https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/HighResolutionTime/Overview.html#dom-performance-now fn Now(&self) -> DOMHighResTimeStamp { - reduce_timing_resolution(self.now()) + self.to_dom_high_res_time_stamp(CrossProcessInstant::now()) } // https://www.w3.org/TR/hr-time-2/#dom-performance-timeorigin fn TimeOrigin(&self) -> DOMHighResTimeStamp { - reduce_timing_resolution(self.navigation_start_precise as f64) + (self.time_origin - CrossProcessInstant::epoch()).to_dom_high_res_time_stamp() } // https://www.w3.org/TR/performance-timeline-2/#dom-performance-getentries @@ -465,7 +478,12 @@ impl PerformanceMethods for Performance { } // Steps 2 to 6. - let entry = PerformanceMark::new(&global, mark_name, self.now(), 0.); + let entry = PerformanceMark::new( + &global, + mark_name, + CrossProcessInstant::now(), + Duration::ZERO, + ); // Steps 7 and 8. self.queue_entry(entry.upcast::<PerformanceEntry>()); @@ -488,22 +506,23 @@ impl PerformanceMethods for Performance { end_mark: Option<DOMString>, ) -> Fallible<()> { // Steps 1 and 2. - let end_time = match end_mark { - Some(name) => self - .buffer - .borrow() - .get_last_entry_start_time_with_name_and_type(DOMString::from("mark"), name), - None => self.now(), - }; + let end_time = end_mark + .map(|name| { + self.buffer + .borrow() + .get_last_entry_start_time_with_name_and_type(DOMString::from("mark"), name) + .unwrap_or(self.time_origin) + }) + .unwrap_or_else(CrossProcessInstant::now); // Step 3. - let start_time = match start_mark { - Some(name) => self - .buffer - .borrow() - .get_last_entry_start_time_with_name_and_type(DOMString::from("mark"), name), - None => 0., - }; + let start_time = start_mark + .and_then(|name| { + self.buffer + .borrow() + .get_last_entry_start_time_with_name_and_type(DOMString::from("mark"), name) + }) + .unwrap_or(self.time_origin); // Steps 4 to 8. let entry = PerformanceMeasure::new( @@ -548,13 +567,18 @@ impl PerformanceMethods for Performance { ); } -// https://www.w3.org/TR/hr-time-2/#clock-resolution -pub fn reduce_timing_resolution(exact: f64) -> DOMHighResTimeStamp { - // We need a granularity no finer than 5 microseconds. - // 5 microseconds isn't an exactly representable f64 so WPT tests - // might occasionally corner-case on rounding. - // web-platform-tests/wpt#21526 wants us to use an integer number of - // microseconds; the next divisor of milliseconds up from 5 microseconds - // is 10, which is 1/100th of a millisecond. - Finite::wrap((exact * 100.0).floor() / 100.0) +pub(crate) trait ToDOMHighResTimeStamp { + fn to_dom_high_res_time_stamp(&self) -> DOMHighResTimeStamp; +} + +impl ToDOMHighResTimeStamp for Duration { + fn to_dom_high_res_time_stamp(&self) -> DOMHighResTimeStamp { + // https://www.w3.org/TR/hr-time-2/#clock-resolution + // We need a granularity no finer than 5 microseconds. 5 microseconds isn't an + // exactly representable f64 so WPT tests might occasionally corner-case on + // rounding. web-platform-tests/wpt#21526 wants us to use an integer number of + // microseconds; the next divisor of milliseconds up from 5 microseconds is 10. + let microseconds_rounded = (self.whole_microseconds() as f64 / 10.).floor() * 10.; + Finite::wrap(microseconds_rounded / 1000.) + } } diff --git a/components/script/dom/performanceentry.rs b/components/script/dom/performanceentry.rs index 1624a7f15fa..46f13d71515 100644 --- a/components/script/dom/performanceentry.rs +++ b/components/script/dom/performanceentry.rs @@ -2,31 +2,38 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use base::cross_process_instant::CrossProcessInstant; use dom_struct::dom_struct; +use time_03::Duration; +use super::performance::ToDOMHighResTimeStamp; use crate::dom::bindings::codegen::Bindings::PerformanceBinding::DOMHighResTimeStamp; use crate::dom::bindings::codegen::Bindings::PerformanceEntryBinding::PerformanceEntryMethods; -use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; use crate::dom::globalscope::GlobalScope; -use crate::dom::performance::reduce_timing_resolution; #[dom_struct] pub struct PerformanceEntry { reflector_: Reflector, name: DOMString, entry_type: DOMString, - start_time: f64, - duration: f64, + #[no_trace] + start_time: Option<CrossProcessInstant>, + /// The duration of this [`PerformanceEntry`]. This is a [`time_03::Duration`], + /// because it can be negative and `std::time::Duration` cannot be. + #[no_trace] + #[ignore_malloc_size_of = "No MallocSizeOf support for `time` crate"] + duration: Duration, } impl PerformanceEntry { pub fn new_inherited( name: DOMString, entry_type: DOMString, - start_time: f64, - duration: f64, + start_time: Option<CrossProcessInstant>, + duration: Duration, ) -> PerformanceEntry { PerformanceEntry { reflector_: Reflector::new(), @@ -42,10 +49,10 @@ impl PerformanceEntry { global: &GlobalScope, name: DOMString, entry_type: DOMString, - start_time: f64, - duration: f64, + start_time: CrossProcessInstant, + duration: Duration, ) -> DomRoot<PerformanceEntry> { - let entry = PerformanceEntry::new_inherited(name, entry_type, start_time, duration); + let entry = PerformanceEntry::new_inherited(name, entry_type, Some(start_time), duration); reflect_dom_object(Box::new(entry), global) } @@ -57,11 +64,11 @@ impl PerformanceEntry { &self.name } - pub fn start_time(&self) -> f64 { + pub fn start_time(&self) -> Option<CrossProcessInstant> { self.start_time } - pub fn duration(&self) -> f64 { + pub fn duration(&self) -> Duration { self.duration } } @@ -79,11 +86,13 @@ impl PerformanceEntryMethods for PerformanceEntry { // https://w3c.github.io/performance-timeline/#dom-performanceentry-starttime fn StartTime(&self) -> DOMHighResTimeStamp { - reduce_timing_resolution(self.start_time) + self.global() + .performance() + .maybe_to_dom_high_res_time_stamp(self.start_time) } // https://w3c.github.io/performance-timeline/#dom-performanceentry-duration fn Duration(&self) -> DOMHighResTimeStamp { - reduce_timing_resolution(self.duration) + self.duration.to_dom_high_res_time_stamp() } } diff --git a/components/script/dom/performancenavigationtiming.rs b/components/script/dom/performancenavigationtiming.rs index f976e7320de..b4489fa8122 100644 --- a/components/script/dom/performancenavigationtiming.rs +++ b/components/script/dom/performancenavigationtiming.rs @@ -2,13 +2,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use base::cross_process_instant::CrossProcessInstant; use dom_struct::dom_struct; use crate::dom::bindings::codegen::Bindings::PerformanceBinding::DOMHighResTimeStamp; use crate::dom::bindings::codegen::Bindings::PerformanceNavigationTimingBinding::{ NavigationTimingType, PerformanceNavigationTimingMethods, }; -use crate::dom::bindings::num::Finite; +use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::reflect_dom_object; use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::document::Document; @@ -22,16 +23,13 @@ use crate::dom::performanceresourcetiming::{InitiatorType, PerformanceResourceTi pub struct PerformanceNavigationTiming { // https://w3c.github.io/navigation-timing/#PerformanceResourceTiming performanceresourcetiming: PerformanceResourceTiming, - navigation_start: u64, - navigation_start_precise: u64, document: Dom<Document>, nav_type: NavigationTimingType, } impl PerformanceNavigationTiming { fn new_inherited( - nav_start: u64, - nav_start_precise: u64, + navigation_start: CrossProcessInstant, document: &Document, ) -> PerformanceNavigationTiming { PerformanceNavigationTiming { @@ -39,10 +37,8 @@ impl PerformanceNavigationTiming { document.url(), InitiatorType::Navigation, None, - nav_start_precise as f64, + Some(navigation_start), ), - navigation_start: nav_start, - navigation_start_precise: nav_start_precise, document: Dom::from_ref(document), nav_type: NavigationTimingType::Navigate, } @@ -50,14 +46,12 @@ impl PerformanceNavigationTiming { pub fn new( global: &GlobalScope, - nav_start: u64, - nav_start_precise: u64, + fetch_start: CrossProcessInstant, document: &Document, ) -> DomRoot<PerformanceNavigationTiming> { reflect_dom_object( Box::new(PerformanceNavigationTiming::new_inherited( - nav_start, - nav_start_precise, + fetch_start, document, )), global, @@ -69,42 +63,50 @@ impl PerformanceNavigationTiming { impl PerformanceNavigationTimingMethods for PerformanceNavigationTiming { // https://w3c.github.io/navigation-timing/#dom-performancenavigationtiming-unloadeventstart fn UnloadEventStart(&self) -> DOMHighResTimeStamp { - Finite::wrap(self.document.get_unload_event_start() as f64) + self.upcast::<PerformanceResourceTiming>() + .to_dom_high_res_time_stamp(self.document.get_unload_event_start()) } // https://w3c.github.io/navigation-timing/#dom-performancenavigationtiming-unloadeventend fn UnloadEventEnd(&self) -> DOMHighResTimeStamp { - Finite::wrap(self.document.get_unload_event_end() as f64) + self.upcast::<PerformanceResourceTiming>() + .to_dom_high_res_time_stamp(self.document.get_unload_event_end()) } // https://w3c.github.io/navigation-timing/#dom-performancenavigationtiming-dominteractive fn DomInteractive(&self) -> DOMHighResTimeStamp { - Finite::wrap(self.document.get_dom_interactive() as f64) + self.upcast::<PerformanceResourceTiming>() + .to_dom_high_res_time_stamp(self.document.get_dom_interactive()) } // https://w3c.github.io/navigation-timing/#dom-performancenavigationtiming-domcontentloadedeventstart fn DomContentLoadedEventStart(&self) -> DOMHighResTimeStamp { - Finite::wrap(self.document.get_dom_content_loaded_event_start() as f64) + self.upcast::<PerformanceResourceTiming>() + .to_dom_high_res_time_stamp(self.document.get_dom_content_loaded_event_start()) } // https://w3c.github.io/navigation-timing/#dom-performancenavigationtiming-domcontentloadedeventstart fn DomContentLoadedEventEnd(&self) -> DOMHighResTimeStamp { - Finite::wrap(self.document.get_dom_content_loaded_event_end() as f64) + self.upcast::<PerformanceResourceTiming>() + .to_dom_high_res_time_stamp(self.document.get_dom_content_loaded_event_end()) } // https://w3c.github.io/navigation-timing/#dom-performancenavigationtiming-domcomplete fn DomComplete(&self) -> DOMHighResTimeStamp { - Finite::wrap(self.document.get_dom_complete() as f64) + self.upcast::<PerformanceResourceTiming>() + .to_dom_high_res_time_stamp(self.document.get_dom_complete()) } // https://w3c.github.io/navigation-timing/#dom-performancenavigationtiming-loadeventstart fn LoadEventStart(&self) -> DOMHighResTimeStamp { - Finite::wrap(self.document.get_load_event_start() as f64) + self.upcast::<PerformanceResourceTiming>() + .to_dom_high_res_time_stamp(self.document.get_load_event_start()) } // https://w3c.github.io/navigation-timing/#dom-performancenavigationtiming-loadeventend fn LoadEventEnd(&self) -> DOMHighResTimeStamp { - Finite::wrap(self.document.get_load_event_end() as f64) + self.upcast::<PerformanceResourceTiming>() + .to_dom_high_res_time_stamp(self.document.get_load_event_end()) } // https://w3c.github.io/navigation-timing/#dom-performancenavigationtiming-type @@ -120,6 +122,7 @@ impl PerformanceNavigationTimingMethods for PerformanceNavigationTiming { // check-tidy: no specs after this line // Servo-only timing for when top-level content (not iframes) is complete fn TopLevelDomComplete(&self) -> DOMHighResTimeStamp { - Finite::wrap(self.document.get_top_level_dom_complete() as f64) + self.upcast::<PerformanceResourceTiming>() + .to_dom_high_res_time_stamp(self.document.get_top_level_dom_complete()) } } diff --git a/components/script/dom/performancepainttiming.rs b/components/script/dom/performancepainttiming.rs index 69fa440d744..4226ab91960 100644 --- a/components/script/dom/performancepainttiming.rs +++ b/components/script/dom/performancepainttiming.rs @@ -2,9 +2,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use base::cross_process_instant::CrossProcessInstant; use dom_struct::dom_struct; -use metrics::ToMs; use script_traits::ProgressiveWebMetricType; +use time_03::Duration; use crate::dom::bindings::reflector::reflect_dom_object; use crate::dom::bindings::root::DomRoot; @@ -20,7 +21,7 @@ pub struct PerformancePaintTiming { impl PerformancePaintTiming { fn new_inherited( metric_type: ProgressiveWebMetricType, - start_time: u64, + start_time: CrossProcessInstant, ) -> PerformancePaintTiming { let name = match metric_type { ProgressiveWebMetricType::FirstPaint => DOMString::from("first-paint"), @@ -33,8 +34,8 @@ impl PerformancePaintTiming { entry: PerformanceEntry::new_inherited( name, DOMString::from("paint"), - start_time.to_ms(), - 0., + Some(start_time), + Duration::ZERO, ), } } @@ -43,7 +44,7 @@ impl PerformancePaintTiming { pub fn new( global: &GlobalScope, metric_type: ProgressiveWebMetricType, - start_time: u64, + start_time: CrossProcessInstant, ) -> DomRoot<PerformancePaintTiming> { let entry = PerformancePaintTiming::new_inherited(metric_type, start_time); reflect_dom_object(Box::new(entry), global) diff --git a/components/script/dom/performanceresourcetiming.rs b/components/script/dom/performanceresourcetiming.rs index 3afaef5f912..ac7791f9fed 100644 --- a/components/script/dom/performanceresourcetiming.rs +++ b/components/script/dom/performanceresourcetiming.rs @@ -2,17 +2,18 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use base::cross_process_instant::CrossProcessInstant; use dom_struct::dom_struct; use net_traits::ResourceFetchTiming; use servo_url::ServoUrl; +use time_03::Duration; use crate::dom::bindings::codegen::Bindings::PerformanceBinding::DOMHighResTimeStamp; use crate::dom::bindings::codegen::Bindings::PerformanceResourceTimingBinding::PerformanceResourceTimingMethods; -use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; use crate::dom::globalscope::GlobalScope; -use crate::dom::performance::reduce_timing_resolution; use crate::dom::performanceentry::PerformanceEntry; // TODO UA may choose to limit how many resources are included as PerformanceResourceTiming objects @@ -37,18 +38,30 @@ pub struct PerformanceResourceTiming { entry: PerformanceEntry, initiator_type: InitiatorType, next_hop: Option<DOMString>, - worker_start: f64, - redirect_start: f64, - redirect_end: f64, - fetch_start: f64, - domain_lookup_start: f64, - domain_lookup_end: f64, - connect_start: f64, - connect_end: f64, - secure_connection_start: f64, - request_start: f64, - response_start: f64, - response_end: f64, + #[no_trace] + worker_start: Option<CrossProcessInstant>, + #[no_trace] + redirect_start: Option<CrossProcessInstant>, + #[no_trace] + redirect_end: Option<CrossProcessInstant>, + #[no_trace] + fetch_start: Option<CrossProcessInstant>, + #[no_trace] + domain_lookup_start: Option<CrossProcessInstant>, + #[no_trace] + domain_lookup_end: Option<CrossProcessInstant>, + #[no_trace] + connect_start: Option<CrossProcessInstant>, + #[no_trace] + connect_end: Option<CrossProcessInstant>, + #[no_trace] + secure_connection_start: Option<CrossProcessInstant>, + #[no_trace] + request_start: Option<CrossProcessInstant>, + #[no_trace] + response_start: Option<CrossProcessInstant>, + #[no_trace] + response_end: Option<CrossProcessInstant>, transfer_size: u64, //size in octets encoded_body_size: u64, //size in octets decoded_body_size: u64, //size in octets @@ -66,7 +79,7 @@ impl PerformanceResourceTiming { url: ServoUrl, initiator_type: InitiatorType, next_hop: Option<DOMString>, - fetch_start: f64, + fetch_start: Option<CrossProcessInstant>, ) -> PerformanceResourceTiming { let entry_type = if initiator_type == InitiatorType::Navigation { DOMString::from("navigation") @@ -77,23 +90,23 @@ impl PerformanceResourceTiming { entry: PerformanceEntry::new_inherited( DOMString::from(url.into_string()), entry_type, - 0., - 0., + None, + Duration::ZERO, ), initiator_type, next_hop, - worker_start: 0., - redirect_start: 0., - redirect_end: 0., + worker_start: None, + redirect_start: None, + redirect_end: None, fetch_start, - domain_lookup_end: 0., - domain_lookup_start: 0., - connect_start: 0., - connect_end: 0., - secure_connection_start: 0., - request_start: 0., - response_start: 0., - response_end: 0., + domain_lookup_end: None, + domain_lookup_start: None, + connect_start: None, + connect_end: None, + secure_connection_start: None, + request_start: None, + response_start: None, + response_end: None, transfer_size: 0, encoded_body_size: 0, decoded_body_size: 0, @@ -108,28 +121,32 @@ impl PerformanceResourceTiming { next_hop: Option<DOMString>, resource_timing: &ResourceFetchTiming, ) -> PerformanceResourceTiming { + let duration = match (resource_timing.start_time, resource_timing.response_end) { + (Some(start_time), Some(end_time)) => end_time - start_time, + _ => Duration::ZERO, + }; PerformanceResourceTiming { entry: PerformanceEntry::new_inherited( DOMString::from(url.into_string()), DOMString::from("resource"), - resource_timing.start_time as f64, - resource_timing.response_end as f64 - resource_timing.start_time as f64, + resource_timing.start_time, + duration, ), initiator_type, next_hop, - worker_start: 0., - redirect_start: resource_timing.redirect_start as f64, - redirect_end: resource_timing.redirect_end as f64, - fetch_start: resource_timing.fetch_start as f64, - domain_lookup_start: resource_timing.domain_lookup_start as f64, + worker_start: None, + redirect_start: resource_timing.redirect_start, + redirect_end: resource_timing.redirect_end, + fetch_start: resource_timing.fetch_start, + domain_lookup_start: resource_timing.domain_lookup_start, //TODO (#21260) - domain_lookup_end: 0., - connect_start: resource_timing.connect_start as f64, - connect_end: resource_timing.connect_end as f64, - secure_connection_start: resource_timing.secure_connection_start as f64, - request_start: resource_timing.request_start as f64, - response_start: resource_timing.response_start as f64, - response_end: resource_timing.response_end as f64, + domain_lookup_end: None, + connect_start: resource_timing.connect_start, + connect_end: resource_timing.connect_end, + secure_connection_start: resource_timing.secure_connection_start, + request_start: resource_timing.request_start, + response_start: resource_timing.response_start, + response_end: resource_timing.response_end, transfer_size: 0, encoded_body_size: 0, decoded_body_size: 0, @@ -153,6 +170,18 @@ impl PerformanceResourceTiming { global, ) } + + /// Convert an optional [`CrossProcessInstant`] to a [`DOMHighResTimeStamp`]. If none + /// return a timestamp for [`Self::fetch_start`] instead, so that timestamps are + /// always after that time. + pub(crate) fn to_dom_high_res_time_stamp( + &self, + instant: Option<CrossProcessInstant>, + ) -> DOMHighResTimeStamp { + self.global() + .performance() + .maybe_to_dom_high_res_time_stamp(instant) + } } // https://w3c.github.io/resource-timing/ @@ -180,17 +209,17 @@ impl PerformanceResourceTimingMethods for PerformanceResourceTiming { // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-domainlookupstart fn DomainLookupStart(&self) -> DOMHighResTimeStamp { - reduce_timing_resolution(self.domain_lookup_start) + self.to_dom_high_res_time_stamp(self.domain_lookup_start) } // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-domainlookupend fn DomainLookupEnd(&self) -> DOMHighResTimeStamp { - reduce_timing_resolution(self.domain_lookup_end) + self.to_dom_high_res_time_stamp(self.domain_lookup_end) } // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-secureconnectionstart fn SecureConnectionStart(&self) -> DOMHighResTimeStamp { - reduce_timing_resolution(self.secure_connection_start) + self.to_dom_high_res_time_stamp(self.secure_connection_start) } // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-transfersize @@ -210,41 +239,41 @@ impl PerformanceResourceTimingMethods for PerformanceResourceTiming { // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-requeststart fn RequestStart(&self) -> DOMHighResTimeStamp { - reduce_timing_resolution(self.request_start) + self.to_dom_high_res_time_stamp(self.request_start) } // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-redirectstart fn RedirectStart(&self) -> DOMHighResTimeStamp { - reduce_timing_resolution(self.redirect_start) + self.to_dom_high_res_time_stamp(self.redirect_start) } // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-redirectend fn RedirectEnd(&self) -> DOMHighResTimeStamp { - reduce_timing_resolution(self.redirect_end) + self.to_dom_high_res_time_stamp(self.redirect_end) } // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-responsestart fn ResponseStart(&self) -> DOMHighResTimeStamp { - reduce_timing_resolution(self.response_start) + self.to_dom_high_res_time_stamp(self.response_start) } // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-fetchstart fn FetchStart(&self) -> DOMHighResTimeStamp { - reduce_timing_resolution(self.fetch_start) + self.to_dom_high_res_time_stamp(self.fetch_start) } // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-connectstart fn ConnectStart(&self) -> DOMHighResTimeStamp { - reduce_timing_resolution(self.connect_start) + self.to_dom_high_res_time_stamp(self.connect_start) } // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-connectend fn ConnectEnd(&self) -> DOMHighResTimeStamp { - reduce_timing_resolution(self.connect_end) + self.to_dom_high_res_time_stamp(self.connect_end) } // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-responseend fn ResponseEnd(&self) -> DOMHighResTimeStamp { - reduce_timing_resolution(self.response_end) + self.to_dom_high_res_time_stamp(self.response_end) } } diff --git a/components/script/dom/performancetiming.rs b/components/script/dom/performancetiming.rs deleted file mode 100644 index b3c2ee426d8..00000000000 --- a/components/script/dom/performancetiming.rs +++ /dev/null @@ -1,103 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -use crate::dom::bindings::codegen::Bindings::PerformanceTimingBinding; -use crate::dom::bindings::codegen::Bindings::PerformanceTimingBinding::PerformanceTimingMethods; -use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; -use crate::dom::bindings::root::{Dom, DomRoot}; -use crate::dom::document::Document; -use crate::dom::window::Window; -use dom_struct::dom_struct; - -#[dom_struct] -pub struct PerformanceTiming { - reflector_: Reflector, - navigation_start: u64, - navigation_start_precise: u64, - document: Dom<Document>, -} - -impl PerformanceTiming { - fn new_inherited( - nav_start: u64, - nav_start_precise: u64, - document: &Document, - ) -> PerformanceTiming { - PerformanceTiming { - reflector_: Reflector::new(), - navigation_start: nav_start, - navigation_start_precise: nav_start_precise, - document: Dom::from_ref(document), - } - } - - #[allow(crown::unrooted_must_root)] - pub fn new( - window: &Window, - navigation_start: u64, - navigation_start_precise: u64, - ) -> DomRoot<PerformanceTiming> { - let timing = PerformanceTiming::new_inherited( - navigation_start, - navigation_start_precise, - &window.Document(), - ); - reflect_dom_object(Box::new(timing), window) - } -} - -impl PerformanceTimingMethods for PerformanceTiming { - // https://w3c.github.io/navigation-timing/#widl-PerformanceTiming-navigationStart - fn NavigationStart(&self) -> u64 { - self.navigation_start - } - - // https://w3c.github.io/navigation-timing/#widl-PerformanceTiming-domLoading - fn DomLoading(&self) -> u64 { - self.document.get_dom_loading() - } - - // https://w3c.github.io/navigation-timing/#widl-PerformanceTiming-domInteractive - fn DomInteractive(&self) -> u64 { - self.document.get_dom_interactive() - } - - // https://w3c.github.io/navigation-timing/#widl-PerformanceTiming-domContentLoadedEventStart - fn DomContentLoadedEventStart(&self) -> u64 { - self.document.get_dom_content_loaded_event_start() - } - - // https://w3c.github.io/navigation-timing/#widl-PerformanceTiming-domContentLoadedEventEnd - fn DomContentLoadedEventEnd(&self) -> u64 { - self.document.get_dom_content_loaded_event_end() - } - - // https://w3c.github.io/navigation-timing/#widl-PerformanceTiming-domComplete - fn DomComplete(&self) -> u64 { - self.document.get_dom_complete() - } - - // https://w3c.github.io/navigation-timing/#widl-PerformanceTiming-loadEventStart - fn LoadEventStart(&self) -> u64 { - self.document.get_load_event_start() - } - - // https://w3c.github.io/navigation-timing/#widl-PerformanceTiming-loadEventEnd - fn LoadEventEnd(&self) -> u64 { - self.document.get_load_event_end() - } - - // check-tidy: no specs after this line - // Servo-only timing for when top-level content (not iframes) is complete - fn TopLevelDomComplete(&self) -> u64 { - self.document.get_top_level_dom_complete() - } -} - -impl PerformanceTiming { - pub fn navigation_start_precise(&self) -> u64 { - self.navigation_start_precise - } -} diff --git a/components/script/dom/servoparser/mod.rs b/components/script/dom/servoparser/mod.rs index 9a95210ca6b..f6c7af85838 100644 --- a/components/script/dom/servoparser/mod.rs +++ b/components/script/dom/servoparser/mod.rs @@ -5,6 +5,7 @@ use std::borrow::Cow; use std::cell::Cell; +use base::cross_process_instant::CrossProcessInstant; use base::id::PipelineId; use base64::engine::general_purpose; use base64::Engine as _; @@ -937,11 +938,15 @@ impl FetchResponseListener for ParserContext { parser.parse_sync(); } - //TODO only update if this is the current document resource + // TODO: Only update if this is the current document resource. + // TODO(mrobinson): Pass a proper fetch_start parameter here instead of `CrossProcessInstant::now()`. if let Some(pushed_index) = self.pushed_entry_index { let document = &parser.document; - let performance_entry = - PerformanceNavigationTiming::new(&document.global(), 0, 0, document); + let performance_entry = PerformanceNavigationTiming::new( + &document.global(), + CrossProcessInstant::now(), + document, + ); document .global() .performance() @@ -969,9 +974,12 @@ impl FetchResponseListener for ParserContext { let document = &parser.document; - //TODO nav_start and nav_start_precise - let performance_entry = - PerformanceNavigationTiming::new(&document.global(), 0, 0, document); + // TODO: Pass a proper fetch start time here. + let performance_entry = PerformanceNavigationTiming::new( + &document.global(), + CrossProcessInstant::now(), + document, + ); self.pushed_entry_index = document .global() .performance() diff --git a/components/script/dom/visibilitystateentry.rs b/components/script/dom/visibilitystateentry.rs index 9dcf74a58e6..c08eac70b56 100644 --- a/components/script/dom/visibilitystateentry.rs +++ b/components/script/dom/visibilitystateentry.rs @@ -4,7 +4,9 @@ use std::ops::Deref; +use base::cross_process_instant::CrossProcessInstant; use dom_struct::dom_struct; +use time_03::Duration; use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentVisibilityState; use crate::dom::bindings::codegen::Bindings::PerformanceEntryBinding::PerformanceEntryMethods; @@ -23,7 +25,10 @@ pub struct VisibilityStateEntry { impl VisibilityStateEntry { #[allow(crown::unrooted_must_root)] - fn new_inherited(state: DocumentVisibilityState, timestamp: f64) -> VisibilityStateEntry { + fn new_inherited( + state: DocumentVisibilityState, + timestamp: CrossProcessInstant, + ) -> VisibilityStateEntry { let name = match state { DocumentVisibilityState::Visible => DOMString::from("visible"), DocumentVisibilityState::Hidden => DOMString::from("hidden"), @@ -32,8 +37,8 @@ impl VisibilityStateEntry { entry: PerformanceEntry::new_inherited( name, DOMString::from("visibility-state"), - timestamp, - 0., + Some(timestamp), + Duration::ZERO, ), } } @@ -41,7 +46,7 @@ impl VisibilityStateEntry { pub fn new( global: &GlobalScope, state: DocumentVisibilityState, - timestamp: f64, + timestamp: CrossProcessInstant, ) -> DomRoot<VisibilityStateEntry> { reflect_dom_object( Box::new(VisibilityStateEntry::new_inherited(state, timestamp)), diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index a815647e1d1..0540cabeab5 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -17,6 +17,7 @@ use std::{cmp, env}; use app_units::Au; use backtrace::Backtrace; +use base::cross_process_instant::CrossProcessInstant; use base::id::{BrowsingContextId, PipelineId}; use base64::Engine; use bluetooth_traits::BluetoothRequest; @@ -201,8 +202,8 @@ pub struct Window { history: MutNullableDom<History>, custom_element_registry: MutNullableDom<CustomElementRegistry>, performance: MutNullableDom<Performance>, - navigation_start: Cell<u64>, - navigation_start_precise: Cell<u64>, + #[no_trace] + navigation_start: Cell<CrossProcessInstant>, screen: MutNullableDom<Screen>, session_storage: MutNullableDom<Storage>, local_storage: MutNullableDom<Storage>, @@ -985,7 +986,7 @@ impl WindowMethods for Window { fn Performance(&self) -> DomRoot<Performance> { self.performance.or_init(|| { let global_scope = self.upcast::<GlobalScope>(); - Performance::new(global_scope, self.navigation_start_precise.get()) + Performance::new(global_scope, self.navigation_start.get()) }) } @@ -1625,10 +1626,6 @@ impl Window { self.paint_worklet.or_init(|| self.new_paint_worklet()) } - pub fn get_navigation_start(&self) -> u64 { - self.navigation_start_precise.get() - } - pub fn has_document(&self) -> bool { self.document.get().is_some() } @@ -2494,10 +2491,7 @@ impl Window { } pub fn set_navigation_start(&self) { - let current_time = time::get_time(); - let now = (current_time.sec * 1000 + current_time.nsec as i64 / 1000000) as u64; - self.navigation_start.set(now); - self.navigation_start_precise.set(time::precise_time_ns()); + self.navigation_start.set(CrossProcessInstant::now()); } pub fn send_to_embedder(&self, msg: EmbedderMsg) { @@ -2547,8 +2541,7 @@ impl Window { window_size: WindowSizeData, origin: MutableOrigin, creator_url: ServoUrl, - navigation_start: u64, - navigation_start_precise: u64, + navigation_start: CrossProcessInstant, webgl_chan: Option<WebGLChan>, webxr_registry: webxr_api::Registry, microtask_queue: Rc<MicrotaskQueue>, @@ -2606,7 +2599,6 @@ impl Window { document: Default::default(), performance: Default::default(), navigation_start: Cell::new(navigation_start), - navigation_start_precise: Cell::new(navigation_start_precise), screen: Default::default(), session_storage: Default::default(), local_storage: Default::default(), diff --git a/components/script/dom/workerglobalscope.rs b/components/script/dom/workerglobalscope.rs index fb1baed40f7..f45ac219284 100644 --- a/components/script/dom/workerglobalscope.rs +++ b/components/script/dom/workerglobalscope.rs @@ -8,6 +8,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; +use base::cross_process_instant::CrossProcessInstant; use base::id::{PipelineId, PipelineNamespace}; use crossbeam_channel::Receiver; use devtools_traits::{DevtoolScriptControlMsg, WorkerId}; @@ -22,7 +23,6 @@ use net_traits::request::{ use net_traits::IpcSend; use script_traits::WorkerGlobalScopeInit; use servo_url::{MutableOrigin, ServoUrl}; -use time::precise_time_ns; use uuid::Uuid; use super::bindings::codegen::Bindings::MessagePortBinding::StructuredSerializeOptions; @@ -125,7 +125,8 @@ pub struct WorkerGlobalScope { /// `IpcSender` doesn't exist from_devtools_receiver: Receiver<DevtoolScriptControlMsg>, - navigation_start_precise: u64, + #[no_trace] + navigation_start: CrossProcessInstant, performance: MutNullableDom<Performance>, } @@ -170,7 +171,7 @@ impl WorkerGlobalScope { navigator: Default::default(), from_devtools_sender: init.from_devtools_sender, from_devtools_receiver, - navigation_start_precise: precise_time_ns(), + navigation_start: CrossProcessInstant::now(), performance: Default::default(), } } @@ -415,7 +416,7 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope { fn Performance(&self) -> DomRoot<Performance> { self.performance.or_init(|| { let global_scope = self.upcast::<GlobalScope>(); - Performance::new(global_scope, self.navigation_start_precise) + Performance::new(global_scope, self.navigation_start) }) } diff --git a/components/script/dom/xrsession.rs b/components/script/dom/xrsession.rs index 928dfa44763..f4d3836f60b 100644 --- a/components/script/dom/xrsession.rs +++ b/components/script/dom/xrsession.rs @@ -8,6 +8,7 @@ use std::f64::consts::{FRAC_PI_2, PI}; use std::rc::Rc; use std::{mem, ptr}; +use base::cross_process_instant::CrossProcessInstant; use dom_struct::dom_struct; use euclid::{RigidTransform3D, Transform3D, Vector3D}; use ipc_channel::ipc::IpcReceiver; @@ -15,7 +16,6 @@ use ipc_channel::router::ROUTER; use js::jsapi::JSObject; use js::jsval::JSVal; use js::typedarray::Float32Array; -use metrics::ToMs; use profile_traits::ipc; use servo_atoms::Atom; use webxr_api::{ @@ -52,7 +52,6 @@ use crate::dom::bindings::utils::to_frozen_array; use crate::dom::event::Event; use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; -use crate::dom::performance::reduce_timing_resolution; use crate::dom::promise::Promise; use crate::dom::xrboundedreferencespace::XRBoundedReferenceSpace; use crate::dom::xrframe::XRFrame; @@ -203,7 +202,7 @@ impl XRSession { frame_receiver.to_opaque(), Box::new(move |message| { let frame: Frame = message.to().unwrap(); - let time = time::precise_time_ns(); + let time = CrossProcessInstant::now(); let this = this.clone(); let _ = task_source.queue_with_canceller( task!(xr_raf_callback: move || { @@ -377,7 +376,7 @@ impl XRSession { } /// <https://immersive-web.github.io/webxr/#xr-animation-frame> - fn raf_callback(&self, mut frame: Frame, time: u64) { + fn raf_callback(&self, mut frame: Frame, time: CrossProcessInstant) { debug!("WebXR RAF callback {:?}", frame); // Step 1-2 happen in the xebxr device thread @@ -427,10 +426,10 @@ impl XRSession { assert!(current.is_empty()); mem::swap(&mut *self.raf_callback_list.borrow_mut(), &mut current); } - let start = self.global().as_window().get_navigation_start(); - let time = reduce_timing_resolution((time - start).to_ms()); + let time = self.global().performance().to_dom_high_res_time_stamp(time); let frame = XRFrame::new(&self.global(), self, frame); + // Step 8-9 frame.set_active(true); frame.set_animation_frame(true); diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 276c25ba4e7..a7c91cc9a49 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -27,12 +27,13 @@ use std::result::Result; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::thread; -use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +use std::time::{Duration, Instant, SystemTime}; use background_hang_monitor_api::{ BackgroundHangMonitor, BackgroundHangMonitorExitSignal, HangAnnotation, MonitoredComponentId, MonitoredComponentType, ScriptHangAnnotation, }; +use base::cross_process_instant::CrossProcessInstant; use base::id::{ BrowsingContextId, HistoryStateId, PipelineId, PipelineNamespace, TopLevelBrowsingContextId, }; @@ -91,7 +92,6 @@ use servo_config::opts; use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl}; use style::dom::OpaqueNode; use style::thread_state::{self, ThreadState}; -use time::precise_time_ns; use url::Position; use webgpu::{WebGPUDevice, WebGPUMsg}; use webrender_api::DocumentId; @@ -213,10 +213,9 @@ struct InProgressLoad { /// The origin for the document #[no_trace] origin: MutableOrigin, - /// Timestamp reporting the time in milliseconds when the browser started this load. - navigation_start: u64, - /// High res timestamp reporting the time in nanoseconds when the browser started this load. - navigation_start_precise: u64, + /// Timestamp reporting the time when the browser started this load. + #[no_trace] + navigation_start: CrossProcessInstant, /// For cancelling the fetch canceller: FetchCanceller, /// If inheriting the security context @@ -237,11 +236,7 @@ impl InProgressLoad { origin: MutableOrigin, inherited_secure_context: Option<bool>, ) -> InProgressLoad { - let duration = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default(); - let navigation_start = duration.as_millis(); - let navigation_start_precise = precise_time_ns(); + let navigation_start = CrossProcessInstant::now(); InProgressLoad { pipeline_id: id, browsing_context_id, @@ -253,8 +248,7 @@ impl InProgressLoad { throttled: false, url, origin, - navigation_start: navigation_start as u64, - navigation_start_precise, + navigation_start, canceller: Default::default(), inherited_secure_context, } @@ -2430,7 +2424,12 @@ impl ScriptThread { ) } - fn handle_set_epoch_paint_time(&self, pipeline_id: PipelineId, epoch: Epoch, time: u64) { + fn handle_set_epoch_paint_time( + &self, + pipeline_id: PipelineId, + epoch: Epoch, + time: CrossProcessInstant, + ) { let Some(window) = self.documents.borrow().find_window(pipeline_id) else { warn!("Received set epoch paint time message for closed pipeline {pipeline_id}."); return; @@ -3595,7 +3594,7 @@ impl ScriptThread { self.layout_to_constellation_chan.clone(), self.control_chan.clone(), final_url.clone(), - incomplete.navigation_start_precise, + incomplete.navigation_start, ); let layout_config = LayoutConfig { @@ -3635,7 +3634,6 @@ impl ScriptThread { origin.clone(), final_url.clone(), incomplete.navigation_start, - incomplete.navigation_start_precise, self.webgl_chan.as_ref().map(|chan| chan.channel()), self.webxr_registry.clone(), self.microtask_queue.clone(), @@ -3770,7 +3768,7 @@ impl ScriptThread { ); document.set_https_state(metadata.https_state); - document.set_navigation_start(incomplete.navigation_start_precise); + document.set_navigation_start(incomplete.navigation_start); if is_html_document == IsHTMLDocument::NonHTMLDocument { ServoParser::parse_xml_document(&document, None, final_url); @@ -4161,7 +4159,7 @@ impl ScriptThread { &self, pipeline_id: PipelineId, metric_type: ProgressiveWebMetricType, - metric_value: u64, + metric_value: CrossProcessInstant, ) { let window = self.documents.borrow().find_window(pipeline_id); if let Some(window) = window { diff --git a/components/shared/base/Cargo.toml b/components/shared/base/Cargo.toml index ec01fb5cc20..ea7426e83ef 100644 --- a/components/shared/base/Cargo.toml +++ b/components/shared/base/Cargo.toml @@ -20,4 +20,14 @@ malloc_size_of_derive = { workspace = true } parking_lot = { workspace = true } serde = { workspace = true } size_of_test = { workspace = true } +time_03 = { workspace = true } webrender_api = { workspace = true } + +[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] +mach2 = { workspace = true } + +[target.'cfg(all(unix, not(any(target_os = "macos", target_os = "ios"))))'.dependencies] +libc = { workspace = true } + +[target.'cfg(target_os = "windows")'.dependencies] +windows-sys = { workspace = true, features = ["Win32_System_Performance"] } diff --git a/components/shared/base/cross_process_instant.rs b/components/shared/base/cross_process_instant.rs new file mode 100644 index 00000000000..aa504b8b389 --- /dev/null +++ b/components/shared/base/cross_process_instant.rs @@ -0,0 +1,167 @@ +// Copyright 2024 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! An implementation of a monotonic, nanosecond precision timer, like [`std::time::Instant`] that +//! can be serialized and compared across processes. + +use std::ops::{Add, Sub}; + +use malloc_size_of_derive::MallocSizeOf; +use serde::{Deserialize, Serialize}; +use time_03::Duration; + +/// A monotonic, nanosecond precision timer that can be used cross-process. The value +/// stored internally is purposefully opaque as the origin is platform-specific. They can +/// be compared and [`time_03::Duration`] can be found by subtracting one from another. +/// The `time` crate is used in this case instead of `std::time` so that durations can +/// be negative. +#[derive( + Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize, +)] +pub struct CrossProcessInstant { + value: u64, +} + +impl CrossProcessInstant { + pub fn now() -> Self { + Self { + value: platform::now(), + } + } + + /// Some unspecified time epoch. This is mainly useful for converting DOM's `timeOrigin` into a + /// `DOMHighResolutionTimestamp`. See <https://w3c.github.io/hr-time/#sec-time-origin>. + pub fn epoch() -> Self { + Self { value: 0 } + } +} + +impl Sub for CrossProcessInstant { + type Output = Duration; + + fn sub(self, rhs: Self) -> Self::Output { + Duration::nanoseconds(self.value as i64 - rhs.value as i64) + } +} + +impl Add<Duration> for CrossProcessInstant { + type Output = Self; + + fn add(self, rhs: Duration) -> Self::Output { + Self { + value: self.value + rhs.whole_nanoseconds() as u64, + } + } +} + +#[cfg(all(unix, not(any(target_os = "macos", target_os = "ios"))))] +mod platform { + use libc::timespec; + + #[allow(unsafe_code)] + pub(super) fn now() -> u64 { + // SAFETY: libc::timespec is zero initializable. + let time = unsafe { + let mut time: timespec = std::mem::zeroed(); + libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut time); + time + }; + (time.tv_sec as u64) * 1000000000 + (time.tv_nsec as u64) + } +} + +#[cfg(any(target_os = "macos", target_os = "ios"))] +mod platform { + use std::sync::LazyLock; + + use mach2::mach_time::{mach_absolute_time, mach_timebase_info}; + + #[allow(unsafe_code)] + fn timebase_info() -> &'static mach_timebase_info { + static TIMEBASE_INFO: LazyLock<mach_timebase_info> = LazyLock::new(|| { + let mut timebase_info = mach_timebase_info { numer: 0, denom: 0 }; + unsafe { mach_timebase_info(&mut timebase_info) }; + timebase_info + }); + &*TIMEBASE_INFO + } + + #[allow(unsafe_code)] + pub(super) fn now() -> u64 { + let timebase_info = timebase_info(); + let absolute_time = unsafe { mach_absolute_time() }; + absolute_time * timebase_info.numer as u64 / timebase_info.denom as u64 + } +} + +#[cfg(target_os = "windows")] +mod platform { + use std::sync::atomic::{AtomicU64, Ordering}; + + use windows_sys::Win32::System::Performance::{ + QueryPerformanceCounter, QueryPerformanceFrequency, + }; + + /// The frequency of the value returned by `QueryPerformanceCounter` in counts per + /// second. This is taken from the Rust source code at: + /// <https://github.com/rust-lang/rust/blob/1a1cc050d8efc906ede39f444936ade1fdc9c6cb/library/std/src/sys/pal/windows/time.rs#L197> + #[allow(unsafe_code)] + fn frequency() -> i64 { + // Either the cached result of `QueryPerformanceFrequency` or `0` for + // uninitialized. Storing this as a single `AtomicU64` allows us to use + // `Relaxed` operations, as we are only interested in the effects on a + // single memory location. + static FREQUENCY: AtomicU64 = AtomicU64::new(0); + + let cached = FREQUENCY.load(Ordering::Relaxed); + // If a previous thread has filled in this global state, use that. + if cached != 0 { + return cached as i64; + } + // ... otherwise learn for ourselves ... + let mut frequency = 0; + let result = unsafe { QueryPerformanceFrequency(&mut frequency) }; + + if result == 0 { + return 0; + } + + FREQUENCY.store(frequency as u64, Ordering::Relaxed); + frequency + } + + #[allow(unsafe_code)] + /// Get the current instant value in nanoseconds. + /// Originally from: <https://github.com/rust-lang/rust/blob/1a1cc050d8efc906ede39f444936ade1fdc9c6cb/library/std/src/sys/pal/windows/time.rs#L175> + pub(super) fn now() -> u64 { + let mut counter_value = 0; + unsafe { QueryPerformanceCounter(&mut counter_value) }; + + /// Computes (value*numer)/denom without overflow, as long as both + /// (numer*denom) and the overall result fit into i64 (which is the case + /// for our time conversions). + /// Originally from: <https://github.com/rust-lang/rust/blob/1a1cc050d8efc906ede39f444936ade1fdc9c6cb/library/std/src/sys_common/mod.rs#L75> + fn mul_div_u64(value: u64, numer: u64, denom: u64) -> u64 { + let q = value / denom; + let r = value % denom; + // Decompose value as (value/denom*denom + value%denom), + // substitute into (value*numer)/denom and simplify. + // r < denom, so (denom*numer) is the upper bound of (r*numer) + q * numer + r * numer / denom + } + + static NANOSECONDS_PER_SECOND: u64 = 1_000_000_000; + mul_div_u64( + counter_value as u64, + NANOSECONDS_PER_SECOND, + frequency() as u64, + ) + } +} diff --git a/components/shared/base/lib.rs b/components/shared/base/lib.rs index 062ece48e21..1c5b6472046 100644 --- a/components/shared/base/lib.rs +++ b/components/shared/base/lib.rs @@ -9,6 +9,7 @@ //! You should almost never need to add a data type to this crate. Instead look for //! a more shared crate that has fewer dependents. +pub mod cross_process_instant; pub mod generic_channel; pub mod id; pub mod print_tree; diff --git a/components/shared/devtools/lib.rs b/components/shared/devtools/lib.rs index a7dfc0a0262..82a621966d7 100644 --- a/components/shared/devtools/lib.rs +++ b/components/shared/devtools/lib.rs @@ -341,8 +341,8 @@ pub struct HttpRequest { pub pipeline_id: PipelineId, pub started_date_time: SystemTime, pub time_stamp: i64, - pub connect_time: u64, - pub send_time: u64, + pub connect_time: Duration, + pub send_time: Duration, pub is_xhr: bool, } diff --git a/components/shared/net/lib.rs b/components/shared/net/lib.rs index 554f2334b8a..81ad50b517d 100644 --- a/components/shared/net/lib.rs +++ b/components/shared/net/lib.rs @@ -6,8 +6,8 @@ use std::fmt::Display; use std::sync::LazyLock; -use std::time::{SystemTime, UNIX_EPOCH}; +use base::cross_process_instant::CrossProcessInstant; use base::id::HistoryStateId; use cookie::Cookie; use headers::{ContentType, HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader}; @@ -20,7 +20,6 @@ use ipc_channel::Error as IpcError; use malloc_size_of::malloc_size_of_is_0; use malloc_size_of_derive::MallocSizeOf; use mime::Mime; -use num_traits::Zero; use rustls::Certificate; use serde::{Deserialize, Serialize}; use servo_rand::RngCore; @@ -494,21 +493,21 @@ pub struct ResourceCorsData { #[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] pub struct ResourceFetchTiming { - pub domain_lookup_start: u64, + pub domain_lookup_start: Option<CrossProcessInstant>, pub timing_check_passed: bool, pub timing_type: ResourceTimingType, /// Number of redirects until final resource (currently limited to 20) pub redirect_count: u16, - pub request_start: u64, - pub secure_connection_start: u64, - pub response_start: u64, - pub fetch_start: u64, - pub response_end: u64, - pub redirect_start: u64, - pub redirect_end: u64, - pub connect_start: u64, - pub connect_end: u64, - pub start_time: u64, + pub request_start: Option<CrossProcessInstant>, + pub secure_connection_start: Option<CrossProcessInstant>, + pub response_start: Option<CrossProcessInstant>, + pub fetch_start: Option<CrossProcessInstant>, + pub response_end: Option<CrossProcessInstant>, + pub redirect_start: Option<CrossProcessInstant>, + pub redirect_end: Option<CrossProcessInstant>, + pub connect_start: Option<CrossProcessInstant>, + pub connect_end: Option<CrossProcessInstant>, + pub start_time: Option<CrossProcessInstant>, } pub enum RedirectStartValue { @@ -539,8 +538,8 @@ pub enum ResourceAttribute { RedirectStart(RedirectStartValue), RedirectEnd(RedirectEndValue), FetchStart, - ConnectStart(u64), - ConnectEnd(u64), + ConnectStart(CrossProcessInstant), + ConnectEnd(CrossProcessInstant), SecureConnectionStart, ResponseEnd, StartTime(ResourceTimeValue), @@ -559,18 +558,18 @@ impl ResourceFetchTiming { ResourceFetchTiming { timing_type, timing_check_passed: true, - domain_lookup_start: 0, + domain_lookup_start: None, redirect_count: 0, - secure_connection_start: 0, - request_start: 0, - response_start: 0, - fetch_start: 0, - redirect_start: 0, - redirect_end: 0, - connect_start: 0, - connect_end: 0, - response_end: 0, - start_time: 0, + secure_connection_start: None, + request_start: None, + response_start: None, + fetch_start: None, + redirect_start: None, + redirect_end: None, + connect_start: None, + connect_end: None, + response_end: None, + start_time: None, } } @@ -586,47 +585,41 @@ impl ResourceFetchTiming { if !self.timing_check_passed && !should_attribute_always_be_updated { return; } - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_nanos() as u64; + let now = Some(CrossProcessInstant::now()); match attribute { ResourceAttribute::DomainLookupStart => self.domain_lookup_start = now, ResourceAttribute::RedirectCount(count) => self.redirect_count = count, ResourceAttribute::RequestStart => self.request_start = now, ResourceAttribute::ResponseStart => self.response_start = now, ResourceAttribute::RedirectStart(val) => match val { - RedirectStartValue::Zero => self.redirect_start = 0, + RedirectStartValue::Zero => self.redirect_start = None, RedirectStartValue::FetchStart => { - if self.redirect_start.is_zero() { + if self.redirect_start.is_none() { self.redirect_start = self.fetch_start } }, }, ResourceAttribute::RedirectEnd(val) => match val { - RedirectEndValue::Zero => self.redirect_end = 0, + RedirectEndValue::Zero => self.redirect_end = None, RedirectEndValue::ResponseEnd => self.redirect_end = self.response_end, }, ResourceAttribute::FetchStart => self.fetch_start = now, - ResourceAttribute::ConnectStart(val) => self.connect_start = val, - ResourceAttribute::ConnectEnd(val) => self.connect_end = val, + ResourceAttribute::ConnectStart(instant) => self.connect_start = Some(instant), + ResourceAttribute::ConnectEnd(instant) => self.connect_end = Some(instant), ResourceAttribute::SecureConnectionStart => self.secure_connection_start = now, ResourceAttribute::ResponseEnd => self.response_end = now, ResourceAttribute::StartTime(val) => match val { ResourceTimeValue::RedirectStart - if self.redirect_start.is_zero() || !self.timing_check_passed => {}, + if self.redirect_start.is_none() || !self.timing_check_passed => {}, _ => self.start_time = self.get_time_value(val), }, } } - fn get_time_value(&self, time: ResourceTimeValue) -> u64 { + fn get_time_value(&self, time: ResourceTimeValue) -> Option<CrossProcessInstant> { match time { - ResourceTimeValue::Zero => 0, - ResourceTimeValue::Now => SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_nanos() as u64, + ResourceTimeValue::Zero => None, + ResourceTimeValue::Now => Some(CrossProcessInstant::now()), ResourceTimeValue::FetchStart => self.fetch_start, ResourceTimeValue::RedirectStart => self.redirect_start, } @@ -634,13 +627,13 @@ impl ResourceFetchTiming { pub fn mark_timing_check_failed(&mut self) { self.timing_check_passed = false; - self.domain_lookup_start = 0; + self.domain_lookup_start = None; self.redirect_count = 0; - self.request_start = 0; - self.response_start = 0; - self.redirect_start = 0; - self.connect_start = 0; - self.connect_end = 0; + self.request_start = None; + self.response_start = None; + self.redirect_start = None; + self.connect_start = None; + self.connect_end = None; } } diff --git a/components/shared/net/tests/lib.rs b/components/shared/net/tests/lib.rs index 290ca902935..ab657052819 100644 --- a/components/shared/net/tests/lib.rs +++ b/components/shared/net/tests/lib.rs @@ -2,16 +2,20 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use base::cross_process_instant::CrossProcessInstant; use net_traits::{ResourceAttribute, ResourceFetchTiming, ResourceTimeValue, ResourceTimingType}; #[test] fn test_set_start_time_to_fetch_start_if_nonzero_tao() { let mut resource_timing: ResourceFetchTiming = ResourceFetchTiming::new(ResourceTimingType::Resource); - resource_timing.fetch_start = 1; - assert_eq!(resource_timing.start_time, 0, "`start_time` should be zero"); + resource_timing.fetch_start = Some(CrossProcessInstant::now()); assert!( - resource_timing.fetch_start > 0, + resource_timing.start_time.is_none(), + "`start_time` should be zero" + ); + assert!( + resource_timing.fetch_start.is_some(), "`fetch_start` should have a positive value" ); @@ -27,13 +31,13 @@ fn test_set_start_time_to_fetch_start_if_nonzero_tao() { fn test_set_start_time_to_fetch_start_if_zero_tao() { let mut resource_timing: ResourceFetchTiming = ResourceFetchTiming::new(ResourceTimingType::Resource); - resource_timing.start_time = 1; + resource_timing.start_time = Some(CrossProcessInstant::now()); assert!( - resource_timing.start_time > 0, + resource_timing.start_time.is_some(), "`start_time` should have a positive value" ); - assert_eq!( - resource_timing.fetch_start, 0, + assert!( + resource_timing.fetch_start.is_none(), "`fetch_start` should be zero" ); @@ -50,10 +54,13 @@ fn test_set_start_time_to_fetch_start_if_nonzero_no_tao() { let mut resource_timing: ResourceFetchTiming = ResourceFetchTiming::new(ResourceTimingType::Resource); resource_timing.mark_timing_check_failed(); - resource_timing.fetch_start = 1; - assert_eq!(resource_timing.start_time, 0, "`start_time` should be zero"); + resource_timing.fetch_start = Some(CrossProcessInstant::now()); + assert!( + resource_timing.start_time.is_none(), + "`start_time` should be zero" + ); assert!( - resource_timing.fetch_start > 0, + !resource_timing.fetch_start.is_none(), "`fetch_start` should have a positive value" ); @@ -70,13 +77,13 @@ fn test_set_start_time_to_fetch_start_if_zero_no_tao() { let mut resource_timing: ResourceFetchTiming = ResourceFetchTiming::new(ResourceTimingType::Resource); resource_timing.mark_timing_check_failed(); - resource_timing.start_time = 1; + resource_timing.start_time = Some(CrossProcessInstant::now()); assert!( - resource_timing.start_time > 0, + resource_timing.start_time.is_some(), "`start_time` should have a positive value" ); - assert_eq!( - resource_timing.fetch_start, 0, + assert!( + resource_timing.fetch_start.is_none(), "`fetch_start` should be zero" ); @@ -92,10 +99,13 @@ fn test_set_start_time_to_fetch_start_if_zero_no_tao() { fn test_set_start_time_to_redirect_start_if_nonzero_tao() { let mut resource_timing: ResourceFetchTiming = ResourceFetchTiming::new(ResourceTimingType::Resource); - resource_timing.redirect_start = 1; - assert_eq!(resource_timing.start_time, 0, "`start_time` should be zero"); + resource_timing.redirect_start = Some(CrossProcessInstant::now()); + assert!( + resource_timing.start_time.is_none(), + "`start_time` should be zero" + ); assert!( - resource_timing.redirect_start > 0, + resource_timing.redirect_start.is_some(), "`redirect_start` should have a positive value" ); @@ -113,13 +123,13 @@ fn test_set_start_time_to_redirect_start_if_nonzero_tao() { fn test_not_set_start_time_to_redirect_start_if_zero_tao() { let mut resource_timing: ResourceFetchTiming = ResourceFetchTiming::new(ResourceTimingType::Resource); - resource_timing.start_time = 1; + resource_timing.start_time = Some(CrossProcessInstant::now()); assert!( - resource_timing.start_time > 0, + resource_timing.start_time.is_some(), "`start_time` should have a positive value" ); - assert_eq!( - resource_timing.redirect_start, 0, + assert!( + resource_timing.redirect_start.is_none(), "`redirect_start` should be zero" ); @@ -139,10 +149,13 @@ fn test_not_set_start_time_to_redirect_start_if_nonzero_no_tao() { ResourceFetchTiming::new(ResourceTimingType::Resource); resource_timing.mark_timing_check_failed(); // Note: properly-behaved redirect_start should never be nonzero once TAO check has failed - resource_timing.redirect_start = 1; - assert_eq!(resource_timing.start_time, 0, "`start_time` should be zero"); + resource_timing.redirect_start = Some(CrossProcessInstant::now()); assert!( - resource_timing.redirect_start > 0, + resource_timing.start_time.is_none(), + "`start_time` should be zero" + ); + assert!( + resource_timing.redirect_start.is_some(), "`redirect_start` should have a positive value" ); @@ -161,13 +174,13 @@ fn test_not_set_start_time_to_redirect_start_if_zero_no_tao() { let mut resource_timing: ResourceFetchTiming = ResourceFetchTiming::new(ResourceTimingType::Resource); resource_timing.mark_timing_check_failed(); - resource_timing.start_time = 1; + resource_timing.start_time = Some(CrossProcessInstant::now()); assert!( - resource_timing.start_time > 0, + resource_timing.start_time.is_some(), "`start_time` should have a positive value" ); - assert_eq!( - resource_timing.redirect_start, 0, + assert!( + resource_timing.redirect_start.is_none(), "`redirect_start` should be zero" ); @@ -185,28 +198,37 @@ fn test_not_set_start_time_to_redirect_start_if_zero_no_tao() { fn test_set_start_time() { let mut resource_timing: ResourceFetchTiming = ResourceFetchTiming::new(ResourceTimingType::Resource); - assert_eq!(resource_timing.start_time, 0, "`start_time` should be zero"); + assert!( + resource_timing.start_time.is_none(), + "`start_time` should be zero" + ); // verify setting `start_time` to current time succeeds resource_timing.set_attribute(ResourceAttribute::StartTime(ResourceTimeValue::Now)); - assert!(resource_timing.start_time > 0, "failed to set `start_time`"); + assert!( + resource_timing.start_time.is_some(), + "failed to set `start_time`" + ); } #[test] fn test_reset_start_time() { let mut resource_timing: ResourceFetchTiming = ResourceFetchTiming::new(ResourceTimingType::Resource); - assert_eq!(resource_timing.start_time, 0, "`start_time` should be zero"); + assert!( + resource_timing.start_time.is_none(), + "`start_time` should be zero" + ); - resource_timing.start_time = 1; + resource_timing.start_time = Some(CrossProcessInstant::now()); assert!( - resource_timing.start_time > 0, + resource_timing.start_time.is_some(), "`start_time` should have a positive value" ); // verify resetting `start_time` (to zero) succeeds resource_timing.set_attribute(ResourceAttribute::StartTime(ResourceTimeValue::Zero)); - assert_eq!( - resource_timing.start_time, 0, + assert!( + resource_timing.start_time.is_none(), "failed to reset `start_time`" ); } diff --git a/components/shared/profile/Cargo.toml b/components/shared/profile/Cargo.toml index 718912a820d..045504652d1 100644 --- a/components/shared/profile/Cargo.toml +++ b/components/shared/profile/Cargo.toml @@ -11,10 +11,11 @@ name = "profile_traits" path = "lib.rs" [dependencies] +base = { workspace = true } crossbeam-channel = { workspace = true } ipc-channel = { workspace = true } log = { workspace = true } serde = { workspace = true } servo_config = { path = "../../config" } signpost = { git = "https://github.com/pcwalton/signpost.git" } -time = { workspace = true } +time_03 = { workspace = true } diff --git a/components/shared/profile/time.rs b/components/shared/profile/time.rs index 2ea952a93ea..62ac03f3e0f 100644 --- a/components/shared/profile/time.rs +++ b/components/shared/profile/time.rs @@ -2,12 +2,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use std::time::{SystemTime, UNIX_EPOCH}; - +use base::cross_process_instant::CrossProcessInstant; use ipc_channel::ipc::IpcSender; use log::warn; use serde::{Deserialize, Serialize}; use servo_config::opts; +use time_03::Duration; #[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] pub struct TimerMetadata { @@ -30,13 +30,16 @@ impl ProfilerChan { #[derive(Clone, Debug, Deserialize, Serialize)] pub enum ProfilerData { NoRecords, - Record(Vec<f64>), + Record(Vec<Duration>), } #[derive(Clone, Debug, Deserialize, Serialize)] pub enum ProfilerMsg { /// Normal message used for reporting time - Time((ProfilerCategory, Option<TimerMetadata>), (u64, u64)), + Time( + (ProfilerCategory, Option<TimerMetadata>), + (CrossProcessInstant, CrossProcessInstant), + ), /// Message used to get time spend entries for a particular ProfilerBuckets (in nanoseconds) Get( (ProfilerCategory, Option<TimerMetadata>), @@ -139,28 +142,15 @@ where if opts::get().debug.signpost { signpost::start(category as u32, &[0, 0, 0, (category as usize) >> 4]); } - let start_time = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_nanos(); - + let start_time = CrossProcessInstant::now(); let val = callback(); + let end_time = CrossProcessInstant::now(); - let end_time = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_nanos(); if opts::get().debug.signpost { signpost::end(category as u32, &[0, 0, 0, (category as usize) >> 4]); } - send_profile_data( - category, - meta, - &profiler_chan, - start_time as u64, - end_time as u64, - ); + send_profile_data(category, meta, &profiler_chan, start_time, end_time); val } @@ -168,8 +158,8 @@ pub fn send_profile_data( category: ProfilerCategory, meta: Option<TimerMetadata>, profiler_chan: &ProfilerChan, - start_time: u64, - end_time: u64, + start_time: CrossProcessInstant, + end_time: CrossProcessInstant, ) { profiler_chan.send(ProfilerMsg::Time((category, meta), (start_time, end_time))); } diff --git a/components/shared/script/lib.rs b/components/shared/script/lib.rs index d008f572048..0262cb8f785 100644 --- a/components/shared/script/lib.rs +++ b/components/shared/script/lib.rs @@ -21,6 +21,7 @@ use std::sync::Arc; use std::time::Duration; use background_hang_monitor_api::BackgroundHangMonitorRegister; +use base::cross_process_instant::CrossProcessInstant; use base::id::{ BlobId, BrowsingContextId, HistoryStateId, MessagePortId, PipelineId, PipelineNamespaceId, TopLevelBrowsingContextId, @@ -373,7 +374,7 @@ pub enum ConstellationControlMsg { /// Reload the given page. Reload(PipelineId), /// Notifies the script thread about a new recorded paint metric. - PaintMetric(PipelineId, ProgressiveWebMetricType, u64), + PaintMetric(PipelineId, ProgressiveWebMetricType, CrossProcessInstant), /// Notifies the media session about a user requested media session action. MediaSessionAction(PipelineId, MediaSessionActionType), /// Notifies script thread that WebGPU server has started @@ -382,7 +383,7 @@ pub enum ConstellationControlMsg { /// pipeline via the Constellation. SetScrollStates(PipelineId, Vec<ScrollState>), /// Send the paint time for a specific epoch. - SetEpochPaintTime(PipelineId, Epoch, u64), + SetEpochPaintTime(PipelineId, Epoch, CrossProcessInstant), } impl fmt::Debug for ConstellationControlMsg { diff --git a/components/shared/script_layout/lib.rs b/components/shared/script_layout/lib.rs index 4c43b2e859a..c5e3bf89d60 100644 --- a/components/shared/script_layout/lib.rs +++ b/components/shared/script_layout/lib.rs @@ -17,6 +17,7 @@ use std::sync::Arc; use app_units::Au; use atomic_refcell::AtomicRefCell; +use base::cross_process_instant::CrossProcessInstant; use base::id::{BrowsingContextId, PipelineId}; use base::Epoch; use canvas_traits::canvas::{CanvasId, CanvasMsg}; @@ -229,7 +230,7 @@ pub trait Layout { fn set_scroll_states(&mut self, scroll_states: &[ScrollState]); /// Set the paint time for a specific epoch. - fn set_epoch_paint_time(&mut self, epoch: Epoch, paint_time: u64); + fn set_epoch_paint_time(&mut self, epoch: Epoch, paint_time: CrossProcessInstant); fn query_content_box(&self, node: OpaqueNode) -> Option<Rect<Au>>; fn query_content_boxes(&self, node: OpaqueNode) -> Vec<Rect<Au>>; |