use parking_lot::RwLock;
use std::sync::Arc;
use std::time::{Duration, Instant};
use thiserror::Error;
#[derive(Error, Debug, Clone)]
pub enum AdaptivePollingError {
#[error("Invalid polling configuration: {0}")]
InvalidConfig(String),
#[error("Polling interval adjustment failed: {0}")]
AdjustmentFailed(String),
}
#[derive(Debug, Clone)]
pub struct AdaptivePollingConfig {
pub min_interval: Duration,
pub max_interval: Duration,
pub default_interval: Duration,
pub idle_increase_factor: f64,
pub active_decrease_factor: f64,
pub idle_threshold: Duration,
pub enable_sleep_mode: bool,
pub sleep_interval: Duration,
pub sleep_threshold: Duration,
}
impl Default for AdaptivePollingConfig {
fn default() -> Self {
Self {
min_interval: Duration::from_millis(50), max_interval: Duration::from_secs(5), default_interval: Duration::from_millis(500), idle_increase_factor: 1.5, active_decrease_factor: 0.7, idle_threshold: Duration::from_secs(10), enable_sleep_mode: true,
sleep_interval: Duration::from_secs(30), sleep_threshold: Duration::from_secs(60), }
}
}
impl AdaptivePollingConfig {
pub fn mobile() -> Self {
Self {
min_interval: Duration::from_millis(100),
max_interval: Duration::from_secs(10),
default_interval: Duration::from_secs(1),
idle_increase_factor: 2.0, active_decrease_factor: 0.6,
idle_threshold: Duration::from_secs(5),
enable_sleep_mode: true,
sleep_interval: Duration::from_secs(60),
sleep_threshold: Duration::from_secs(30),
}
}
pub fn iot() -> Self {
Self {
min_interval: Duration::from_millis(200),
max_interval: Duration::from_secs(30),
default_interval: Duration::from_secs(2),
idle_increase_factor: 2.5, active_decrease_factor: 0.5,
idle_threshold: Duration::from_secs(5),
enable_sleep_mode: true,
sleep_interval: Duration::from_secs(120), sleep_threshold: Duration::from_secs(20),
}
}
pub fn low_power() -> Self {
Self {
min_interval: Duration::from_millis(500),
max_interval: Duration::from_secs(60),
default_interval: Duration::from_secs(5),
idle_increase_factor: 3.0, active_decrease_factor: 0.5,
idle_threshold: Duration::from_secs(3),
enable_sleep_mode: true,
sleep_interval: Duration::from_secs(300), sleep_threshold: Duration::from_secs(15),
}
}
pub fn high_performance() -> Self {
Self {
min_interval: Duration::from_millis(10),
max_interval: Duration::from_millis(500),
default_interval: Duration::from_millis(50),
idle_increase_factor: 1.2, active_decrease_factor: 0.9,
idle_threshold: Duration::from_secs(30),
enable_sleep_mode: false, sleep_interval: Duration::from_secs(1),
sleep_threshold: Duration::from_secs(600),
}
}
pub fn validate(&self) -> Result<(), AdaptivePollingError> {
if self.min_interval.is_zero() {
return Err(AdaptivePollingError::InvalidConfig(
"Minimum interval must be > 0".to_string(),
));
}
if self.max_interval < self.min_interval {
return Err(AdaptivePollingError::InvalidConfig(
"Maximum interval must be >= minimum interval".to_string(),
));
}
if self.default_interval < self.min_interval || self.default_interval > self.max_interval {
return Err(AdaptivePollingError::InvalidConfig(
"Default interval must be between min and max".to_string(),
));
}
if self.idle_increase_factor < 1.0 {
return Err(AdaptivePollingError::InvalidConfig(
"Idle increase factor must be >= 1.0".to_string(),
));
}
if self.active_decrease_factor <= 0.0 || self.active_decrease_factor > 1.0 {
return Err(AdaptivePollingError::InvalidConfig(
"Active decrease factor must be in (0.0, 1.0]".to_string(),
));
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ActivityLevel {
High,
Moderate,
Low,
Idle,
Sleep,
}
#[derive(Debug)]
struct PollingState {
current_interval: Duration,
activity_level: ActivityLevel,
last_activity: Instant,
events_in_window: u64,
window_start: Instant,
adjustments_made: u64,
}
impl PollingState {
fn new(default_interval: Duration) -> Self {
let now = Instant::now();
Self {
current_interval: default_interval,
activity_level: ActivityLevel::Moderate,
last_activity: now,
events_in_window: 0,
window_start: now,
adjustments_made: 0,
}
}
}
pub struct AdaptivePolling {
config: AdaptivePollingConfig,
state: Arc<RwLock<PollingState>>,
}
impl AdaptivePolling {
pub fn new(config: AdaptivePollingConfig) -> Result<Self, AdaptivePollingError> {
config.validate()?;
let state = PollingState::new(config.default_interval);
Ok(Self {
config: config.clone(),
state: Arc::new(RwLock::new(state)),
})
}
pub fn record_activity(&self) {
let mut state = self.state.write();
let now = Instant::now();
state.last_activity = now;
state.events_in_window += 1;
if now.duration_since(state.window_start) >= Duration::from_secs(1) {
let events_per_sec = state.events_in_window;
state.activity_level = if events_per_sec >= 10 {
ActivityLevel::High
} else if events_per_sec >= 3 {
ActivityLevel::Moderate
} else if events_per_sec >= 1 {
ActivityLevel::Low
} else {
ActivityLevel::Idle
};
state.events_in_window = 0;
state.window_start = now;
}
}
pub fn adjust_interval(&self) {
let mut state = self.state.write();
let now = Instant::now();
let time_since_activity = now.duration_since(state.last_activity);
if self.config.enable_sleep_mode && time_since_activity >= self.config.sleep_threshold {
state.activity_level = ActivityLevel::Sleep;
state.current_interval = self.config.sleep_interval;
state.adjustments_made += 1;
return;
}
if time_since_activity >= self.config.idle_threshold {
state.activity_level = ActivityLevel::Idle;
let new_interval = Duration::from_secs_f64(
state.current_interval.as_secs_f64() * self.config.idle_increase_factor,
);
state.current_interval = new_interval.min(self.config.max_interval);
state.adjustments_made += 1;
return;
}
match state.activity_level {
ActivityLevel::High => {
let new_interval = Duration::from_secs_f64(
state.current_interval.as_secs_f64() * self.config.active_decrease_factor,
);
state.current_interval = new_interval.max(self.config.min_interval);
state.adjustments_made += 1;
}
ActivityLevel::Moderate => {
let diff = state.current_interval.as_secs_f64()
- self.config.default_interval.as_secs_f64();
if diff.abs() > 0.1 {
let new_interval =
Duration::from_secs_f64(state.current_interval.as_secs_f64() - diff * 0.1);
state.current_interval = new_interval
.max(self.config.min_interval)
.min(self.config.max_interval);
state.adjustments_made += 1;
}
}
ActivityLevel::Low => {
let new_interval = Duration::from_secs_f64(
state.current_interval.as_secs_f64() * self.config.idle_increase_factor * 0.5,
);
state.current_interval = new_interval.min(self.config.max_interval);
state.adjustments_made += 1;
}
ActivityLevel::Idle | ActivityLevel::Sleep => {
}
}
}
pub fn current_interval(&self) -> Duration {
self.state.read().current_interval
}
pub fn activity_level(&self) -> ActivityLevel {
self.state.read().activity_level
}
pub fn time_since_activity(&self) -> Duration {
let state = self.state.read();
Instant::now().duration_since(state.last_activity)
}
pub fn reset(&self) {
let mut state = self.state.write();
state.current_interval = self.config.default_interval;
state.activity_level = ActivityLevel::Moderate;
state.last_activity = Instant::now();
state.events_in_window = 0;
state.window_start = Instant::now();
}
pub fn stats(&self) -> AdaptivePollingStats {
let state = self.state.read();
AdaptivePollingStats {
current_interval: state.current_interval,
activity_level: state.activity_level,
time_since_activity: Instant::now().duration_since(state.last_activity),
events_in_window: state.events_in_window,
adjustments_made: state.adjustments_made,
}
}
}
#[derive(Debug, Clone)]
pub struct AdaptivePollingStats {
pub current_interval: Duration,
pub activity_level: ActivityLevel,
pub time_since_activity: Duration,
pub events_in_window: u64,
pub adjustments_made: u64,
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_config_default() {
let config = AdaptivePollingConfig::default();
assert!(config.validate().is_ok());
assert_eq!(config.min_interval, Duration::from_millis(50));
}
#[test]
fn test_config_mobile() {
let config = AdaptivePollingConfig::mobile();
assert!(config.validate().is_ok());
assert!(config.enable_sleep_mode);
}
#[test]
fn test_config_iot() {
let config = AdaptivePollingConfig::iot();
assert!(config.validate().is_ok());
assert_eq!(config.max_interval, Duration::from_secs(30));
}
#[test]
fn test_config_low_power() {
let config = AdaptivePollingConfig::low_power();
assert!(config.validate().is_ok());
assert_eq!(config.sleep_interval, Duration::from_secs(300));
}
#[test]
fn test_config_high_performance() {
let config = AdaptivePollingConfig::high_performance();
assert!(config.validate().is_ok());
assert!(!config.enable_sleep_mode);
}
#[test]
fn test_config_validation_min_interval() {
let config = AdaptivePollingConfig {
min_interval: Duration::from_secs(0),
..Default::default()
};
assert!(config.validate().is_err());
}
#[test]
fn test_config_validation_max_less_than_min() {
let config = AdaptivePollingConfig {
max_interval: Duration::from_millis(10),
min_interval: Duration::from_millis(100),
..Default::default()
};
assert!(config.validate().is_err());
}
#[test]
fn test_polling_new() {
let config = AdaptivePollingConfig::default();
let polling =
AdaptivePolling::new(config.clone()).expect("test: failed to create AdaptivePolling");
assert_eq!(polling.current_interval(), config.default_interval);
assert_eq!(polling.activity_level(), ActivityLevel::Moderate);
}
#[test]
fn test_record_activity() {
let config = AdaptivePollingConfig::default();
let polling = AdaptivePolling::new(config).expect("test: failed to create AdaptivePolling");
polling.record_activity();
assert!(polling.time_since_activity() < Duration::from_millis(100));
}
#[test]
fn test_adjust_interval_idle() {
let config = AdaptivePollingConfig {
idle_threshold: Duration::from_millis(100),
..Default::default()
};
let polling =
AdaptivePolling::new(config.clone()).expect("test: failed to create AdaptivePolling");
thread::sleep(Duration::from_millis(150));
polling.adjust_interval();
let new_interval = polling.current_interval();
assert!(new_interval > config.default_interval);
}
#[test]
fn test_adjust_interval_active() {
let config = AdaptivePollingConfig::default();
let polling = AdaptivePolling::new(config.clone())
.expect("test: failed to create AdaptivePolling for adjust_interval_active");
for _ in 0..15 {
polling.record_activity();
}
thread::sleep(Duration::from_secs(1));
polling.record_activity();
polling.adjust_interval();
let new_interval = polling.current_interval();
assert!(new_interval < config.default_interval);
}
#[test]
fn test_sleep_mode() {
let config = AdaptivePollingConfig {
sleep_threshold: Duration::from_millis(100),
enable_sleep_mode: true,
..Default::default()
};
let polling = AdaptivePolling::new(config.clone())
.expect("test: failed to create AdaptivePolling for sleep_mode");
thread::sleep(Duration::from_millis(150));
polling.adjust_interval();
assert_eq!(polling.activity_level(), ActivityLevel::Sleep);
assert_eq!(polling.current_interval(), config.sleep_interval);
}
#[test]
fn test_reset() {
let config = AdaptivePollingConfig::default();
let polling = AdaptivePolling::new(config.clone())
.expect("test: failed to create AdaptivePolling for reset");
thread::sleep(Duration::from_millis(100));
polling.adjust_interval();
polling.reset();
assert_eq!(polling.current_interval(), config.default_interval);
assert_eq!(polling.activity_level(), ActivityLevel::Moderate);
}
#[test]
fn test_stats() {
let config = AdaptivePollingConfig::default();
let polling =
AdaptivePolling::new(config).expect("test: failed to create AdaptivePolling for stats");
polling.record_activity();
let stats = polling.stats();
assert!(stats.time_since_activity < Duration::from_millis(100));
assert_eq!(stats.activity_level, ActivityLevel::Moderate);
}
#[test]
fn test_interval_bounds() {
let config = AdaptivePollingConfig::default();
let polling = AdaptivePolling::new(config.clone())
.expect("test: failed to create AdaptivePolling for interval_bounds");
for _ in 0..100 {
polling.adjust_interval();
}
let interval = polling.current_interval();
assert!(interval >= config.min_interval);
assert!(interval <= config.max_interval);
}
}