Skip to main content

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 Measured<V> {
39    pub value: V,
40    pub measurement: Measurement,
41}
42
43impl<V> std::ops::Deref for Measured<V> {
44    type Target = V;
45
46    fn deref(&self) -> &Self::Target {
47        &self.value
48    }
49}
50
51impl<V> std::ops::DerefMut for Measured<V> {
52    fn deref_mut(&mut self) -> &mut Self::Target {
53        &mut self.value
54    }
55}
56
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
59pub struct Measurement(chrono::Duration);
60
61
62impl Measurement {
63    pub fn zero() -> Self { Self(chrono::Duration::zero()) }
64}
65
66impl Default for Measurement {
67    fn default() -> Self { Self::zero() }
68}
69
70impl ops::Add for Measurement {
71    type Output = MeasureResult<Self>;
72
73    fn add(self, rhs: Self) -> Self::Output {
74        let duration = self.0.checked_add(&rhs.0).ok_or(MeasureErr::Overflow)?;
75        Ok(Self::from(duration))
76    }
77}
78
79impl ops::Sub for Measurement {
80    type Output = MeasureResult<Self>;
81
82    fn sub(self, rhs: Self) -> Self::Output {
83        let duration = self.0.checked_sub(&rhs.0).ok_or(MeasureErr::Underflow)?;
84        Ok(Self::from(duration))
85    }
86}
87
88impl fmt::Display for Measurement {
89    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
90        match self.0.num_nanoseconds().map(|nanos| nanos as u64) {
91            None => write!(f, "overflow"),
92            Some(nanos) if nanos < NS_PER_US => write!(f, "{} ns", nanos),
93            Some(nanos) if nanos < NS_PER_MS => {
94                let micros: u64 = nanos / NS_PER_US;
95                let nanos: u64 = nanos % NS_PER_US;
96                if nanos > 0 {
97                    write!(f, "{} µs {} ns", micros, nanos)
98                } else {
99                    write!(f, "{} µs", micros)
100                }
101            },
102            Some(nanos) if nanos < NS_PER_SEC => {
103                let millis: u64 = nanos / NS_PER_MS;
104                let micros: u64 = (nanos % NS_PER_MS) / NS_PER_US;
105                if micros > 0 {
106                    write!(f, "{} ms {} µs", millis, micros)
107                } else {
108                    write!(f, "{} ms", millis)
109                }
110            },
111            Some(nanos) if nanos < NS_PER_MIN => {
112                let secs: u64 = nanos / NS_PER_SEC;
113                let millis: u64 = (nanos % NS_PER_SEC) / NS_PER_MS;
114                if millis > 0 {
115                    write!(f, "{} s {} ms", secs, millis)
116                } else {
117                    write!(f, "{} s", secs)
118                }
119            },
120            Some(nanos) if nanos < NS_PER_HOUR => {
121                let mins: u64 = nanos / NS_PER_MIN;
122                let secs: u64 = (nanos % NS_PER_MIN) / NS_PER_SEC;
123                if secs > 0 {
124                    write!(f, "{} m {} s", mins, secs)
125                } else {
126                    write!(f, "{} m", mins)
127                }
128            },
129            Some(nanos) => {
130                let hours: u64 = nanos / NS_PER_HOUR;
131                let mins: u64 = (nanos % NS_PER_HOUR) / NS_PER_MIN;
132                if mins > 0 {
133                    write!(f, "{} h {} m", hours, mins)
134                } else {
135                    write!(f, "{} h", hours)
136                }
137            },
138        }
139    }
140}
141
142
143impl From<Measurement> for chrono::Duration {
144    fn from(m: Measurement) -> chrono::Duration { m.0 }
145}
146
147impl From<chrono::Duration> for Measurement {
148    fn from(d: chrono::Duration) -> Self { Self(d) }
149}
150
151
152
153
154
155#[cfg(test)]
156mod tests {
157    use crate::Measurement;
158    use chrono::Duration;
159
160    #[test]
161    fn readme_md_example() {
162        use std::fs::File;
163        use std::io::Read;
164
165        let (contents, measurement) = measure! {{
166            let mut file = File::open("Cargo.lock")
167                .expect("failed to open Cargo.lock");
168            let mut contents = vec![];
169            file.read_to_end(&mut contents)
170                .expect("failed to read Cargo.lock");
171            String::from_utf8(contents)
172                .expect("failed to extract contents to String")
173        }};
174
175        println!("contents: {:?}", contents);
176        println!("opening and reading Cargo.lock took {}", measurement);
177    }
178
179    #[test]
180    fn format_hours_one_chunk() {
181        let one_chunk = Measurement(Duration::hours(10));
182        assert_eq!("10 h", format!("{}", one_chunk));
183    }
184
185    #[test]
186    fn format_hours_two_chunks() {
187        let (hours, mins) = (Duration::hours(3), Duration::minutes(3));
188        let two_chunks = Measurement(hours.checked_add(&mins).unwrap());
189        assert_eq!("3 h 3 m", format!("{}", two_chunks));
190    }
191
192    #[test]
193    fn format_minutes_one_chunk() {
194        let one_chunk = Measurement(Duration::minutes(10));
195        assert_eq!("10 m", format!("{}", one_chunk));
196    }
197
198    #[test]
199    fn format_minutes_two_chunks() {
200        let (mins, secs) = (Duration::minutes(3), Duration::seconds(3));
201        let two_chunks = Measurement(mins.checked_add(&secs).unwrap());
202        assert_eq!("3 m 3 s", format!("{}", two_chunks));
203    }
204
205    #[test]
206    fn format_seconds_one_chunk() {
207        let one_chunk = Measurement(Duration::seconds(10));
208        assert_eq!("10 s", format!("{}", one_chunk));
209    }
210
211    #[test]
212    fn format_seconds_two_chunks() {
213        let (secs, millis) = (Duration::seconds(3), Duration::milliseconds(3));
214        let two_chunks = Measurement(secs.checked_add(&millis).unwrap());
215        assert_eq!("3 s 3 ms", format!("{}", two_chunks));
216    }
217
218    #[test]
219    fn format_milliseconds_one_chunk() {
220        let one_chunk = Measurement(Duration::milliseconds(10));
221        assert_eq!("10 ms", format!("{}", one_chunk));
222    }
223
224    #[test]
225    fn format_milliseconds_two_chunks() {
226        let millis = Duration::milliseconds(3);
227        let micros = Duration::microseconds(3);
228        let two_chunks = Measurement(millis.checked_add(&micros).unwrap());
229        assert_eq!("3 ms 3 µs", format!("{}", two_chunks));
230    }
231
232    #[test]
233    fn format_microseconds_one_chunk() {
234        let one_chunk = Measurement(Duration::microseconds(10));
235        assert_eq!("10 µs", format!("{}", one_chunk));
236    }
237
238    #[test]
239    fn format_microseconds_two_chunks() {
240        let micros = Duration::microseconds(3);
241        let nanos = Duration::nanoseconds(3);
242        let two_chunks = Measurement(micros.checked_add(&nanos).unwrap());
243        assert_eq!("3 µs 3 ns", format!("{}", two_chunks));
244    }
245
246    #[test]
247    fn format_nanoseconds_one_chunk() {
248        let one_chunk = Measurement(Duration::nanoseconds(10));
249        assert_eq!("10 ns", format!("{}", one_chunk));
250    }
251}