scirs2_core/logging/progress/
adaptive.rs1use std::collections::VecDeque;
7use std::time::{Duration, Instant};
8
9pub struct AdaptiveController {
11 update_intervals: VecDeque<Duration>,
13 processing_speeds: VecDeque<f64>,
15 min_interval: Duration,
17 max_interval: Duration,
19 target_frequency: f64,
21}
22
23impl AdaptiveController {
24 pub fn new(min_interval: Duration, maxinterval: Duration) -> Self {
26 Self {
27 update_intervals: VecDeque::with_capacity(20),
28 processing_speeds: VecDeque::with_capacity(20),
29 min_interval,
30 max_interval: maxinterval,
31 target_frequency: 2.0, }
33 }
34
35 pub fn set_target_frequency(&mut self, frequency: f64) {
37 self.target_frequency = frequency.clamp(0.1, 10.0); }
39
40 pub fn record_update(&mut self, interval: Duration, processingspeed: f64) {
42 self.update_intervals.push_back(interval);
43 self.processing_speeds.push_back(processingspeed);
44
45 if self.update_intervals.len() > 20 {
47 self.update_intervals.pop_front();
48 self.processing_speeds.pop_front();
49 }
50 }
51
52 pub fn calculate_interval(&self, currentprogress: f64) -> Duration {
54 let base_interval = Duration::from_secs_f64(1.0 / self.target_frequency);
56
57 let position_factor = self.calculate_position_factor(currentprogress);
59
60 let stability_factor = self.calculate_stability_factor();
62
63 let adjusted_interval = base_interval.mul_f64(position_factor * stability_factor);
65
66 adjusted_interval
68 .max(self.min_interval)
69 .min(self.max_interval)
70 }
71
72 fn calculate_position_factor(&self, progress: f64) -> f64 {
74 let normalized_progress = progress.clamp(0.0, 1.0);
77 let middle_distance = (0.5 - normalized_progress).abs() * 2.0; 0.5 + 0.5 * middle_distance
81 }
82
83 fn calculate_stability_factor(&self) -> f64 {
85 if self.processing_speeds.len() < 2 {
86 return 1.0;
87 }
88
89 let mean: f64 =
91 self.processing_speeds.iter().sum::<f64>() / self.processing_speeds.len() as f64;
92
93 if mean <= 0.0 {
94 return 1.0;
95 }
96
97 let variance: f64 = self
98 .processing_speeds
99 .iter()
100 .map(|&speed| (speed - mean).powi(2))
101 .sum::<f64>()
102 / self.processing_speeds.len() as f64;
103
104 let std_dev = variance.sqrt();
105 let cv = std_dev / mean; (1.0 + cv).clamp(0.5, 2.0)
111 }
112
113 pub fn average_speed(&self) -> f64 {
115 if self.processing_speeds.is_empty() {
116 return 0.0;
117 }
118
119 self.processing_speeds.iter().sum::<f64>() / self.processing_speeds.len() as f64
120 }
121
122 pub fn speed_trend(&self) -> SpeedTrend {
124 if self.processing_speeds.len() < 3 {
125 return SpeedTrend::Stable;
126 }
127
128 let recent_half = self.processing_speeds.len() / 2;
129 let early_speeds: f64 =
130 self.processing_speeds.iter().take(recent_half).sum::<f64>() / recent_half as f64;
131
132 let late_speeds: f64 = self.processing_speeds.iter().skip(recent_half).sum::<f64>()
133 / (self.processing_speeds.len() - recent_half) as f64;
134
135 let change_ratio = if early_speeds > 0.0 {
136 late_speeds / early_speeds
137 } else {
138 1.0
139 };
140
141 if change_ratio > 1.1 {
142 SpeedTrend::Increasing
143 } else if change_ratio < 0.9 {
144 SpeedTrend::Decreasing
145 } else {
146 SpeedTrend::Stable
147 }
148 }
149}
150
151#[derive(Debug, Clone, Copy, PartialEq)]
153pub enum SpeedTrend {
154 Increasing,
156 Decreasing,
158 Stable,
160}
161
162pub struct PredictiveETA {
164 progress_history: VecDeque<(Instant, u64)>,
166 speed_history: VecDeque<f64>,
168 max_history: usize,
170}
171
172impl PredictiveETA {
173 pub fn new() -> Self {
175 Self {
176 progress_history: VecDeque::with_capacity(50),
177 speed_history: VecDeque::with_capacity(50),
178 max_history: 50,
179 }
180 }
181
182 pub fn record_progress(&mut self, time: Instant, processed: u64, speed: f64) {
184 self.progress_history.push_back((time, processed));
185 self.speed_history.push_back(speed);
186
187 if self.progress_history.len() > self.max_history {
189 self.progress_history.pop_front();
190 self.speed_history.pop_front();
191 }
192 }
193
194 pub fn calculate_eta(&self, currentprocessed: u64, total: u64) -> Duration {
196 if currentprocessed >= total || self.progress_history.len() < 2 {
197 return Duration::from_secs(0);
198 }
199
200 let remaining = total - currentprocessed;
201
202 let linear_eta = self.linear_regression_eta(remaining);
204
205 let smoothed_eta = self.exponential_smoothing_eta(remaining);
207
208 let average_eta = self.moving_average_eta(remaining);
210
211 let weights = self.calculate_method_weights();
213
214 let combined_seconds = linear_eta.as_secs_f64() * weights.0
215 + smoothed_eta.as_secs_f64() * weights.1
216 + average_eta.as_secs_f64() * weights.2;
217
218 Duration::from_secs_f64(combined_seconds.max(0.0))
219 }
220
221 fn linear_regression_eta(&self, remaining: u64) -> Duration {
223 if self.progress_history.len() < 3 {
224 return self.moving_average_eta(remaining);
225 }
226
227 let n = self.progress_history.len() as f64;
229 let start_time = self.progress_history[0].0;
230
231 let (sum_x, sum_y, sum_xy, sum_x2) = self.progress_history.iter().fold(
232 (0.0, 0.0, 0.0, 0.0),
233 |(sx, sy, sxy, sx2), &(time, progress)| {
234 let x = time.duration_since(start_time).as_secs_f64();
235 let y = progress as f64;
236 (sx + x, sy + y, sxy + x * y, sx2 + x * x)
237 },
238 );
239
240 let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x);
241
242 if slope > 0.0 {
243 let remaining_time = remaining as f64 / slope;
244 Duration::from_secs_f64(remaining_time)
245 } else {
246 self.moving_average_eta(remaining)
247 }
248 }
249
250 fn exponential_smoothing_eta(&self, remaining: u64) -> Duration {
252 if self.speed_history.is_empty() {
253 return Duration::from_secs(0);
254 }
255
256 let alpha = 0.3; let mut smoothed_speed = self.speed_history[0];
259
260 for &speed in self.speed_history.iter().skip(1) {
261 smoothed_speed = alpha * speed + (1.0 - alpha) * smoothed_speed;
262 }
263
264 if smoothed_speed > 0.0 {
265 Duration::from_secs_f64(remaining as f64 / smoothed_speed)
266 } else {
267 Duration::from_secs(0)
268 }
269 }
270
271 fn moving_average_eta(&self, remaining: u64) -> Duration {
273 if self.speed_history.is_empty() {
274 return Duration::from_secs(0);
275 }
276
277 let recent_count = (self.speed_history.len() / 2).clamp(1, 10);
278 let recent_speeds: Vec<_> = self.speed_history.iter().rev().take(recent_count).collect();
279
280 let avg_speed: f64 =
281 recent_speeds.iter().map(|&&s| s).sum::<f64>() / recent_speeds.len() as f64;
282
283 if avg_speed > 0.0 {
284 Duration::from_secs_f64(remaining as f64 / avg_speed)
285 } else {
286 Duration::from_secs(0)
287 }
288 }
289
290 fn calculate_method_weights(&self) -> (f64, f64, f64) {
292 let history_len = self.progress_history.len();
293
294 if history_len < 3 {
295 (0.0, 0.4, 0.6)
297 } else if history_len < 10 {
298 (0.3, 0.4, 0.3)
300 } else {
301 (0.5, 0.3, 0.2)
303 }
304 }
305}
306
307impl Default for PredictiveETA {
308 fn default() -> Self {
309 Self::new()
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316
317 #[test]
318 fn test_adaptive_controller_creation() {
319 let controller =
320 AdaptiveController::new(Duration::from_millis(100), Duration::from_secs(1));
321
322 assert_eq!(controller.min_interval, Duration::from_millis(100));
323 assert_eq!(controller.max_interval, Duration::from_secs(1));
324 }
325
326 #[test]
327 fn test_adaptive_controller_position_factor() {
328 let controller =
329 AdaptiveController::new(Duration::from_millis(100), Duration::from_secs(1));
330
331 let start_factor = controller.calculate_position_factor(0.0);
333 let middle_factor = controller.calculate_position_factor(0.5);
334 let end_factor = controller.calculate_position_factor(1.0);
335
336 assert!(start_factor > middle_factor);
337 assert!(end_factor > middle_factor);
338 assert!((start_factor - end_factor).abs() < 0.1);
339 }
340
341 #[test]
342 fn test_speed_trend_detection() {
343 let mut controller =
344 AdaptiveController::new(Duration::from_millis(100), Duration::from_secs(1));
345
346 for i in 1..=10 {
348 controller.record_update(Duration::from_millis(100), i as f64 * 10.0);
349 }
350
351 assert_eq!(controller.speed_trend(), SpeedTrend::Increasing);
352 }
353
354 #[test]
355 fn test_predictive_eta() {
356 let mut eta_calc = PredictiveETA::new();
357 let start_time = Instant::now();
358
359 for i in 1..=10 {
361 let time = start_time + Duration::from_secs(i);
362 eta_calc.record_progress(time, i * 10, 10.0);
363 }
364
365 let eta = eta_calc.calculate_eta(50, 100);
366
367 assert!((eta.as_secs_f64() - 5.0).abs() < 2.0);
369 }
370}