apollo_framework/
average.rs

1//! Provides a helper which computes a average of a series of values.
2//!
3//! This is intended to be used when measuring the system performance which is often tracked as
4//! averages (e.g. execution duration of commands).
5//!
6//! An [Average](Average) is internally mutable without needing a mutable reference as we rely on
7//! atomic intrinsics as provided by modern processors / compilers.
8//!
9//! # Example
10//!
11//! ```
12//! # use apollo_framework::average::Average;
13//! let avg = Average::new();
14//! avg.add(10);
15//! avg.add(20);
16//! avg.add(30);
17//!
18//! assert_eq!(avg.avg(), 20);
19//! assert_eq!(avg.count(), 3);
20//! ```
21use crate::fmt::format_micros;
22use std::fmt;
23use std::fmt::Display;
24use std::sync::atomic::{AtomicU64, Ordering};
25
26/// Computes a sliding average of a series of values.
27///
28/// This is intended to record performance measurements and to keep track of the sliding average
29/// as well as the total number of recorded values.
30///
31/// Note that this class overflows gracefully.
32///
33/// # Example
34///
35/// ```
36/// # use apollo_framework::average::Average;
37/// let avg = Average::new();
38/// avg.add(10);
39/// avg.add(20);
40/// avg.add(30);
41///
42/// assert_eq!(avg.avg(), 20);
43/// assert_eq!(avg.count(), 3);
44/// ```
45#[derive(Default)]
46pub struct Average {
47    sum_and_count: AtomicU64,
48    count: AtomicU64,
49}
50
51impl Clone for Average {
52    fn clone(&self) -> Self {
53        Average {
54            sum_and_count: AtomicU64::new(self.sum_and_count.load(Ordering::Relaxed)),
55            count: AtomicU64::new(self.count.load(Ordering::Relaxed)),
56        }
57    }
58}
59
60impl Average {
61    /// Creates a new average.
62    pub fn new() -> Average {
63        Average {
64            sum_and_count: AtomicU64::new(0),
65            count: AtomicU64::new(0),
66        }
67    }
68
69    fn sum_and_count(&self) -> (i32, i32) {
70        let last_sum_and_count = self.sum_and_count.load(Ordering::Relaxed);
71        let count = (last_sum_and_count & 0xFFFFFFFF) as i32;
72        let sum = ((last_sum_and_count >> 32) & 0xFFFFFFFF) as i32;
73
74        (sum, count)
75    }
76
77    /// Adds another value to be added to the average calculation.
78    ///
79    /// Internally we simply update the global u64 counter to keep track of the total recorded
80    /// values. Additionally, we have another u64 which is split into two i32 fields. One of these
81    /// is used to keep the actual count of the sliding average and another is used to store the
82    /// sum of the values.
83    ///
84    /// Whenever we recorded 100 values or the sum counter might overflow, we divide both values
85    /// by two and add the new values. This yields a sliding average which is fit for our purposes.
86    ///
87    /// As the main task is to store the average duration of a task in microseconds, the i32 sum
88    /// field shouldn't overflow under normal conditions.
89    ///
90    /// We perform this trickery (splitting a single field into two) so that this algorithm is
91    /// completely lock and wait free, as we only utilize atomic load and store operations. This
92    /// guarantees correctness while ensuring maximal performance.
93    pub fn add(&self, value: i32) {
94        let _ = self.count.fetch_add(1, Ordering::Relaxed);
95
96        let (mut sum, mut count) = self.sum_and_count();
97
98        while count > 100 || sum as i64 + value as i64 > i32::MAX as i64 {
99            sum = count / 2 * sum / count;
100            count /= 2;
101        }
102
103        sum += value;
104        count += 1;
105
106        let next_sum_and_count = (sum as u64 & 0xFFFFFFFF) << 32 | (count as u64 & 0xFFFFFFFF);
107        self.sum_and_count
108            .store(next_sum_and_count, Ordering::Relaxed);
109    }
110
111    /// Returns the total number of recorded values (unless an overflow of the internal u64 counter
112    /// occurred).
113    pub fn count(&self) -> u64 {
114        self.count.load(Ordering::Relaxed)
115    }
116
117    /// Computes the sliding average of the last 100 values.
118    pub fn avg(&self) -> i32 {
119        let (sum, count) = self.sum_and_count();
120
121        if sum == 0 {
122            0
123        } else {
124            sum / count
125        }
126    }
127}
128
129impl Display for Average {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        format_micros(self.avg(), f)?;
132        write!(f, " ({})", self.count())?;
133        Ok(())
134    }
135}
136
137#[cfg(test)]
138mod test {
139    use crate::average::Average;
140
141    #[test]
142    fn empty_average_is_properly_initialized() {
143        let avg = Average::new();
144        assert_eq!(avg.avg(), 0);
145        assert_eq!(avg.count(), 0);
146    }
147
148    #[test]
149    fn average_with_some_values_works() {
150        let avg = Average::new();
151        for i in 1..=10 {
152            avg.add(i);
153        }
154        assert_eq!(avg.avg(), 5);
155        assert_eq!(avg.count(), 10);
156    }
157
158    #[test]
159    fn formatting_average_works() {
160        let avg = Average::new();
161        avg.add(10_123);
162        assert_eq!(format!("{}", avg), "10.1 ms (1)");
163    }
164
165    #[test]
166    fn average_with_many_values_keeps_count() {
167        let avg = Average::new();
168        for i in 1..=1000 {
169            avg.add(i);
170        }
171        assert_eq!(avg.avg(), 928);
172        assert_eq!(avg.count(), 1000);
173    }
174
175    #[test]
176    fn average_overflows_sanely() {
177        {
178            let avg = Average::new();
179            avg.add(i32::MAX);
180            assert_eq!(avg.avg(), i32::MAX);
181            avg.add(i32::MAX);
182            assert_eq!(avg.avg(), i32::MAX);
183            avg.add(i32::MAX / 2);
184            avg.add(i32::MAX / 2);
185            assert_eq!(avg.avg(), i32::MAX / 2);
186        }
187        {
188            let avg = Average::new();
189            avg.add(10);
190            avg.add(i32::MAX - 50);
191            avg.add(60);
192
193            // If an overflow occurs, we compute the internal average (in this case the average of
194            // 10 and std::i32::MAX - 50. We then accept the next value (60) and compute the average
195            // between these two as result...
196            let average_before_overflow = (i32::MAX - 50 + 10) / 2;
197            let expected_average = (average_before_overflow + 60) / 2;
198            assert_eq!(avg.avg(), expected_average);
199        }
200    }
201}