Skip to main content

shrub_core/
timing.rs

1//! Command timing
2
3use crate::error;
4
5struct StopwatchTime {
6    now: std::time::SystemTime,
7    self_user: std::time::Duration,
8    self_system: std::time::Duration,
9    children_user: std::time::Duration,
10    children_system: std::time::Duration,
11}
12
13impl StopwatchTime {
14    #[allow(clippy::unchecked_time_subtraction)]
15    fn minus(&self, other: &Self) -> Result<StopwatchTiming, error::Error> {
16        let user = (self.self_user - other.self_user) + (self.children_user - other.children_user);
17        let system =
18            (self.self_system - other.self_system) + (self.children_system - other.children_system);
19
20        Ok(StopwatchTiming {
21            wall: self.now.duration_since(other.now)?,
22            user,
23            system,
24        })
25    }
26}
27
28pub(crate) struct Stopwatch {
29    start: StopwatchTime,
30}
31
32impl Stopwatch {
33    /// Stops timing and returns the elapsed wall, user, and system time.
34    ///
35    /// # Errors
36    ///
37    /// Returns an error if the current time or resource usage cannot be read,
38    /// or if the system clock moved backwards since the stopwatch started
39    /// ([`error::ErrorKind::TimeError`]).
40    pub fn stop(&self) -> Result<StopwatchTiming, error::Error> {
41        let end = get_current_stopwatch_time()?;
42        end.minus(&self.start)
43    }
44}
45pub(crate) struct StopwatchTiming {
46    pub wall: std::time::Duration,
47    pub user: std::time::Duration,
48    pub system: std::time::Duration,
49}
50
51pub(crate) fn start_timing() -> Result<Stopwatch, error::Error> {
52    Ok(Stopwatch {
53        start: get_current_stopwatch_time()?,
54    })
55}
56
57fn get_current_stopwatch_time() -> Result<StopwatchTime, error::Error> {
58    let now = std::time::SystemTime::now();
59    let (self_user, self_system) = crate::sys::resource::get_self_user_and_system_time()?;
60    let (children_user, children_system) =
61        crate::sys::resource::get_children_user_and_system_time()?;
62
63    Ok(StopwatchTime {
64        now,
65        self_user,
66        self_system,
67        children_user,
68        children_system,
69    })
70}
71
72/// Format the given duration in a non-POSIX-y way.
73///
74/// # Arguments
75///
76/// * `duration` - The duration to format.
77pub fn format_duration_non_posixly(duration: &std::time::Duration) -> String {
78    let minutes = duration.as_secs() / 60;
79    let seconds = duration.as_secs() % 60;
80    let millis = duration.subsec_millis();
81    format!("{minutes}m{seconds}.{millis:03}s")
82}
83
84/// Format the given duration in a POSIX-y way.
85///
86/// # Arguments
87///
88/// * `duration` - The duration to format.
89pub fn format_duration_posixly(duration: &std::time::Duration) -> String {
90    let seconds = duration.as_secs();
91    let ten_millis = duration.subsec_millis() / 10;
92    format!("{seconds}.{ten_millis:02}")
93}
94
95#[cfg(test)]
96mod tests {
97    use std::time::Duration;
98
99    use super::*;
100
101    #[test]
102    fn test_format_time() {
103        assert_eq!(
104            format_duration_non_posixly(&Duration::from_millis(0)),
105            "0m0.000s"
106        );
107        assert_eq!(
108            format_duration_non_posixly(&Duration::from_millis(1)),
109            "0m0.001s"
110        );
111        assert_eq!(
112            format_duration_non_posixly(&Duration::from_millis(123)),
113            "0m0.123s"
114        );
115        assert_eq!(
116            format_duration_non_posixly(&Duration::from_millis(1234)),
117            "0m1.234s"
118        );
119        assert_eq!(
120            format_duration_non_posixly(&Duration::from_millis(12345)),
121            "0m12.345s"
122        );
123        assert_eq!(
124            format_duration_non_posixly(&Duration::from_millis(123_456)),
125            "2m3.456s"
126        );
127        assert_eq!(
128            format_duration_non_posixly(&Duration::from_millis(1_234_567)),
129            "20m34.567s"
130        );
131
132        assert_eq!(
133            format_duration_non_posixly(&Duration::from_micros(1)),
134            "0m0.000s"
135        );
136        assert_eq!(
137            format_duration_non_posixly(&Duration::from_micros(999)),
138            "0m0.000s"
139        );
140        assert_eq!(
141            format_duration_non_posixly(&Duration::from_micros(1001)),
142            "0m0.001s"
143        );
144    }
145}