ipfrs_network/
adaptive_polling.rs

1//! Adaptive polling intervals for power-efficient network operations
2//!
3//! This module provides dynamic adjustment of polling intervals based on network activity.
4//! It's designed to reduce power consumption on edge devices and mobile platforms by:
5//! - Increasing poll intervals during idle periods
6//! - Decreasing intervals during active periods
7//! - Supporting sleep mode detection
8//! - Providing activity-based adjustments
9
10use parking_lot::RwLock;
11use std::sync::Arc;
12use std::time::{Duration, Instant};
13use thiserror::Error;
14
15/// Errors that can occur during adaptive polling operations
16#[derive(Error, Debug, Clone)]
17pub enum AdaptivePollingError {
18    #[error("Invalid polling configuration: {0}")]
19    InvalidConfig(String),
20
21    #[error("Polling interval adjustment failed: {0}")]
22    AdjustmentFailed(String),
23}
24
25/// Configuration for adaptive polling
26#[derive(Debug, Clone)]
27pub struct AdaptivePollingConfig {
28    /// Minimum poll interval (fastest polling rate)
29    pub min_interval: Duration,
30
31    /// Maximum poll interval (slowest polling rate)
32    pub max_interval: Duration,
33
34    /// Default poll interval when activity is moderate
35    pub default_interval: Duration,
36
37    /// Interval increase factor when idle (multiplier)
38    pub idle_increase_factor: f64,
39
40    /// Interval decrease factor when active (multiplier)
41    pub active_decrease_factor: f64,
42
43    /// Time threshold to consider network idle
44    pub idle_threshold: Duration,
45
46    /// Enable sleep mode when completely inactive
47    pub enable_sleep_mode: bool,
48
49    /// Sleep mode interval (very long poll interval)
50    pub sleep_interval: Duration,
51
52    /// Time threshold to enter sleep mode
53    pub sleep_threshold: Duration,
54}
55
56impl Default for AdaptivePollingConfig {
57    fn default() -> Self {
58        Self {
59            min_interval: Duration::from_millis(50), // 50ms minimum (20 Hz max)
60            max_interval: Duration::from_secs(5),    // 5s maximum
61            default_interval: Duration::from_millis(500), // 500ms default (2 Hz)
62            idle_increase_factor: 1.5,               // 1.5x slower when idle
63            active_decrease_factor: 0.7,             // 0.7x faster when active
64            idle_threshold: Duration::from_secs(10), // 10s without activity = idle
65            enable_sleep_mode: true,
66            sleep_interval: Duration::from_secs(30), // 30s in sleep mode
67            sleep_threshold: Duration::from_secs(60), // 60s without activity = sleep
68        }
69    }
70}
71
72impl AdaptivePollingConfig {
73    /// Configuration for mobile devices (battery-conscious)
74    pub fn mobile() -> Self {
75        Self {
76            min_interval: Duration::from_millis(100),
77            max_interval: Duration::from_secs(10),
78            default_interval: Duration::from_secs(1),
79            idle_increase_factor: 2.0, // More aggressive idle slowdown
80            active_decrease_factor: 0.6,
81            idle_threshold: Duration::from_secs(5),
82            enable_sleep_mode: true,
83            sleep_interval: Duration::from_secs(60),
84            sleep_threshold: Duration::from_secs(30),
85        }
86    }
87
88    /// Configuration for IoT/edge devices (power-saving)
89    pub fn iot() -> Self {
90        Self {
91            min_interval: Duration::from_millis(200),
92            max_interval: Duration::from_secs(30),
93            default_interval: Duration::from_secs(2),
94            idle_increase_factor: 2.5, // Very aggressive idle slowdown
95            active_decrease_factor: 0.5,
96            idle_threshold: Duration::from_secs(5),
97            enable_sleep_mode: true,
98            sleep_interval: Duration::from_secs(120), // 2 minutes
99            sleep_threshold: Duration::from_secs(20),
100        }
101    }
102
103    /// Configuration for low-power mode (maximum battery saving)
104    pub fn low_power() -> Self {
105        Self {
106            min_interval: Duration::from_millis(500),
107            max_interval: Duration::from_secs(60),
108            default_interval: Duration::from_secs(5),
109            idle_increase_factor: 3.0, // Very aggressive
110            active_decrease_factor: 0.5,
111            idle_threshold: Duration::from_secs(3),
112            enable_sleep_mode: true,
113            sleep_interval: Duration::from_secs(300), // 5 minutes
114            sleep_threshold: Duration::from_secs(15),
115        }
116    }
117
118    /// Configuration for high-performance mode (minimal latency)
119    pub fn high_performance() -> Self {
120        Self {
121            min_interval: Duration::from_millis(10),
122            max_interval: Duration::from_millis(500),
123            default_interval: Duration::from_millis(50),
124            idle_increase_factor: 1.2, // Gentle slowdown
125            active_decrease_factor: 0.9,
126            idle_threshold: Duration::from_secs(30),
127            enable_sleep_mode: false, // No sleep mode
128            sleep_interval: Duration::from_secs(1),
129            sleep_threshold: Duration::from_secs(600),
130        }
131    }
132
133    /// Validate the configuration
134    pub fn validate(&self) -> Result<(), AdaptivePollingError> {
135        if self.min_interval.is_zero() {
136            return Err(AdaptivePollingError::InvalidConfig(
137                "Minimum interval must be > 0".to_string(),
138            ));
139        }
140
141        if self.max_interval < self.min_interval {
142            return Err(AdaptivePollingError::InvalidConfig(
143                "Maximum interval must be >= minimum interval".to_string(),
144            ));
145        }
146
147        if self.default_interval < self.min_interval || self.default_interval > self.max_interval {
148            return Err(AdaptivePollingError::InvalidConfig(
149                "Default interval must be between min and max".to_string(),
150            ));
151        }
152
153        if self.idle_increase_factor < 1.0 {
154            return Err(AdaptivePollingError::InvalidConfig(
155                "Idle increase factor must be >= 1.0".to_string(),
156            ));
157        }
158
159        if self.active_decrease_factor <= 0.0 || self.active_decrease_factor > 1.0 {
160            return Err(AdaptivePollingError::InvalidConfig(
161                "Active decrease factor must be in (0.0, 1.0]".to_string(),
162            ));
163        }
164
165        Ok(())
166    }
167}
168
169/// Activity level for adaptive polling
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub enum ActivityLevel {
172    /// High activity (many events)
173    High,
174    /// Moderate activity
175    Moderate,
176    /// Low activity (few events)
177    Low,
178    /// Idle (no recent events)
179    Idle,
180    /// Sleep mode (completely inactive)
181    Sleep,
182}
183
184/// State of the adaptive polling mechanism
185#[derive(Debug)]
186struct PollingState {
187    /// Current poll interval
188    current_interval: Duration,
189
190    /// Current activity level
191    activity_level: ActivityLevel,
192
193    /// Last activity timestamp
194    last_activity: Instant,
195
196    /// Number of events in current window
197    events_in_window: u64,
198
199    /// Window start time
200    window_start: Instant,
201
202    /// Total adjustments made
203    adjustments_made: u64,
204}
205
206impl PollingState {
207    fn new(default_interval: Duration) -> Self {
208        let now = Instant::now();
209        Self {
210            current_interval: default_interval,
211            activity_level: ActivityLevel::Moderate,
212            last_activity: now,
213            events_in_window: 0,
214            window_start: now,
215            adjustments_made: 0,
216        }
217    }
218}
219
220/// Adaptive polling manager
221pub struct AdaptivePolling {
222    config: AdaptivePollingConfig,
223    state: Arc<RwLock<PollingState>>,
224}
225
226impl AdaptivePolling {
227    /// Create a new adaptive polling manager
228    pub fn new(config: AdaptivePollingConfig) -> Result<Self, AdaptivePollingError> {
229        config.validate()?;
230
231        let state = PollingState::new(config.default_interval);
232
233        Ok(Self {
234            config: config.clone(),
235            state: Arc::new(RwLock::new(state)),
236        })
237    }
238
239    /// Record network activity (call this when events occur)
240    pub fn record_activity(&self) {
241        let mut state = self.state.write();
242        let now = Instant::now();
243
244        state.last_activity = now;
245        state.events_in_window += 1;
246
247        // Reset window every second
248        if now.duration_since(state.window_start) >= Duration::from_secs(1) {
249            // Determine activity level based on event count
250            let events_per_sec = state.events_in_window;
251            state.activity_level = if events_per_sec >= 10 {
252                ActivityLevel::High
253            } else if events_per_sec >= 3 {
254                ActivityLevel::Moderate
255            } else if events_per_sec >= 1 {
256                ActivityLevel::Low
257            } else {
258                ActivityLevel::Idle
259            };
260
261            state.events_in_window = 0;
262            state.window_start = now;
263        }
264    }
265
266    /// Adjust poll interval based on current activity
267    pub fn adjust_interval(&self) {
268        let mut state = self.state.write();
269        let now = Instant::now();
270        let time_since_activity = now.duration_since(state.last_activity);
271
272        // Check for sleep mode
273        if self.config.enable_sleep_mode && time_since_activity >= self.config.sleep_threshold {
274            state.activity_level = ActivityLevel::Sleep;
275            state.current_interval = self.config.sleep_interval;
276            state.adjustments_made += 1;
277            return;
278        }
279
280        // Check for idle mode
281        if time_since_activity >= self.config.idle_threshold {
282            state.activity_level = ActivityLevel::Idle;
283            let new_interval = Duration::from_secs_f64(
284                state.current_interval.as_secs_f64() * self.config.idle_increase_factor,
285            );
286            state.current_interval = new_interval.min(self.config.max_interval);
287            state.adjustments_made += 1;
288            return;
289        }
290
291        // Adjust based on activity level
292        match state.activity_level {
293            ActivityLevel::High => {
294                let new_interval = Duration::from_secs_f64(
295                    state.current_interval.as_secs_f64() * self.config.active_decrease_factor,
296                );
297                state.current_interval = new_interval.max(self.config.min_interval);
298                state.adjustments_made += 1;
299            }
300            ActivityLevel::Moderate => {
301                // Gradually move towards default interval
302                let diff = state.current_interval.as_secs_f64()
303                    - self.config.default_interval.as_secs_f64();
304                if diff.abs() > 0.1 {
305                    let new_interval =
306                        Duration::from_secs_f64(state.current_interval.as_secs_f64() - diff * 0.1);
307                    state.current_interval = new_interval
308                        .max(self.config.min_interval)
309                        .min(self.config.max_interval);
310                    state.adjustments_made += 1;
311                }
312            }
313            ActivityLevel::Low => {
314                let new_interval = Duration::from_secs_f64(
315                    state.current_interval.as_secs_f64() * self.config.idle_increase_factor * 0.5,
316                );
317                state.current_interval = new_interval.min(self.config.max_interval);
318                state.adjustments_made += 1;
319            }
320            ActivityLevel::Idle | ActivityLevel::Sleep => {
321                // Already handled above
322            }
323        }
324    }
325
326    /// Get the current poll interval
327    pub fn current_interval(&self) -> Duration {
328        self.state.read().current_interval
329    }
330
331    /// Get the current activity level
332    pub fn activity_level(&self) -> ActivityLevel {
333        self.state.read().activity_level
334    }
335
336    /// Get the time since last activity
337    pub fn time_since_activity(&self) -> Duration {
338        let state = self.state.read();
339        Instant::now().duration_since(state.last_activity)
340    }
341
342    /// Reset to default interval
343    pub fn reset(&self) {
344        let mut state = self.state.write();
345        state.current_interval = self.config.default_interval;
346        state.activity_level = ActivityLevel::Moderate;
347        state.last_activity = Instant::now();
348        state.events_in_window = 0;
349        state.window_start = Instant::now();
350    }
351
352    /// Get polling statistics
353    pub fn stats(&self) -> AdaptivePollingStats {
354        let state = self.state.read();
355        AdaptivePollingStats {
356            current_interval: state.current_interval,
357            activity_level: state.activity_level,
358            time_since_activity: Instant::now().duration_since(state.last_activity),
359            events_in_window: state.events_in_window,
360            adjustments_made: state.adjustments_made,
361        }
362    }
363}
364
365/// Statistics for adaptive polling
366#[derive(Debug, Clone)]
367pub struct AdaptivePollingStats {
368    pub current_interval: Duration,
369    pub activity_level: ActivityLevel,
370    pub time_since_activity: Duration,
371    pub events_in_window: u64,
372    pub adjustments_made: u64,
373}
374
375#[cfg(test)]
376mod tests {
377    use super::*;
378    use std::thread;
379
380    #[test]
381    fn test_config_default() {
382        let config = AdaptivePollingConfig::default();
383        assert!(config.validate().is_ok());
384        assert_eq!(config.min_interval, Duration::from_millis(50));
385    }
386
387    #[test]
388    fn test_config_mobile() {
389        let config = AdaptivePollingConfig::mobile();
390        assert!(config.validate().is_ok());
391        assert!(config.enable_sleep_mode);
392    }
393
394    #[test]
395    fn test_config_iot() {
396        let config = AdaptivePollingConfig::iot();
397        assert!(config.validate().is_ok());
398        assert_eq!(config.max_interval, Duration::from_secs(30));
399    }
400
401    #[test]
402    fn test_config_low_power() {
403        let config = AdaptivePollingConfig::low_power();
404        assert!(config.validate().is_ok());
405        assert_eq!(config.sleep_interval, Duration::from_secs(300));
406    }
407
408    #[test]
409    fn test_config_high_performance() {
410        let config = AdaptivePollingConfig::high_performance();
411        assert!(config.validate().is_ok());
412        assert!(!config.enable_sleep_mode);
413    }
414
415    #[test]
416    fn test_config_validation_min_interval() {
417        let config = AdaptivePollingConfig {
418            min_interval: Duration::from_secs(0),
419            ..Default::default()
420        };
421        assert!(config.validate().is_err());
422    }
423
424    #[test]
425    fn test_config_validation_max_less_than_min() {
426        let config = AdaptivePollingConfig {
427            max_interval: Duration::from_millis(10),
428            min_interval: Duration::from_millis(100),
429            ..Default::default()
430        };
431        assert!(config.validate().is_err());
432    }
433
434    #[test]
435    fn test_polling_new() {
436        let config = AdaptivePollingConfig::default();
437        let polling = AdaptivePolling::new(config.clone()).unwrap();
438
439        assert_eq!(polling.current_interval(), config.default_interval);
440        assert_eq!(polling.activity_level(), ActivityLevel::Moderate);
441    }
442
443    #[test]
444    fn test_record_activity() {
445        let config = AdaptivePollingConfig::default();
446        let polling = AdaptivePolling::new(config).unwrap();
447
448        polling.record_activity();
449
450        assert!(polling.time_since_activity() < Duration::from_millis(100));
451    }
452
453    #[test]
454    fn test_adjust_interval_idle() {
455        let config = AdaptivePollingConfig {
456            idle_threshold: Duration::from_millis(100),
457            ..Default::default()
458        };
459        let polling = AdaptivePolling::new(config.clone()).unwrap();
460
461        // Wait to become idle
462        thread::sleep(Duration::from_millis(150));
463        polling.adjust_interval();
464
465        let new_interval = polling.current_interval();
466        assert!(new_interval > config.default_interval);
467    }
468
469    #[test]
470    fn test_adjust_interval_active() {
471        let config = AdaptivePollingConfig::default();
472        let polling = AdaptivePolling::new(config.clone()).unwrap();
473
474        // Simulate high activity
475        for _ in 0..15 {
476            polling.record_activity();
477        }
478
479        // Wait for window reset
480        thread::sleep(Duration::from_secs(1));
481        polling.record_activity();
482
483        polling.adjust_interval();
484
485        let new_interval = polling.current_interval();
486        assert!(new_interval < config.default_interval);
487    }
488
489    #[test]
490    fn test_sleep_mode() {
491        let config = AdaptivePollingConfig {
492            sleep_threshold: Duration::from_millis(100),
493            enable_sleep_mode: true,
494            ..Default::default()
495        };
496        let polling = AdaptivePolling::new(config.clone()).unwrap();
497
498        // Wait to enter sleep mode
499        thread::sleep(Duration::from_millis(150));
500        polling.adjust_interval();
501
502        assert_eq!(polling.activity_level(), ActivityLevel::Sleep);
503        assert_eq!(polling.current_interval(), config.sleep_interval);
504    }
505
506    #[test]
507    fn test_reset() {
508        let config = AdaptivePollingConfig::default();
509        let polling = AdaptivePolling::new(config.clone()).unwrap();
510
511        // Change interval
512        thread::sleep(Duration::from_millis(100));
513        polling.adjust_interval();
514
515        // Reset
516        polling.reset();
517
518        assert_eq!(polling.current_interval(), config.default_interval);
519        assert_eq!(polling.activity_level(), ActivityLevel::Moderate);
520    }
521
522    #[test]
523    fn test_stats() {
524        let config = AdaptivePollingConfig::default();
525        let polling = AdaptivePolling::new(config).unwrap();
526
527        polling.record_activity();
528
529        let stats = polling.stats();
530        assert!(stats.time_since_activity < Duration::from_millis(100));
531        assert_eq!(stats.activity_level, ActivityLevel::Moderate);
532    }
533
534    #[test]
535    fn test_interval_bounds() {
536        let config = AdaptivePollingConfig::default();
537        let polling = AdaptivePolling::new(config.clone()).unwrap();
538
539        // Try to go below min
540        for _ in 0..100 {
541            polling.adjust_interval();
542        }
543
544        let interval = polling.current_interval();
545        assert!(interval >= config.min_interval);
546        assert!(interval <= config.max_interval);
547    }
548}