1mod error;
4
5#[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#[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(µs).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}