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 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(µs).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}