use serde::{Deserialize, Serialize};
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy)]
pub struct ResourcePressure {
pub cpu_usage: f64,
pub memory_usage: f64,
pub disk_usage: f64,
pub bandwidth_usage: f64,
}
impl ResourcePressure {
#[must_use]
#[inline]
pub fn overall_score(&self) -> f64 {
(self.cpu_usage * 0.2
+ self.memory_usage * 0.3
+ self.disk_usage * 0.3
+ self.bandwidth_usage * 0.2)
.clamp(0.0, 1.0)
}
#[must_use]
#[inline]
pub fn has_critical_resource(&self) -> bool {
self.cpu_usage > 0.95
|| self.memory_usage > 0.95
|| self.disk_usage > 0.95
|| self.bandwidth_usage > 0.95
}
}
impl Default for ResourcePressure {
fn default() -> Self {
Self {
cpu_usage: 0.0,
memory_usage: 0.0,
disk_usage: 0.0,
bandwidth_usage: 0.0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum ServiceDegradationLevel {
Normal = 0,
LightDegradation = 1,
ModerateDegradation = 2,
SevereDegradation = 3,
}
impl ServiceDegradationLevel {
#[must_use]
#[inline]
pub fn from_pressure_score(score: f64) -> Self {
if score < 0.70 {
Self::Normal
} else if score < 0.80 {
Self::LightDegradation
} else if score < 0.90 {
Self::ModerateDegradation
} else {
Self::SevereDegradation
}
}
#[must_use]
#[inline]
pub const fn description(&self) -> &'static str {
match self {
Self::Normal => "Operating normally",
Self::LightDegradation => "Light resource pressure - reducing non-critical features",
Self::ModerateDegradation => "Moderate resource pressure - core functionality only",
Self::SevereDegradation => "Severe resource pressure - minimal operations",
}
}
#[must_use]
#[inline]
pub const fn is_degraded(&self) -> bool {
!matches!(self, Self::Normal)
}
}
#[derive(Debug, Clone, Copy)]
pub struct DegradationActions {
pub disable_prefetching: bool,
pub reduce_cache_size: bool,
pub disable_analytics: bool,
pub throttle_bandwidth: bool,
pub pause_gc: bool,
pub disable_backups: bool,
pub reduce_connection_pool: bool,
pub reject_new_pins: bool,
}
impl DegradationActions {
#[must_use]
pub const fn for_level(level: ServiceDegradationLevel) -> Self {
match level {
ServiceDegradationLevel::Normal => Self {
disable_prefetching: false,
reduce_cache_size: false,
disable_analytics: false,
throttle_bandwidth: false,
pause_gc: false,
disable_backups: false,
reduce_connection_pool: false,
reject_new_pins: false,
},
ServiceDegradationLevel::LightDegradation => Self {
disable_prefetching: true,
reduce_cache_size: true,
disable_analytics: false,
throttle_bandwidth: false,
pause_gc: false,
disable_backups: false,
reduce_connection_pool: false,
reject_new_pins: false,
},
ServiceDegradationLevel::ModerateDegradation => Self {
disable_prefetching: true,
reduce_cache_size: true,
disable_analytics: true,
throttle_bandwidth: true,
pause_gc: true,
disable_backups: true,
reduce_connection_pool: true,
reject_new_pins: false,
},
ServiceDegradationLevel::SevereDegradation => Self {
disable_prefetching: true,
reduce_cache_size: true,
disable_analytics: true,
throttle_bandwidth: true,
pause_gc: true,
disable_backups: true,
reduce_connection_pool: true,
reject_new_pins: true,
},
}
}
}
pub struct DegradationManager {
current_level: ServiceDegradationLevel,
current_pressure: ResourcePressure,
last_update: Instant,
pressure_history: Vec<(Instant, f64)>,
hysteresis_duration: Duration,
}
impl DegradationManager {
#[must_use]
pub fn new() -> Self {
Self {
current_level: ServiceDegradationLevel::Normal,
current_pressure: ResourcePressure::default(),
last_update: Instant::now(),
pressure_history: Vec::new(),
hysteresis_duration: Duration::from_secs(60), }
}
pub fn update_pressure(&mut self, pressure: ResourcePressure) {
self.current_pressure = pressure;
self.last_update = Instant::now();
let score = pressure.overall_score();
self.pressure_history.push((Instant::now(), score));
let cutoff = Instant::now() - Duration::from_secs(300);
self.pressure_history.retain(|(t, _)| *t > cutoff);
let new_level = ServiceDegradationLevel::from_pressure_score(score);
if new_level != self.current_level {
let sustained = self.is_level_sustained(new_level);
if sustained {
self.current_level = new_level;
}
}
}
fn is_level_sustained(&self, level: ServiceDegradationLevel) -> bool {
let cutoff = Instant::now() - self.hysteresis_duration;
let recent_scores: Vec<f64> = self
.pressure_history
.iter()
.filter(|(t, _)| *t > cutoff)
.map(|(_, s)| *s)
.collect();
if recent_scores.is_empty() {
return false;
}
recent_scores
.iter()
.all(|&score| ServiceDegradationLevel::from_pressure_score(score) == level)
}
#[must_use]
#[inline]
pub const fn current_level(&self) -> ServiceDegradationLevel {
self.current_level
}
#[must_use]
#[inline]
pub const fn current_pressure(&self) -> &ResourcePressure {
&self.current_pressure
}
#[must_use]
pub const fn get_actions(&self) -> DegradationActions {
DegradationActions::for_level(self.current_level)
}
#[must_use]
#[inline]
pub fn should_disable_prefetching(&self) -> bool {
self.get_actions().disable_prefetching
}
#[must_use]
#[inline]
pub fn should_reduce_cache_size(&self) -> bool {
self.get_actions().reduce_cache_size
}
#[must_use]
#[inline]
pub fn should_disable_analytics(&self) -> bool {
self.get_actions().disable_analytics
}
#[must_use]
#[inline]
pub fn should_throttle_bandwidth(&self) -> bool {
self.get_actions().throttle_bandwidth
}
#[must_use]
#[inline]
pub fn should_pause_gc(&self) -> bool {
self.get_actions().pause_gc
}
#[must_use]
#[inline]
pub fn should_reject_new_pins(&self) -> bool {
self.get_actions().reject_new_pins
}
#[must_use]
pub fn time_since_update(&self) -> Duration {
Instant::now().duration_since(self.last_update)
}
#[must_use]
pub fn average_pressure_score(&self, duration: Duration) -> Option<f64> {
let cutoff = Instant::now() - duration;
let scores: Vec<f64> = self
.pressure_history
.iter()
.filter(|(t, _)| *t > cutoff)
.map(|(_, s)| *s)
.collect();
if scores.is_empty() {
None
} else {
Some(scores.iter().sum::<f64>() / scores.len() as f64)
}
}
}
impl Default for DegradationManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resource_pressure_overall_score() {
let pressure = ResourcePressure {
cpu_usage: 0.5,
memory_usage: 0.6,
disk_usage: 0.7,
bandwidth_usage: 0.4,
};
let score = pressure.overall_score();
assert!(score > 0.5 && score < 0.7);
}
#[test]
fn test_resource_pressure_critical() {
let pressure = ResourcePressure {
cpu_usage: 0.96,
memory_usage: 0.5,
disk_usage: 0.5,
bandwidth_usage: 0.5,
};
assert!(pressure.has_critical_resource());
let normal = ResourcePressure {
cpu_usage: 0.5,
memory_usage: 0.5,
disk_usage: 0.5,
bandwidth_usage: 0.5,
};
assert!(!normal.has_critical_resource());
}
#[test]
fn test_degradation_level_from_score() {
assert_eq!(
ServiceDegradationLevel::from_pressure_score(0.5),
ServiceDegradationLevel::Normal
);
assert_eq!(
ServiceDegradationLevel::from_pressure_score(0.75),
ServiceDegradationLevel::LightDegradation
);
assert_eq!(
ServiceDegradationLevel::from_pressure_score(0.85),
ServiceDegradationLevel::ModerateDegradation
);
assert_eq!(
ServiceDegradationLevel::from_pressure_score(0.95),
ServiceDegradationLevel::SevereDegradation
);
}
#[test]
fn test_degradation_actions() {
let normal_actions = DegradationActions::for_level(ServiceDegradationLevel::Normal);
assert!(!normal_actions.disable_prefetching);
assert!(!normal_actions.reject_new_pins);
let severe_actions =
DegradationActions::for_level(ServiceDegradationLevel::SevereDegradation);
assert!(severe_actions.disable_prefetching);
assert!(severe_actions.reject_new_pins);
}
#[test]
fn test_degradation_manager_update() {
let mut manager = DegradationManager::new();
assert_eq!(manager.current_level(), ServiceDegradationLevel::Normal);
manager.update_pressure(ResourcePressure {
cpu_usage: 0.95,
memory_usage: 0.90,
disk_usage: 0.92,
bandwidth_usage: 0.88,
});
assert!(manager.current_pressure().overall_score() > 0.9);
}
#[test]
fn test_degradation_manager_helpers() {
let mut manager = DegradationManager::new();
assert!(!manager.should_disable_prefetching());
assert!(!manager.should_reject_new_pins());
manager.current_level = ServiceDegradationLevel::SevereDegradation;
assert!(manager.should_disable_prefetching());
assert!(manager.should_reject_new_pins());
assert!(manager.should_pause_gc());
}
#[test]
fn test_average_pressure_score() {
let mut manager = DegradationManager::new();
manager.update_pressure(ResourcePressure {
cpu_usage: 0.5,
memory_usage: 0.5,
disk_usage: 0.5,
bandwidth_usage: 0.5,
});
let avg = manager.average_pressure_score(Duration::from_secs(60));
assert!(avg.is_some());
assert!((avg.unwrap() - 0.5).abs() < 0.1);
}
}