| // Copyright (C) 2020, Cloudflare, Inc. |
| // Copyright (C) 2017, Google, Inc. |
| // |
| // Use of this source code is governed by the following BSD-style license: |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| // lib/minmax.c: windowed min/max tracker |
| // |
| // Kathleen Nichols' algorithm for tracking the minimum (or maximum) |
| // value of a data stream over some fixed time interval. (E.g., |
| // the minimum RTT over the past five minutes.) It uses constant |
| // space and constant time per update yet almost always delivers |
| // the same minimum as an implementation that has to keep all the |
| // data in the window. |
| // |
| // The algorithm keeps track of the best, 2nd best & 3rd best min |
| // values, maintaining an invariant that the measurement time of |
| // the n'th best >= n-1'th best. It also makes sure that the three |
| // values are widely separated in the time window since that bounds |
| // the worse case error when that data is monotonically increasing |
| // over the window. |
| // |
| // Upon getting a new min, we can forget everything earlier because |
| // it has no value - the new min is <= everything else in the window |
| // by definition and it's the most recent. So we restart fresh on |
| // every new min and overwrites 2nd & 3rd choices. The same property |
| // holds for 2nd & 3rd best. |
| |
| use std::time::Duration; |
| use std::time::Instant; |
| |
| #[derive(Copy, Clone)] |
| struct MinmaxSample<T> { |
| time: Instant, |
| value: T, |
| } |
| |
| pub struct Minmax<T> { |
| estimate: [MinmaxSample<T>; 3], |
| } |
| |
| impl<T: PartialOrd + Copy> Minmax<T> { |
| pub fn new(val: T) -> Self { |
| Minmax { |
| estimate: [MinmaxSample { |
| time: Instant::now(), |
| value: val, |
| }; 3], |
| } |
| } |
| |
| /// Resets the estimates to the given value. |
| pub fn reset(&mut self, time: Instant, meas: T) -> T { |
| let val = MinmaxSample { time, value: meas }; |
| |
| for i in self.estimate.iter_mut() { |
| *i = val; |
| } |
| |
| self.estimate[0].value |
| } |
| |
| /// Updates the min estimate based on the given measurement, and returns it. |
| pub fn running_min(&mut self, win: Duration, time: Instant, meas: T) -> T { |
| let val = MinmaxSample { time, value: meas }; |
| |
| let delta_time = time.duration_since(self.estimate[2].time); |
| |
| // Reset if there's nothing in the window or a new min value is found. |
| if val.value <= self.estimate[0].value || delta_time > win { |
| return self.reset(time, meas); |
| } |
| |
| if val.value <= self.estimate[1].value { |
| self.estimate[2] = val; |
| self.estimate[1] = val; |
| } else if val.value <= self.estimate[2].value { |
| self.estimate[2] = val; |
| } |
| |
| self.subwin_update(win, time, meas) |
| } |
| |
| /// Updates the max estimate based on the given measurement, and returns it. |
| pub fn _running_max(&mut self, win: Duration, time: Instant, meas: T) -> T { |
| let val = MinmaxSample { time, value: meas }; |
| |
| let delta_time = time.duration_since(self.estimate[2].time); |
| |
| // Reset if there's nothing in the window or a new max value is found. |
| if val.value >= self.estimate[0].value || delta_time > win { |
| return self.reset(time, meas); |
| } |
| |
| if val.value >= self.estimate[1].value { |
| self.estimate[2] = val; |
| self.estimate[1] = val; |
| } else if val.value >= self.estimate[2].value { |
| self.estimate[2] = val |
| } |
| |
| self.subwin_update(win, time, meas) |
| } |
| |
| /// As time advances, update the 1st, 2nd and 3rd estimates. |
| fn subwin_update(&mut self, win: Duration, time: Instant, meas: T) -> T { |
| let val = MinmaxSample { time, value: meas }; |
| |
| let delta_time = time.duration_since(self.estimate[0].time); |
| |
| if delta_time > win { |
| // Passed entire window without a new val so make 2nd estimate the |
| // new val & 3rd estimate the new 2nd choice. we may have to iterate |
| // this since our 2nd estimate may also be outside the window (we |
| // checked on entry that the third estimate was in the window). |
| self.estimate[0] = self.estimate[1]; |
| self.estimate[1] = self.estimate[2]; |
| self.estimate[2] = val; |
| |
| if time.duration_since(self.estimate[0].time) > win { |
| self.estimate[0] = self.estimate[1]; |
| self.estimate[1] = self.estimate[2]; |
| self.estimate[2] = val; |
| } |
| } else if self.estimate[1].time == self.estimate[0].time && |
| delta_time > win.div_f32(4.0) |
| { |
| // We've passed a quarter of the window without a new val so take a |
| // 2nd estimate from the 2nd quarter of the window. |
| self.estimate[2] = val; |
| self.estimate[1] = val; |
| } else if self.estimate[2].time == self.estimate[1].time && |
| delta_time > win.div_f32(2.0) |
| { |
| // We've passed half the window without finding a new val so take a |
| // 3rd estimate from the last half of the window. |
| self.estimate[2] = val; |
| } |
| |
| self.estimate[0].value |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[test] |
| fn reset_filter_rtt() { |
| let mut f = Minmax::new(Duration::new(0, 0)); |
| let now = Instant::now(); |
| let rtt = Duration::from_millis(50); |
| |
| let rtt_min = f.reset(now, rtt); |
| assert_eq!(rtt_min, rtt); |
| |
| assert_eq!(f.estimate[0].time, now); |
| assert_eq!(f.estimate[0].value, rtt); |
| |
| assert_eq!(f.estimate[1].time, now); |
| assert_eq!(f.estimate[1].value, rtt); |
| |
| assert_eq!(f.estimate[2].time, now); |
| assert_eq!(f.estimate[2].value, rtt); |
| } |
| |
| #[test] |
| fn reset_filter_bandwidth() { |
| let mut f = Minmax::new(0); |
| let now = Instant::now(); |
| let bw = 2000; |
| |
| let bw_min = f.reset(now, bw); |
| assert_eq!(bw_min, bw); |
| |
| assert_eq!(f.estimate[0].time, now); |
| assert_eq!(f.estimate[0].value, bw); |
| |
| assert_eq!(f.estimate[1].time, now); |
| assert_eq!(f.estimate[1].value, bw); |
| |
| assert_eq!(f.estimate[2].time, now); |
| assert_eq!(f.estimate[2].value, bw); |
| } |
| |
| #[test] |
| fn get_windowed_min_rtt() { |
| let mut f = Minmax::new(Duration::new(0, 0)); |
| let rtt_25 = Duration::from_millis(25); |
| let rtt_24 = Duration::from_millis(24); |
| let win = Duration::from_millis(500); |
| let mut time = Instant::now(); |
| |
| let mut rtt_min = f.reset(time, rtt_25); |
| assert_eq!(rtt_min, rtt_25); |
| |
| time += Duration::from_millis(250); |
| rtt_min = f.running_min(win, time, rtt_24); |
| assert_eq!(rtt_min, rtt_24); |
| assert_eq!(f.estimate[1].value, rtt_24); |
| assert_eq!(f.estimate[2].value, rtt_24); |
| |
| time += Duration::from_millis(600); |
| rtt_min = f.running_min(win, time, rtt_25); |
| assert_eq!(rtt_min, rtt_25); |
| assert_eq!(f.estimate[1].value, rtt_25); |
| assert_eq!(f.estimate[2].value, rtt_25); |
| } |
| |
| #[test] |
| fn get_windowed_min_bandwidth() { |
| let mut f = Minmax::new(0); |
| let bw_200 = 200; |
| let bw_500 = 500; |
| let win = Duration::from_millis(500); |
| let mut time = Instant::now(); |
| |
| let mut bw_min = f.reset(time, bw_500); |
| assert_eq!(bw_min, bw_500); |
| |
| time += Duration::from_millis(250); |
| bw_min = f.running_min(win, time, bw_200); |
| assert_eq!(bw_min, bw_200); |
| assert_eq!(f.estimate[1].value, bw_200); |
| assert_eq!(f.estimate[2].value, bw_200); |
| |
| time += Duration::from_millis(600); |
| bw_min = f.running_min(win, time, bw_500); |
| assert_eq!(bw_min, bw_500); |
| assert_eq!(f.estimate[1].value, bw_500); |
| assert_eq!(f.estimate[2].value, bw_500); |
| } |
| |
| #[test] |
| fn get_windowed_max_rtt() { |
| let mut f = Minmax::new(Duration::new(0, 0)); |
| let rtt_25 = Duration::from_millis(25); |
| let rtt_24 = Duration::from_millis(24); |
| let win = Duration::from_millis(500); |
| let mut time = Instant::now(); |
| |
| let mut rtt_max = f.reset(time, rtt_24); |
| assert_eq!(rtt_max, rtt_24); |
| |
| time += Duration::from_millis(250); |
| rtt_max = f._running_max(win, time, rtt_25); |
| assert_eq!(rtt_max, rtt_25); |
| assert_eq!(f.estimate[1].value, rtt_25); |
| assert_eq!(f.estimate[2].value, rtt_25); |
| |
| time += Duration::from_millis(600); |
| rtt_max = f._running_max(win, time, rtt_24); |
| assert_eq!(rtt_max, rtt_24); |
| assert_eq!(f.estimate[1].value, rtt_24); |
| assert_eq!(f.estimate[2].value, rtt_24); |
| } |
| |
| #[test] |
| fn get_windowed_max_bandwidth() { |
| let mut f = Minmax::new(0); |
| let bw_200 = 200; |
| let bw_500 = 500; |
| let win = Duration::from_millis(500); |
| let mut time = Instant::now(); |
| |
| let mut bw_max = f.reset(time, bw_200); |
| assert_eq!(bw_max, bw_200); |
| |
| time += Duration::from_millis(5000); |
| bw_max = f._running_max(win, time, bw_500); |
| assert_eq!(bw_max, bw_500); |
| assert_eq!(f.estimate[1].value, bw_500); |
| assert_eq!(f.estimate[2].value, bw_500); |
| |
| time += Duration::from_millis(600); |
| bw_max = f._running_max(win, time, bw_200); |
| assert_eq!(bw_max, bw_200); |
| assert_eq!(f.estimate[1].value, bw_200); |
| assert_eq!(f.estimate[2].value, bw_200); |
| } |
| |
| #[test] |
| fn get_windowed_min_estimates_rtt() { |
| let mut f = Minmax::new(Duration::new(0, 0)); |
| let rtt_25 = Duration::from_millis(25); |
| let rtt_24 = Duration::from_millis(24); |
| let rtt_23 = Duration::from_millis(23); |
| let rtt_22 = Duration::from_millis(22); |
| let win = Duration::from_secs(1); |
| let mut time = Instant::now(); |
| |
| let mut rtt_min = f.reset(time, rtt_23); |
| assert_eq!(rtt_min, rtt_23); |
| |
| time += Duration::from_millis(300); |
| rtt_min = f.running_min(win, time, rtt_24); |
| assert_eq!(rtt_min, rtt_23); |
| assert_eq!(f.estimate[1].value, rtt_24); |
| assert_eq!(f.estimate[2].value, rtt_24); |
| |
| time += Duration::from_millis(300); |
| rtt_min = f.running_min(win, time, rtt_25); |
| assert_eq!(rtt_min, rtt_23); |
| assert_eq!(f.estimate[1].value, rtt_24); |
| assert_eq!(f.estimate[2].value, rtt_25); |
| |
| time += Duration::from_millis(300); |
| rtt_min = f.running_min(win, time, rtt_22); |
| assert_eq!(rtt_min, rtt_22); |
| assert_eq!(f.estimate[1].value, rtt_22); |
| assert_eq!(f.estimate[2].value, rtt_22); |
| } |
| |
| #[test] |
| fn get_windowed_min_estimates_bandwidth() { |
| let mut f = Minmax::new(0); |
| let bw_500 = 500; |
| let bw_400 = 400; |
| let bw_300 = 300; |
| let bw_200 = 200; |
| let win = Duration::from_secs(1); |
| let mut time = Instant::now(); |
| |
| let mut bw_min = f.reset(time, bw_300); |
| assert_eq!(bw_min, bw_300); |
| |
| time += Duration::from_millis(300); |
| bw_min = f.running_min(win, time, bw_400); |
| assert_eq!(bw_min, bw_300); |
| assert_eq!(f.estimate[1].value, bw_400); |
| assert_eq!(f.estimate[2].value, bw_400); |
| |
| time += Duration::from_millis(300); |
| bw_min = f.running_min(win, time, bw_500); |
| assert_eq!(bw_min, bw_300); |
| assert_eq!(f.estimate[1].value, bw_400); |
| assert_eq!(f.estimate[2].value, bw_500); |
| |
| time += Duration::from_millis(300); |
| bw_min = f.running_min(win, time, bw_200); |
| assert_eq!(bw_min, bw_200); |
| assert_eq!(f.estimate[1].value, bw_200); |
| assert_eq!(f.estimate[2].value, bw_200); |
| } |
| |
| #[test] |
| fn get_windowed_max_estimates_rtt() { |
| let mut f = Minmax::new(Duration::new(0, 0)); |
| let rtt_25 = Duration::from_millis(25); |
| let rtt_24 = Duration::from_millis(24); |
| let rtt_23 = Duration::from_millis(23); |
| let rtt_26 = Duration::from_millis(26); |
| let win = Duration::from_secs(1); |
| let mut time = Instant::now(); |
| |
| let mut rtt_max = f.reset(time, rtt_25); |
| assert_eq!(rtt_max, rtt_25); |
| |
| time += Duration::from_millis(300); |
| rtt_max = f._running_max(win, time, rtt_24); |
| assert_eq!(rtt_max, rtt_25); |
| assert_eq!(f.estimate[1].value, rtt_24); |
| assert_eq!(f.estimate[2].value, rtt_24); |
| |
| time += Duration::from_millis(300); |
| rtt_max = f._running_max(win, time, rtt_23); |
| assert_eq!(rtt_max, rtt_25); |
| assert_eq!(f.estimate[1].value, rtt_24); |
| assert_eq!(f.estimate[2].value, rtt_23); |
| |
| time += Duration::from_millis(300); |
| rtt_max = f._running_max(win, time, rtt_26); |
| assert_eq!(rtt_max, rtt_26); |
| assert_eq!(f.estimate[1].value, rtt_26); |
| assert_eq!(f.estimate[2].value, rtt_26); |
| } |
| |
| #[test] |
| fn get_windowed_max_estimates_bandwidth() { |
| let mut f = Minmax::new(0); |
| let bw_500 = 500; |
| let bw_400 = 400; |
| let bw_300 = 300; |
| let bw_600 = 600; |
| let win = Duration::from_secs(1); |
| let mut time = Instant::now(); |
| |
| let mut bw_max = f.reset(time, bw_500); |
| assert_eq!(bw_max, bw_500); |
| |
| time += Duration::from_millis(300); |
| bw_max = f._running_max(win, time, bw_400); |
| assert_eq!(bw_max, bw_500); |
| assert_eq!(f.estimate[1].value, bw_400); |
| assert_eq!(f.estimate[2].value, bw_400); |
| |
| time += Duration::from_millis(300); |
| bw_max = f._running_max(win, time, bw_300); |
| assert_eq!(bw_max, bw_500); |
| assert_eq!(f.estimate[1].value, bw_400); |
| assert_eq!(f.estimate[2].value, bw_300); |
| |
| time += Duration::from_millis(300); |
| bw_max = f._running_max(win, time, bw_600); |
| assert_eq!(bw_max, bw_600); |
| assert_eq!(f.estimate[1].value, bw_600); |
| assert_eq!(f.estimate[2].value, bw_600); |
| } |
| } |