Skip to main content

t2t_core/
uptime.rs

1//! Points on the clocks that count from boot.
2
3use core::str::FromStr;
4
5use derive_more::{Debug, Display};
6#[cfg(feature = "zerocopy")]
7use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
8
9use crate::{ParseTimedeltaError, Timedelta};
10
11/// A point on the monotonic clock, in nanoseconds since an origin near boot.
12///
13/// It never steps, and every process on a machine reads the same clock, so a deadline is an
14/// [`Uptime`] plus a [`Timedelta`]; it names no moment off this machine, which a
15/// [`Timestamp`](crate::Timestamp) does. It is written as the span since its origin, and parses
16/// back from it.
17///
18/// # Examples
19/// ```
20/// use t2t_core::{Timedelta, Uptime};
21///
22/// let start = Uptime::from_secs(42);
23/// let deadline = start + Timedelta::from_millis(50);
24///
25/// assert!(start < deadline, "a deadline lies ahead");
26/// assert_eq!(deadline.to_string(), "42s50ms", "written as the span since the origin");
27/// ```
28#[repr(transparent)]
29#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
30#[display("{}", Timedelta(*_0))]
31#[debug("{self}")]
32#[cfg_attr(feature = "zerocopy", derive(FromBytes, IntoBytes, Immutable, KnownLayout))]
33pub struct Uptime(pub(crate) i64);
34
35/// A point on the monotonic clock at the hardware's own rate, in nanoseconds since an origin
36/// near boot.
37///
38/// No time service adjusts its rate, so it drifts from an [`Uptime`] by as much as one slews the
39/// monotonic clock, and the two never mix. It is written as the span since its origin, and parses
40/// back from it.
41///
42/// # Examples
43/// ```
44/// use t2t_core::{RawUptime, Timedelta};
45///
46/// let (start, end) = (RawUptime::from_secs(42), RawUptime::from_millis(42_010));
47/// assert_eq!(end - start, Timedelta::from_millis(10), "a span free of any slewing");
48/// ```
49#[repr(transparent)]
50#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
51#[display("{}", Timedelta(*_0))]
52#[debug("{self}")]
53#[cfg_attr(feature = "zerocopy", derive(FromBytes, IntoBytes, Immutable, KnownLayout))]
54pub struct RawUptime(pub(crate) i64);
55
56/// A point on the boot clock, in nanoseconds since boot, the machine's suspensions counted.
57///
58/// It is the monotonic clock plus every suspension, so it never mixes with an [`Uptime`]. It is
59/// written as the span since boot, and parses back from it.
60///
61/// # Examples
62/// ```
63/// use t2t_core::{BootUptime, Timedelta};
64///
65/// let lease = BootUptime::from_secs(3_600) + Timedelta::HOUR;
66/// assert_eq!(lease.to_string(), "2h", "an expiry a suspension cannot carry past");
67/// ```
68#[repr(transparent)]
69#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
70#[display("{}", Timedelta(*_0))]
71#[debug("{self}")]
72#[cfg_attr(feature = "zerocopy", derive(FromBytes, IntoBytes, Immutable, KnownLayout))]
73pub struct BootUptime(pub(crate) i64);
74
75/// Reading a point counted from boot from the span since its origin.
76macro_rules! read_as_span {
77    ($point:ident) => {
78        /// Reads what [`Display`](core::fmt::Display) writes: the span since the origin.
79        impl FromStr for $point {
80            type Err = ParseTimedeltaError;
81
82            fn from_str(text: &str) -> Result<Self, Self::Err> {
83                text.parse().map(|span: Timedelta| Self(span.0))
84            }
85        }
86    };
87}
88
89read_as_span!(Uptime);
90read_as_span!(RawUptime);
91read_as_span!(BootUptime);
92
93#[cfg(test)]
94mod tests {
95    use alloc::format;
96    use alloc::string::ToString as _;
97
98    use crate::{BootUptime, ParseTimedeltaError, RawUptime, Uptime};
99
100    #[test]
101    fn a_point_from_boot_reads_back_from_its_span() {
102        let uptime = Uptime::from_millis(90_250);
103        assert_eq!(uptime.to_string(), "1m30s250ms", "written as the span since the origin");
104        assert_eq!("1m30s250ms".parse(), Ok(uptime), "and read back");
105        assert_eq!("2h".parse(), Ok(BootUptime::from_secs(7_200)), "on every clock from boot");
106        assert_eq!("-".parse::<RawUptime>(), Err(ParseTimedeltaError), "refused as a span is");
107    }
108
109    #[test]
110    fn a_width_pads_a_point_from_boot_as_it_pads_a_span() {
111        assert_eq!(format!("[{:>8}]", Uptime::from_millis(500)), "[   500ms]", "padded");
112        assert_eq!(format!("{:?}", RawUptime::from_secs(2)), "2s", "and debugged as written");
113    }
114}