evobench_tools/
times.rs

1//! Time durations in microseconds and nanoseconds
2//!
3//! Plus conversions between them as well as traits for a common `u64`
4//! based representation and getting the unit as human-readable string
5//! from the type for doing type safe statistics (works with the
6//! `stats` module). Also includes formatting as strings in
7//! milliseconds but padded to the precision.
8
9use std::fmt::{Debug, Display};
10use std::ops::{Add, Sub};
11
12use num_traits::CheckedSub;
13use serde::{Deserialize, Serialize};
14
15use crate::resolution_unit::ResolutionUnit;
16
17pub trait ToStringMilliseconds {
18    /// "1234.567890" or as many digits as the type has precision for
19    /// (always that many, filling with zeroes)
20    fn to_string_ms(&self) -> String;
21}
22
23pub trait ToStringSeconds {
24    /// "1.234567890" or as many digits as the type has precision for
25    /// (always that many, filling with zeroes)
26    fn to_string_seconds(&self) -> String;
27}
28
29pub trait FromMicroseconds: Sized {
30    fn from_usec(useconds: u64) -> Option<Self>;
31}
32
33pub trait ToNanoseconds {
34    fn to_nsec(self) -> u64;
35}
36
37/// To nsec or usec depending on the type
38pub trait ToIncrements {
39    fn to_increments(self) -> u64;
40}
41
42/// (Only used in tests, should not be treated as important, or
43/// ToStringMilliseconds removed?)
44pub trait Time:
45    ToStringMilliseconds
46    + FromMicroseconds
47    + From<u64>
48    + Display
49    + ToNanoseconds
50    + Debug
51    + Copy
52    + ToIncrements
53{
54}
55
56impl Time for MicroTime {}
57impl ResolutionUnit for MicroTime {
58    const RESOLUTION_UNIT_SHORT: &str = "us";
59}
60impl Time for NanoTime {}
61impl ResolutionUnit for NanoTime {
62    const RESOLUTION_UNIT_SHORT: &str = "ns";
63}
64
65fn print_milli_micro(f: &mut std::fmt::Formatter<'_>, milli: u32, micro: u32) -> std::fmt::Result {
66    write!(f, "{milli}.{micro:03} ms")
67}
68
69fn print_milli_micro_nano(
70    f: &mut std::fmt::Formatter<'_>,
71    milli: u32,
72    micro: u32,
73    nano: u32,
74) -> std::fmt::Result {
75    write!(f, "{milli}.{micro:03}_{nano:03} ms")
76}
77
78fn print_micro_nano(f: &mut std::fmt::Formatter<'_>, micro: u32, nano: u32) -> std::fmt::Result {
79    write!(f, "{micro}.{nano:03} us")
80}
81
82macro_rules! define_time {
83    { $_Time:ident, $_sec:tt, $max__sec:tt } => {
84
85        #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
86        #[serde(deny_unknown_fields)]
87        pub struct $_Time {
88            sec: u32,
89            $_sec: u32,
90        }
91
92        impl $_Time {
93            /// Panics if sub-second part is not within range. XX how better
94            /// with serde?
95            pub fn check(self) {
96                assert!(self.is_valid())
97            }
98
99            pub fn is_valid(self)-> bool {
100                self.$_sec < $max__sec
101            }
102
103            pub fn new(sec: u32, $_sec: u32) -> Option<Self> {
104                let slf = Self { sec, $_sec };
105                if slf.is_valid() {
106                    Some(slf)
107                }  else {
108                    None
109                }
110            }
111
112            pub fn sec(self) -> u32 { self.sec }
113            pub fn $_sec(self) -> u32 { self.$_sec }
114        }
115
116        impl ToIncrements for $_Time {
117            fn to_increments(self) -> u64 {
118                u64::from(self.sec) * $max__sec + u64::from(self.$_sec)
119            }
120        }
121
122        impl Add for $_Time {
123            type Output = $_Time;
124
125            fn add(self, rhs: Self) -> Self::Output {
126                const CUTOFF: u32 = $max__sec;
127                let $_sec = self.$_sec + rhs.$_sec;
128                if $_sec >= CUTOFF {
129                    Self {
130                        sec: self.sec + rhs.sec + 1,
131                        $_sec: $_sec - CUTOFF,
132                    }
133                } else {
134                    Self {
135                        sec: self.sec + rhs.sec,
136                        $_sec,
137                    }
138                }
139            }
140        }
141
142        impl Sub for $_Time {
143            type Output = $_Time;
144
145            fn sub(self, rhs: Self) -> Self::Output {
146                self.checked_sub(&rhs).expect("number overflow")
147            }
148        }
149
150        impl CheckedSub for $_Time {
151            fn checked_sub(&self, rhs: &Self) -> Option<Self> {
152                let sec = self.sec.checked_sub(rhs.sec)?;
153                match self.$_sec.checked_sub(rhs.$_sec) {
154                    Some($_sec) => Some(Self { sec, $_sec }),
155                    None => Some(Self {
156                        sec: sec - 1,
157                        $_sec: (self.$_sec + $max__sec) - rhs.$_sec,
158                    }),
159                }
160            }
161        }
162    }
163}
164
165// `struct timeval` in POSIX.
166
167define_time!(MicroTime, usec, 1_000_000);
168
169impl MicroTime {
170    fn to_usec(self) -> u64 {
171        self.sec as u64 * 1_000_000 + (self.usec as u64)
172    }
173}
174
175impl FromMicroseconds for MicroTime {
176    fn from_usec(useconds: u64) -> Option<Self> {
177        let sec = useconds / 1_000_000;
178        let usec = useconds % 1_000_000;
179        Some(Self {
180            sec: sec.try_into().ok()?,
181            usec: usec.try_into().expect("always in range"),
182        })
183    }
184}
185
186impl ToNanoseconds for MicroTime {
187    fn to_nsec(self) -> u64 {
188        self.sec as u64 * 1_000_000_000 + (self.usec as u64 * 1000)
189    }
190}
191
192/// Assumes microseconds. Panics for values outside the representable
193/// range!
194impl From<u64> for MicroTime {
195    fn from(value: u64) -> Self {
196        Self::from_usec(value).expect("outside representable range")
197    }
198}
199
200/// Into microseconds.
201impl From<MicroTime> for u64 {
202    fn from(value: MicroTime) -> Self {
203        value.to_usec()
204    }
205}
206
207fn milli_micro(usec: u32) -> (u32, u32) {
208    (usec / 1000, usec % 1000)
209}
210
211fn format_integer_with_undercores(digits: &str) -> String {
212    digits
213        .as_bytes()
214        .rchunks(3)
215        .rev()
216        .map(std::str::from_utf8)
217        .collect::<Result<Vec<&str>, _>>()
218        .unwrap()
219        .join("_")
220}
221
222impl Display for MicroTime {
223    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224        let Self { sec, usec } = *self;
225        if sec >= 1 {
226            let (milli, micro) = milli_micro(usec);
227            let sec_str = format_integer_with_undercores(&sec.to_string());
228            write!(f, "{sec_str}.{milli:03}_{micro:03} s")
229        } else if usec >= 1_000 {
230            let (milli, micro) = milli_micro(usec);
231            print_milli_micro(f, milli, micro)
232        } else {
233            let usec_str = format_integer_with_undercores(&usec.to_string());
234            write!(f, "{usec_str} us")
235        }
236    }
237}
238
239// `struct timespec` in POSIX.
240
241define_time!(NanoTime, nsec, 1_000_000_000);
242
243impl NanoTime {
244    pub fn from_nsec(nseconds: u64) -> Option<Self> {
245        let sec = nseconds / 1_000_000_000;
246        let nsec = nseconds % 1_000_000_000;
247        Some(Self {
248            sec: sec.try_into().ok()?,
249            nsec: nsec.try_into().expect("always in range"),
250        })
251    }
252}
253
254impl FromMicroseconds for NanoTime {
255    fn from_usec(useconds: u64) -> Option<Self> {
256        let nsec = useconds.checked_mul(1000)?;
257        Self::from_nsec(nsec)
258    }
259}
260
261impl ToNanoseconds for NanoTime {
262    fn to_nsec(self) -> u64 {
263        self.sec as u64 * 1_000_000_000 + self.nsec as u64
264    }
265}
266
267impl From<MicroTime> for NanoTime {
268    fn from(value: MicroTime) -> Self {
269        NanoTime {
270            sec: value.sec,
271            nsec: value.usec * 1000,
272        }
273    }
274}
275
276/// Assumes nanoseconds. Panics for values outside the representable
277/// range!
278impl From<u64> for NanoTime {
279    fn from(value: u64) -> Self {
280        Self::from_nsec(value).expect("outside representable range")
281    }
282}
283
284/// Into nanoseconds.
285impl From<NanoTime> for u64 {
286    fn from(value: NanoTime) -> Self {
287        value.to_nsec()
288    }
289}
290
291fn milli_micro_nano(nsec: u32) -> (u32, u32, u32) {
292    let usec = nsec / 1000;
293    let nano = nsec % 1000;
294    let (milli, micro) = milli_micro(usec);
295    (milli, micro, nano)
296}
297
298impl Display for NanoTime {
299    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
300        let Self { sec, nsec } = *self;
301        if sec >= 1 {
302            let (milli, micro, nano) = milli_micro_nano(nsec);
303            let sec_str = format_integer_with_undercores(&sec.to_string());
304            write!(f, "{sec_str}.{milli:03}_{micro:03}_{nano:03} s")
305        } else {
306            let (milli, micro, nano) = milli_micro_nano(nsec);
307            if milli > 0 {
308                print_milli_micro_nano(f, milli, micro, nano)
309            } else if micro > 0 {
310                print_micro_nano(f, micro, nano)
311            } else {
312                let nsec_str = format_integer_with_undercores(&nsec.to_string());
313                write!(f, "{nsec_str} ns")
314            }
315        }
316    }
317}
318
319impl ToStringMilliseconds for MicroTime {
320    fn to_string_ms(&self) -> String {
321        let ms = self.sec * 1000 + self.usec / 1_000;
322        let usec_rest = self.usec % 1_000;
323        format!("{ms}.{usec_rest:03}")
324    }
325}
326
327impl ToStringMilliseconds for NanoTime {
328    fn to_string_ms(&self) -> String {
329        let ms = self.sec * 1000 + self.nsec / 1_000_000;
330        let nsec_rest = self.nsec % 1_000_000;
331        format!("{ms}.{nsec_rest:06}")
332    }
333}
334
335impl ToStringSeconds for MicroTime {
336    fn to_string_seconds(&self) -> String {
337        let Self { sec, usec } = self;
338        format!("{sec}.{usec:06}")
339    }
340}
341
342impl ToStringSeconds for NanoTime {
343    fn to_string_seconds(&self) -> String {
344        let Self { sec, nsec } = self;
345        format!("{sec}.{nsec:09}")
346    }
347}
348
349#[cfg(test)]
350mod tests {
351    use super::*;
352    use crate::digit_num::{Digit, DigitNum, DigitNumFormat};
353    use rand::Rng;
354
355    #[test]
356    fn t_micro_time() {
357        let a = MicroTime::new(2, 999_999).unwrap();
358        let b = MicroTime::new(3, 1).unwrap();
359        assert_eq!(a + b, MicroTime::new(6, 0).unwrap());
360        assert_eq!(
361            a + MicroTime::new(0, 2).unwrap(),
362            MicroTime::new(3, 1).unwrap()
363        );
364        let t = |sec, usec| MicroTime::new(sec, usec).unwrap();
365        assert_eq!(t(10, 2) - t(10, 1), t(0, 1));
366        assert_eq!(t(10, 2) - t(10, 2), t(0, 0));
367        // t(10, 2) - t(10, 3),
368        assert_eq!(t(11, 2) - t(10, 2), t(1, 0));
369        assert_eq!(t(11, 2) - t(10, 1), t(1, 1));
370        assert_eq!(t(11, 2) - t(10, 3), t(0, 999_999));
371        assert_eq!(b - a, MicroTime::new(0, 2).unwrap());
372        assert_eq!(t(0, 999_999) + t(3, 999_999), t(4, 999_998));
373        assert_eq!(t(4, 999_998) - t(3, 999_999), t(0, 999_999));
374        assert_eq!(t(4, 999_998) - t(0, 999_999), t(3, 999_999));
375    }
376
377    #[test]
378    #[should_panic]
379    fn t_micro_time_panic() {
380        let a = MicroTime::new(2, 999_999).unwrap();
381        let b = MicroTime::new(3, 1).unwrap();
382        let _ = a - b;
383    }
384
385    #[test]
386    #[should_panic]
387    fn t_micro_time_panic_new() {
388        let _ = MicroTime::new(2, 1_000_000).unwrap();
389    }
390
391    #[test]
392    fn t_nano_time() {
393        let t = |sec, nsec| NanoTime::new(sec, nsec).unwrap();
394        assert_eq!(t(4, 999_998) - t(0, 999_999), t(3, 999_999_999));
395        assert_eq!(t(4, 999_999) - t(0, 999_998), t(4, 1));
396
397        assert_eq!(t(0, 999_999_999) + t(3, 999_999_999), t(4, 999_999_998));
398        assert_eq!(t(4, 999_999_998) - t(3, 999_999_999), t(0, 999_999_999));
399        assert_eq!(t(4, 999_999_998) - t(0, 999_999_999), t(3, 999_999_999));
400    }
401
402    #[test]
403    fn t_nano_time_convert() {
404        let n = |sec, nsec| NanoTime::new(sec, nsec).unwrap();
405        let u = |sec, usec| MicroTime::new(sec, usec).unwrap();
406        assert_eq!(
407            {
408                let x: NanoTime = u(3, 490_000).into();
409                x
410            },
411            n(3, 490_000_000),
412        );
413        assert_eq!(u(8, 30).to_nsec(), n(8, 30_000).to_nsec(),);
414
415        assert_eq!(
416            NanoTime::from_nsec(u(8, 30).to_nsec()).unwrap(),
417            n(8, 30_000)
418        );
419    }
420
421    fn test_stringification<
422        const DIGITS_BELOW_MS: usize,
423        const DIGITS_BELOW_S: usize,
424        Time: ToStringMilliseconds
425            + ToStringSeconds
426            + FromMicroseconds
427            + From<u64>
428            + Display
429            + ToNanoseconds
430            + Debug
431            + Copy
432            + ToIncrements,
433    >() {
434        let mut num: DigitNum<DIGITS_BELOW_MS> = DigitNum::new();
435        let mut num_seconds: DigitNum<DIGITS_BELOW_S> = DigitNum::new();
436        let digits_above_ms = 10; // 10 is the max possible for just creating nums
437        let num_digits_to_test = DIGITS_BELOW_MS + digits_above_ms;
438        for _ in 0..num_digits_to_test {
439            let num_u64: u64 = (&num).try_into().unwrap();
440            let time = Time::from(num_u64);
441
442            // Test `to_string_ms()`
443            assert_eq!(
444                time.to_string_ms(),
445                num.to_string_with_params(DigitNumFormat {
446                    underscores: false,
447                    omit_trailing_dot: false
448                })
449            );
450
451            // Test `to_string_seconds()`
452            assert_eq!(
453                time.to_string_seconds(),
454                num_seconds.to_string_with_params(DigitNumFormat {
455                    underscores: false,
456                    omit_trailing_dot: false
457                })
458            );
459
460            // Test `Display`
461            let time_str = format!("{time}");
462            let parts: Vec<_> = time_str.split(' ').collect();
463            let (number, num_digits_below_seconds) = match parts.as_slice() {
464                &[number, "ns"] => (number, 9),
465                &[number, "us"] => (number, 6),
466                &[number, "ms"] => (number, 3),
467                &[number, "s"] => (number, 0),
468                _ => unreachable!(),
469            };
470            let (expect_dot, expect_digits_after_dot) =
471                if DIGITS_BELOW_S == num_digits_below_seconds {
472                    (false, 0)
473                } else {
474                    (true, DIGITS_BELOW_S - num_digits_below_seconds)
475                };
476            let parts: Vec<&str> = number.split('.').collect();
477            let digits = match parts.as_slice() {
478                [left, right] => {
479                    assert!(expect_dot);
480                    // If given ns, and DIGITS_BELOW_S is 9, then
481                    // right.len() is 0. Won't even have a dot.
482                    let right_without_underscores = right.replace("_", "");
483                    assert_eq!(right_without_underscores.len(), expect_digits_after_dot);
484                    format!("{left}_{right}")
485                }
486                [left_only] => {
487                    assert!(!expect_dot);
488                    format!("{left_only}")
489                }
490                _ => unreachable!(),
491            };
492            let num_in_increments: DigitNum<0> = num.clone().into_changed_dot_position();
493            assert_eq!(num_u64, u64::try_from(&num_in_increments).unwrap());
494            assert_eq!(
495                digits,
496                num_in_increments.to_string_with_params(DigitNumFormat {
497                    underscores: true,
498                    omit_trailing_dot: true
499                })
500            );
501
502            // Test `ToIncrements`
503            assert_eq!(time.to_increments(), num_u64);
504
505            // Test `ToNanoseconds`
506            let ns = time.to_nsec();
507            // num_u64 is in us or ns, depending on the type.
508            let num_u64_multiplicator = match DIGITS_BELOW_S {
509                6 => 1000,
510                9 => 1,
511                _ => unreachable!(),
512            };
513            assert_eq!(ns, num_u64 * num_u64_multiplicator);
514
515            // Test `FromMicroseconds`
516            let time2 = Time::from_usec(num_u64).expect("works for MicroTime::from, so also here");
517            let ns2 = time2.to_nsec();
518            assert_eq!(ns2, num_u64 * 1000);
519
520            let digit = Digit::random();
521            num.push_lowest_digit(digit);
522            num_seconds.push_lowest_digit(digit);
523        }
524    }
525
526    #[test]
527    fn t_micro_time_stringification() {
528        test_stringification::<3, 6, MicroTime>();
529    }
530
531    #[test]
532    fn t_nano_time_stringification() {
533        test_stringification::<6, 9, NanoTime>();
534    }
535
536    fn test_arithmetic<Time: From<u64> + Display + Debug + Copy + Add + Sub>()
537    where
538        <Time as Add>::Output: ToIncrements,
539        <Time as Sub>::Output: ToIncrements,
540    {
541        let mut rng = rand::thread_rng();
542
543        for _ in 0..100000 {
544            // (* (- (expt 2 32) 1) 1000000 1/2)
545            let max = 2147483647500000;
546            let a: u64 = rng.gen_range(0..max);
547            let ta = Time::from(a);
548            let b: u64 = rng.gen_range(0..max);
549            let tb = Time::from(b);
550            let c = a + b;
551            let tc = ta + tb;
552            assert_eq!(c, tc.to_increments());
553            let (d, td) = if a > b {
554                (a - b, ta - tb)
555            } else {
556                (b - a, tb - ta)
557            };
558            assert_eq!(d, td.to_increments());
559        }
560    }
561
562    #[test]
563    fn t_micro_time_arithmetic() {
564        test_arithmetic::<MicroTime>();
565    }
566
567    #[test]
568    fn t_nano_time_arithmetic() {
569        test_arithmetic::<NanoTime>();
570    }
571}