tempus_fugit/
lib.rs

1/// A library to measure the wall-clock time of Rust expressions.
2
3mod error;
4
5// TODO: If / When possible, replace this with derived De/Serialize impls.
6#[cfg(feature = "enable_serde")] mod serialize;
7
8pub use error::{MeasureErr, MeasureResult};
9pub use chrono::{Duration, Utc};
10use std::fmt;
11use std::ops;
12
13
14const NS_PER_US: u64   = 1e3 as u64;
15const NS_PER_MS: u64   = 1e6 as u64;
16const NS_PER_SEC: u64  = 1e9 as u64;
17const NS_PER_MIN: u64  = 60 * NS_PER_SEC;
18const NS_PER_HOUR: u64 = 60 * NS_PER_MIN;
19
20
21/// This macro measures the execution time of an expression,
22/// then returns a `(result, measurement)` tuple where:
23/// - `result` is the result of executing the expression on its own
24/// - `measurement` has type `Measurement`.
25#[macro_export]
26macro_rules! measure {
27    ($e:expr) => {{
28        let pre = $crate::Utc::now();
29        let result = { $e };
30        let post = $crate::Utc::now();
31        let delta = post.signed_duration_since(pre);
32        (result,  $crate::Measurement::from(delta))
33    }}
34}
35
36
37#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
38pub struct Measurement(chrono::Duration);
39
40
41impl Measurement {
42    pub fn zero() -> Self { Self(chrono::Duration::zero()) }
43}
44
45impl Default for Measurement {
46    fn default() -> Self { Self::zero() }
47}
48
49impl ops::Add for Measurement {
50    type Output = MeasureResult<Self>;
51
52    fn add(self, rhs: Self) -> Self::Output {
53        let duration = self.0.checked_add(&rhs.0).ok_or(MeasureErr::Overflow)?;
54        Ok(Self::from(duration))
55    }
56}
57
58impl ops::Sub for Measurement {
59    type Output = MeasureResult<Self>;
60
61    fn sub(self, rhs: Self) -> Self::Output {
62        let duration = self.0.checked_sub(&rhs.0).ok_or(MeasureErr::Underflow)?;
63        Ok(Self::from(duration))
64    }
65}
66
67impl fmt::Display for Measurement {
68    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
69        match self.0.num_nanoseconds().map(|nanos| nanos as u64) {
70            None => write!(f, "overflow"),
71            Some(nanos) if nanos < NS_PER_US => write!(f, "{} ns", nanos),
72            Some(nanos) if nanos < NS_PER_MS => {
73                let micros: u64 = nanos / NS_PER_US;
74                let nanos: u64 = nanos % NS_PER_US;
75                if nanos > 0 {
76                    write!(f, "{} µs {} ns", micros, nanos)
77                } else {
78                    write!(f, "{} µs", micros)
79                }
80            },
81            Some(nanos) if nanos < NS_PER_SEC => {
82                let millis: u64 = nanos / NS_PER_MS;
83                let micros: u64 = (nanos % NS_PER_MS) / NS_PER_US;
84                if micros > 0 {
85                    write!(f, "{} ms {} µs", millis, micros)
86                } else {
87                    write!(f, "{} ms", millis)
88                }
89            },
90            Some(nanos) if nanos < NS_PER_MIN => {
91                let secs: u64 = nanos / NS_PER_SEC;
92                let millis: u64 = (nanos % NS_PER_SEC) / NS_PER_MS;
93                if millis > 0 {
94                    write!(f, "{} s {} ms", secs, millis)
95                } else {
96                    write!(f, "{} s", secs)
97                }
98            },
99            Some(nanos) if nanos < NS_PER_HOUR => {
100                let mins: u64 = nanos / NS_PER_MIN;
101                let secs: u64 = (nanos % NS_PER_MIN) / NS_PER_SEC;
102                if secs > 0 {
103                    write!(f, "{} m {} s", mins, secs)
104                } else {
105                    write!(f, "{} m", mins)
106                }
107            },
108            Some(nanos) => {
109                let hours: u64 = nanos / NS_PER_HOUR;
110                let mins: u64 = (nanos % NS_PER_HOUR) / NS_PER_MIN;
111                if mins > 0 {
112                    write!(f, "{} h {} m", hours, mins)
113                } else {
114                    write!(f, "{} h", hours)
115                }
116            },
117        }
118    }
119}
120
121
122impl From<Measurement> for chrono::Duration {
123    fn from(m: Measurement) -> chrono::Duration { m.0 }
124}
125
126impl From<chrono::Duration> for Measurement {
127    fn from(d: chrono::Duration) -> Self { Self(d) }
128}
129
130
131
132
133
134#[cfg(test)]
135mod tests {
136    use crate::Measurement;
137    use chrono::Duration;
138
139    #[test]
140    fn readme_md_example() {
141        use std::fs::File;
142        use std::io::Read;
143
144        let (contents, measurement) = measure! {{
145            let mut file = File::open("Cargo.lock")
146                .expect("failed to open Cargo.lock");
147            let mut contents = vec![];
148            file.read_to_end(&mut contents)
149                .expect("failed to read Cargo.lock");
150            String::from_utf8(contents)
151                .expect("failed to extract contents to String")
152        }};
153
154        println!("contents: {:?}", contents);
155        println!("opening and reading Cargo.lock took {}", measurement);
156    }
157
158    #[test]
159    fn format_hours_one_chunk() {
160        let one_chunk = Measurement(Duration::hours(10));
161        assert_eq!("10 h", format!("{}", one_chunk));
162    }
163
164    #[test]
165    fn format_hours_two_chunks() {
166        let (hours, mins) = (Duration::hours(3), Duration::minutes(3));
167        let two_chunks = Measurement(hours.checked_add(&mins).unwrap());
168        assert_eq!("3 h 3 m", format!("{}", two_chunks));
169    }
170
171    #[test]
172    fn format_minutes_one_chunk() {
173        let one_chunk = Measurement(Duration::minutes(10));
174        assert_eq!("10 m", format!("{}", one_chunk));
175    }
176
177    #[test]
178    fn format_minutes_two_chunks() {
179        let (mins, secs) = (Duration::minutes(3), Duration::seconds(3));
180        let two_chunks = Measurement(mins.checked_add(&secs).unwrap());
181        assert_eq!("3 m 3 s", format!("{}", two_chunks));
182    }
183
184    #[test]
185    fn format_seconds_one_chunk() {
186        let one_chunk = Measurement(Duration::seconds(10));
187        assert_eq!("10 s", format!("{}", one_chunk));
188    }
189
190    #[test]
191    fn format_seconds_two_chunks() {
192        let (secs, millis) = (Duration::seconds(3), Duration::milliseconds(3));
193        let two_chunks = Measurement(secs.checked_add(&millis).unwrap());
194        assert_eq!("3 s 3 ms", format!("{}", two_chunks));
195    }
196
197    #[test]
198    fn format_milliseconds_one_chunk() {
199        let one_chunk = Measurement(Duration::milliseconds(10));
200        assert_eq!("10 ms", format!("{}", one_chunk));
201    }
202
203    #[test]
204    fn format_milliseconds_two_chunks() {
205        let millis = Duration::milliseconds(3);
206        let micros = Duration::microseconds(3);
207        let two_chunks = Measurement(millis.checked_add(&micros).unwrap());
208        assert_eq!("3 ms 3 µs", format!("{}", two_chunks));
209    }
210
211    #[test]
212    fn format_microseconds_one_chunk() {
213        let one_chunk = Measurement(Duration::microseconds(10));
214        assert_eq!("10 µs", format!("{}", one_chunk));
215    }
216
217    #[test]
218    fn format_microseconds_two_chunks() {
219        let micros = Duration::microseconds(3);
220        let nanos = Duration::nanoseconds(3);
221        let two_chunks = Measurement(micros.checked_add(&nanos).unwrap());
222        assert_eq!("3 µs 3 ns", format!("{}", two_chunks));
223    }
224
225    #[test]
226    fn format_nanoseconds_one_chunk() {
227        let one_chunk = Measurement(Duration::nanoseconds(10));
228        assert_eq!("10 ns", format!("{}", one_chunk));
229    }
230}