1#[derive(Clone, Debug, PartialEq)]
19pub struct RateMeter {
20 tau: f64,
21 rate: f64,
22 mark: Option<(f64, u64)>,
23}
24
25impl RateMeter {
26 pub const ETA_FLOOR: f64 = 1024.0;
29
30 pub fn new(tau_secs: f64) -> Self {
32 RateMeter {
33 tau: tau_secs.max(f64::MIN_POSITIVE),
34 rate: 0.0,
35 mark: None,
36 }
37 }
38
39 pub fn sample(&mut self, now_secs: f64, done: u64) -> f64 {
45 if let Some((at, prev)) = self.mark {
46 let dt = now_secs - at;
47 if dt <= 0.0 {
48 return self.rate;
49 }
50 let inst = done.saturating_sub(prev) as f64 / dt;
51 let keep = (-dt / self.tau).exp();
52 self.rate = self.rate * keep + inst * (1.0 - keep);
53 }
54 self.mark = Some((now_secs, done));
55 self.rate
56 }
57
58 pub fn rate(&self) -> f64 {
60 self.rate
61 }
62
63 pub fn eta_secs(&self, remaining: u64) -> Option<f64> {
66 if self.rate > Self::ETA_FLOOR {
67 Some(remaining as f64 / self.rate)
68 } else {
69 None
70 }
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 fn close(a: f64, b: f64) -> bool {
79 (a - b).abs() <= 1e-6 * b.abs().max(1.0)
80 }
81
82 #[test]
83 fn the_first_sample_only_sets_the_mark() {
84 let mut m = RateMeter::new(1.5);
85 assert_eq!(m.sample(0.0, 5000), 0.0);
86 assert_eq!(m.rate(), 0.0);
87 assert_eq!(m.eta_secs(1), None);
88 }
89
90 #[test]
91 fn a_steady_transfer_converges_on_its_true_rate() {
92 let mut m = RateMeter::new(1.5);
93 let mut done = 0;
94 for i in 0..200 {
95 done += 10_000;
96 m.sample(i as f64 * 0.1, done);
97 }
98 assert!((m.rate() - 100_000.0).abs() < 1.0, "{}", m.rate());
99 }
100
101 #[test]
102 fn one_time_constant_forgets_sixty_three_percent_of_the_past() {
103 let mut m = RateMeter::new(2.0);
104 m.sample(0.0, 0);
105 m.sample(2.0, 2_000_000);
106 let expected = 1_000_000.0 * (1.0 - (-1.0f64).exp());
107 assert!(close(m.rate(), expected), "{} vs {expected}", m.rate());
108 }
109
110 #[test]
111 fn the_time_constant_does_not_depend_on_the_sample_cadence() {
112 let mut fast = RateMeter::new(1.5);
113 let mut slow = RateMeter::new(1.5);
114 for i in 0..=30 {
115 fast.sample(i as f64 * 0.1, i * 100_000);
116 }
117 for i in 0..=3 {
118 slow.sample(i as f64, i * 1_000_000);
119 }
120 let expected = 1_000_000.0 * (1.0 - (-3.0f64 / 1.5).exp());
121 assert!(
122 close(fast.rate(), expected),
123 "{} vs {expected}",
124 fast.rate()
125 );
126 assert!(
127 close(slow.rate(), expected),
128 "{} vs {expected}",
129 slow.rate()
130 );
131 }
132
133 #[test]
134 fn a_stalled_transfer_decays_towards_zero() {
135 let mut m = RateMeter::new(1.0);
136 m.sample(0.0, 0);
137 m.sample(1.0, 1_000_000);
138 let busy = m.rate();
139 m.sample(2.0, 1_000_000);
140 m.sample(3.0, 1_000_000);
141 assert!(m.rate() < busy * 0.2, "{} after {busy}", m.rate());
142 assert!(m.rate() > 0.0);
143 }
144
145 #[test]
146 fn a_clock_that_did_not_advance_and_a_counter_that_went_backwards_are_harmless() {
147 let mut m = RateMeter::new(1.5);
148 m.sample(0.0, 0);
149 m.sample(1.0, 500_000);
150 let r = m.rate();
151 assert_eq!(m.sample(1.0, 900_000), r);
152 assert_eq!(m.sample(0.5, 900_000), r);
153 let after = m.sample(2.0, 100_000);
154 assert!(after < r && after >= 0.0, "{after} after {r}");
155 }
156
157 #[test]
158 fn eta_is_withheld_until_the_rate_clears_the_floor() {
159 let mut m = RateMeter::new(0.001);
160 m.sample(0.0, 0);
161 m.sample(1.0, 1024);
162 assert!(close(m.rate(), 1024.0));
163 assert_eq!(m.eta_secs(4096), None, "exactly the floor is still noise");
164 m.sample(2.0, 1024 + 2048);
165 assert!(m.rate() > RateMeter::ETA_FLOOR);
166 let eta = m.eta_secs(4096).unwrap();
167 assert!(close(eta, 4096.0 / m.rate()), "{eta}");
168 assert_eq!(m.eta_secs(0), Some(0.0));
169 }
170
171 #[test]
172 fn a_degenerate_time_constant_does_not_produce_nan() {
173 let mut m = RateMeter::new(0.0);
174 m.sample(0.0, 0);
175 let r = m.sample(0.5, 1000);
176 assert!(r.is_finite());
177 assert!(close(r, 2000.0), "{r}");
178 }
179}