base/
cross_process_instant.rs

1// Copyright 2024 The Servo Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution and at
3// http://rust-lang.org/COPYRIGHT.
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10
11//! An implementation of a monotonic, nanosecond precision timer, like [`std::time::Instant`] that
12//! can be serialized and compared across processes.
13
14use std::ops::{Add, Sub};
15
16use malloc_size_of_derive::MallocSizeOf;
17use serde::{Deserialize, Serialize};
18use time::Duration;
19
20/// A monotonic, nanosecond precision timer that can be used cross-process. The value
21/// stored internally is purposefully opaque as the origin is platform-specific. They can
22/// be compared and [`time::Duration`] can be found by subtracting one from another.
23/// The `time` crate is used in this case instead of `std::time` so that durations can
24/// be negative.
25#[derive(
26    Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize,
27)]
28pub struct CrossProcessInstant {
29    value: u64,
30}
31
32impl CrossProcessInstant {
33    pub fn now() -> Self {
34        Self {
35            value: platform::now(),
36        }
37    }
38
39    /// Some unspecified time epoch. This is mainly useful for converting DOM's `timeOrigin` into a
40    /// `DOMHighResolutionTimestamp`. See <https://w3c.github.io/hr-time/#sec-time-origin>.
41    pub fn epoch() -> Self {
42        Self { value: 0 }
43    }
44}
45
46impl Sub for CrossProcessInstant {
47    type Output = Duration;
48
49    fn sub(self, rhs: Self) -> Self::Output {
50        Duration::nanoseconds(self.value as i64 - rhs.value as i64)
51    }
52}
53
54impl Add<Duration> for CrossProcessInstant {
55    type Output = Self;
56
57    fn add(self, rhs: Duration) -> Self::Output {
58        Self {
59            value: self.value + rhs.whole_nanoseconds() as u64,
60        }
61    }
62}
63
64impl Sub<Duration> for CrossProcessInstant {
65    type Output = Self;
66
67    fn sub(self, rhs: Duration) -> Self::Output {
68        Self {
69            value: self.value - rhs.whole_nanoseconds() as u64,
70        }
71    }
72}
73
74#[cfg(all(unix, not(any(target_os = "macos", target_os = "ios"))))]
75mod platform {
76    use libc::timespec;
77
78    #[allow(unsafe_code)]
79    pub(super) fn now() -> u64 {
80        // SAFETY: libc::timespec is zero initializable.
81        let time = unsafe {
82            let mut time: timespec = std::mem::zeroed();
83            libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut time);
84            time
85        };
86        (time.tv_sec as u64) * 1000000000 + (time.tv_nsec as u64)
87    }
88}
89
90#[cfg(any(target_os = "macos", target_os = "ios"))]
91mod platform {
92    use std::sync::LazyLock;
93
94    use mach2::mach_time::{mach_absolute_time, mach_timebase_info};
95
96    #[allow(unsafe_code)]
97    fn timebase_info() -> &'static mach_timebase_info {
98        static TIMEBASE_INFO: LazyLock<mach_timebase_info> = LazyLock::new(|| {
99            let mut timebase_info = mach_timebase_info { numer: 0, denom: 0 };
100            unsafe { mach_timebase_info(&mut timebase_info) };
101            timebase_info
102        });
103        &TIMEBASE_INFO
104    }
105
106    #[allow(unsafe_code)]
107    pub(super) fn now() -> u64 {
108        let timebase_info = timebase_info();
109        let absolute_time = unsafe { mach_absolute_time() };
110        absolute_time * timebase_info.numer as u64 / timebase_info.denom as u64
111    }
112}
113
114#[cfg(target_os = "windows")]
115mod platform {
116    use std::sync::atomic::{AtomicU64, Ordering};
117
118    use windows_sys::Win32::System::Performance::{
119        QueryPerformanceCounter, QueryPerformanceFrequency,
120    };
121
122    /// The frequency of the value returned by `QueryPerformanceCounter` in counts per
123    /// second. This is taken from the Rust source code at:
124    /// <https://github.com/rust-lang/rust/blob/1a1cc050d8efc906ede39f444936ade1fdc9c6cb/library/std/src/sys/pal/windows/time.rs#L197>
125    #[allow(unsafe_code)]
126    fn frequency() -> i64 {
127        // Either the cached result of `QueryPerformanceFrequency` or `0` for
128        // uninitialized. Storing this as a single `AtomicU64` allows us to use
129        // `Relaxed` operations, as we are only interested in the effects on a
130        // single memory location.
131        static FREQUENCY: AtomicU64 = AtomicU64::new(0);
132
133        let cached = FREQUENCY.load(Ordering::Relaxed);
134        // If a previous thread has filled in this global state, use that.
135        if cached != 0 {
136            return cached as i64;
137        }
138        // ... otherwise learn for ourselves ...
139        let mut frequency = 0;
140        let result = unsafe { QueryPerformanceFrequency(&mut frequency) };
141
142        if result == 0 {
143            return 0;
144        }
145
146        FREQUENCY.store(frequency as u64, Ordering::Relaxed);
147        frequency
148    }
149
150    #[allow(unsafe_code)]
151    /// Get the current instant value in nanoseconds.
152    /// Originally from: <https://github.com/rust-lang/rust/blob/1a1cc050d8efc906ede39f444936ade1fdc9c6cb/library/std/src/sys/pal/windows/time.rs#L175>
153    pub(super) fn now() -> u64 {
154        let mut counter_value = 0;
155        unsafe { QueryPerformanceCounter(&mut counter_value) };
156
157        /// Computes (value*numer)/denom without overflow, as long as both
158        /// (numer*denom) and the overall result fit into i64 (which is the case
159        /// for our time conversions).
160        /// Originally from: <https://github.com/rust-lang/rust/blob/1a1cc050d8efc906ede39f444936ade1fdc9c6cb/library/std/src/sys_common/mod.rs#L75>
161        fn mul_div_u64(value: u64, numer: u64, denom: u64) -> u64 {
162            let q = value / denom;
163            let r = value % denom;
164            // Decompose value as (value/denom*denom + value%denom),
165            // substitute into (value*numer)/denom and simplify.
166            // r < denom, so (denom*numer) is the upper bound of (r*numer)
167            q * numer + r * numer / denom
168        }
169
170        static NANOSECONDS_PER_SECOND: u64 = 1_000_000_000;
171        mul_div_u64(
172            counter_value as u64,
173            NANOSECONDS_PER_SECOND,
174            frequency() as u64,
175        )
176    }
177}