jupiter_rs/
watch.rs

1use std::fmt;
2use std::fmt::Display;
3use std::sync::atomic::{AtomicU64, Ordering};
4use std::time::Instant;
5
6pub fn format_micros(micros: i32, f: &mut dyn fmt::Write) -> fmt::Result {
7    return if micros < 1_000 {
8        write!(f, "{} us", micros)
9    } else if micros < 10_000 {
10        write!(f, "{:.2} ms", micros as f64 / 1_000.)
11    } else if micros < 100_000 {
12        write!(f, "{:.1} ms", micros as f64 / 1_000.)
13    } else if micros < 1_000_000 {
14        write!(f, "{} ms", micros / 1_000)
15    } else if micros < 10_000_000 {
16        write!(f, "{:.2} s", micros as f64 / 1_000_000.)
17    } else if micros < 100_000_000 {
18        write!(f, "{:.1} s", micros as f64 / 1_000_000.)
19    } else {
20        write!(f, "{} s", micros / 1_000_000)
21    };
22}
23
24pub struct Watch {
25    start: Instant
26}
27
28impl Watch {
29    pub fn start() -> Watch {
30        Watch { start: Instant::now() }
31    }
32
33    pub fn micros(&self) -> i32 {
34        self.start.elapsed().as_micros() as i32
35    }
36}
37
38impl Display for Watch {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        let micros = self.micros();
41        format_micros(micros, f)
42    }
43}
44
45pub struct Average {
46    sum_and_count: AtomicU64,
47    count: AtomicU64,
48}
49
50impl Clone for Average {
51    fn clone(&self) -> Self {
52        Average {
53            sum_and_count: AtomicU64::new(self.sum_and_count.load(Ordering::Relaxed)),
54            count: AtomicU64::new(self.count.load(Ordering::Relaxed)),
55        }
56    }
57}
58
59impl Average {
60    pub fn new() -> Average {
61        Average {
62            sum_and_count: AtomicU64::new(0),
63            count: AtomicU64::new(0),
64        }
65    }
66
67    fn sum_and_count(&self) -> (i32, i32) {
68        let last_sum_and_count = self.sum_and_count.load(Ordering::Relaxed);
69        let count = (last_sum_and_count & 0xFFFFFFFF) as i32;
70        let sum = ((last_sum_and_count >> 32) & 0xFFFFFFFF) as i32;
71
72        return (sum, count);
73    }
74
75    pub fn add(&self, value: i32) {
76        self.count.fetch_add(1, Ordering::Relaxed);
77
78        let (mut sum, mut count) = self.sum_and_count();
79
80        while count > 100 || sum as i64 + value as i64 > std::i32::MAX as i64 {
81            sum = count / 2 * sum / count;
82            count = count / 2;
83        }
84
85        sum += value;
86        count += 1;
87
88        let next_sum_and_count = (sum as u64 & 0xFFFFFFFF) << 32 | (count as u64 & 0xFFFFFFFF);
89        self.sum_and_count.store(next_sum_and_count, Ordering::Relaxed);
90    }
91
92    pub fn count(&self) -> u64 {
93        self.count.load(Ordering::Relaxed)
94    }
95
96    pub fn avg(&self) -> i32 {
97        let (sum, count) = self.sum_and_count();
98
99        return if sum == 0 {
100            0
101        } else {
102            sum / count
103        };
104    }
105}
106
107impl Display for Average {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        format_micros(self.avg(), f)?;
110        write!(f, " ({})", self.count())?;
111        Ok(())
112    }
113}
114
115#[cfg(test)]
116mod test {
117    use crate::watch::{Average, format_micros, Watch};
118
119    fn format(micros: i32) -> String {
120        let mut result = String::new();
121        format_micros(micros, &mut result).unwrap_or_default();
122
123        return result;
124    }
125
126    #[test]
127    fn formatting_works_as_expected() {
128        let w = Watch::start();
129        println!("{}", w);
130        assert_eq!(format(1), "1 us");
131        assert_eq!(format(12), "12 us");
132        assert_eq!(format(123), "123 us");
133        assert_eq!(format(1230), "1.23 ms");
134        assert_eq!(format(12300), "12.3 ms");
135        assert_eq!(format(123000), "123 ms");
136        assert_eq!(format(1230000), "1.23 s");
137        assert_eq!(format(12300000), "12.3 s");
138        assert_eq!(format(123000000), "123 s");
139        println!("{}", w);
140    }
141
142    #[test]
143    fn empty_average_is_properly_initialized() {
144        let avg = Average::new();
145        assert_eq!(avg.avg(), 0);
146        assert_eq!(avg.count(), 0);
147    }
148
149    #[test]
150    fn average_with_some_values_works() {
151        let avg = Average::new();
152        for i in 1..=10 { avg.add(i); }
153        assert_eq!(avg.avg(), 5);
154        assert_eq!(avg.count(), 10);
155    }
156
157    #[test]
158    fn formatting_average_works() {
159        let avg = Average::new();
160        avg.add(10_123);
161        assert_eq!(format!("{}", avg), "10.1 ms (1)");
162    }
163
164    #[test]
165    fn average_with_many_values_keeps_count() {
166        let avg = Average::new();
167        for i in 1..=1000 { avg.add(i); }
168        assert_eq!(avg.avg(), 928);
169        assert_eq!(avg.count(), 1000);
170    }
171
172    #[test]
173    fn average_overflows_sanely() {
174        {
175            let avg = Average::new();
176            avg.add(std::i32::MAX);
177            assert_eq!(avg.avg(), std::i32::MAX);
178            avg.add(std::i32::MAX);
179            assert_eq!(avg.avg(), std::i32::MAX);
180            avg.add(std::i32::MAX / 2);
181            avg.add(std::i32::MAX / 2);
182            assert_eq!(avg.avg(), std::i32::MAX / 2);
183        }
184        {
185            let avg = Average::new();
186            avg.add(10);
187            avg.add(std::i32::MAX - 50);
188            avg.add(60);
189
190            // If an overflow occurs, we compute the internal average (in this case the average of
191            // 10 and std::i32::MAX - 50. We then accept the next value (60) and compute the average
192            // between these two as result...
193            let average_before_overflow = (std::i32::MAX - 50 + 10) / 2;
194            let expected_average = (average_before_overflow + 60) / 2;
195            assert_eq!(avg.avg(), expected_average);
196        }
197    }
198}