use std::sync::{ Arc, Mutex };
use std::sync::atomic::Ordering;
use std::time::{ Duration, SystemTime };
use super::health::DeploymentMetrics;
#[ derive( Debug, Clone ) ]
pub struct ScalingConfig
{
pub min_instances : usize,
pub max_instances : usize,
pub target_cpu_utilization : f64,
pub target_memory_utilization : f64,
pub scale_up_cooldown : Duration,
pub scale_down_cooldown : Duration,
}
impl Default for ScalingConfig
{
fn default() -> Self
{
Self {
min_instances : 1,
max_instances : 3,
target_cpu_utilization : 70.0,
target_memory_utilization : 80.0,
scale_up_cooldown : Duration::from_secs( 300 ), scale_down_cooldown : Duration::from_secs( 600 ), }
}
}
#[ derive( Debug, Clone ) ]
pub struct ScalingConfigBuilder
{
config : ScalingConfig,
}
impl ScalingConfigBuilder
{
pub fn new() -> Self
{
Self {
config : ScalingConfig::default(),
}
}
pub fn min_instances( mut self, min : usize ) -> Self
{
self.config.min_instances = min;
self
}
pub fn max_instances( mut self, max : usize ) -> Self
{
self.config.max_instances = max;
self
}
pub fn target_cpu_utilization( mut self, cpu : f64 ) -> Self
{
self.config.target_cpu_utilization = cpu;
self
}
pub fn target_memory_utilization( mut self, memory : f64 ) -> Self
{
self.config.target_memory_utilization = memory;
self
}
pub fn scale_up_cooldown( mut self, cooldown : Duration ) -> Self
{
self.config.scale_up_cooldown = cooldown;
self
}
pub fn scale_down_cooldown( mut self, cooldown : Duration ) -> Self
{
self.config.scale_down_cooldown = cooldown;
self
}
pub fn build( self ) -> Result< ScalingConfig, crate::error::Error >
{
if self.config.min_instances == 0
{
return Err( crate::error::Error::ConfigurationError(
"Minimum instances must be greater than 0".to_string()
) );
}
if self.config.max_instances < self.config.min_instances
{
return Err( crate::error::Error::ConfigurationError(
"Maximum instances must be greater than or equal to minimum instances".to_string()
) );
}
if self.config.target_cpu_utilization <= 0.0 || self.config.target_cpu_utilization > 100.0
{
return Err( crate::error::Error::ConfigurationError(
"Target CPU utilization must be between 0 and 100".to_string()
) );
}
if self.config.target_memory_utilization <= 0.0 || self.config.target_memory_utilization > 100.0
{
return Err( crate::error::Error::ConfigurationError(
"Target memory utilization must be between 0 and 100".to_string()
) );
}
Ok( self.config )
}
}
impl ScalingConfig
{
pub fn builder() -> ScalingConfigBuilder
{
ScalingConfigBuilder::new()
}
}
#[ derive( Debug, Clone ) ]
pub struct ResourceConfig
{
pub cpu_cores : f64,
pub memory_gb : f64,
pub gpu_count : usize,
pub gpu_memory_gb : f64,
pub storage_gb : f64,
}
impl Default for ResourceConfig
{
fn default() -> Self
{
Self {
cpu_cores : 1.0,
memory_gb : 2.0,
gpu_count : 0,
gpu_memory_gb : 0.0,
storage_gb : 10.0,
}
}
}
#[ derive( Debug, Clone ) ]
pub struct ResourceConfigBuilder
{
config : ResourceConfig,
}
impl ResourceConfigBuilder
{
pub fn new() -> Self
{
Self {
config : ResourceConfig::default(),
}
}
pub fn cpu_cores( mut self, cores : f64 ) -> Self
{
self.config.cpu_cores = cores;
self
}
pub fn memory_gb( mut self, memory : f64 ) -> Self
{
self.config.memory_gb = memory;
self
}
pub fn gpu_count( mut self, count : usize ) -> Self
{
self.config.gpu_count = count;
self
}
pub fn gpu_memory_gb( mut self, memory : f64 ) -> Self
{
self.config.gpu_memory_gb = memory;
self
}
pub fn storage_gb( mut self, storage : f64 ) -> Self
{
self.config.storage_gb = storage;
self
}
pub fn build( self ) -> Result< ResourceConfig, crate::error::Error >
{
if self.config.cpu_cores <= 0.0
{
return Err( crate::error::Error::ConfigurationError(
"CPU cores must be greater than 0".to_string()
) );
}
if self.config.memory_gb <= 0.0
{
return Err( crate::error::Error::ConfigurationError(
"Memory must be greater than 0".to_string()
) );
}
Ok( self.config )
}
}
impl ResourceConfig
{
pub fn builder() -> ResourceConfigBuilder
{
ResourceConfigBuilder::new()
}
}
#[ derive( Debug, Clone ) ]
pub struct IntelligentScaler
{
config : ScalingConfig,
metrics_history : Arc< Mutex< Vec< ( SystemTime, DeploymentMetrics ) > > >,
last_scaling_action : Arc< Mutex< Option< SystemTime > > >,
prediction_window_minutes : u64,
}
impl IntelligentScaler
{
pub fn new( config : ScalingConfig ) -> Self
{
Self {
config,
metrics_history : Arc::new( Mutex::new( Vec::new() ) ),
last_scaling_action : Arc::new( Mutex::new( None ) ),
prediction_window_minutes : 15, }
}
pub fn record_metrics( &self, metrics : &DeploymentMetrics )
{
let mut history = self.metrics_history.lock().unwrap();
let now = SystemTime::now();
let cloned_metrics = DeploymentMetrics::new();
cloned_metrics.set_cpu_utilization( metrics.cpu_utilization() );
cloned_metrics.set_memory_utilization( metrics.memory_utilization() );
cloned_metrics.set_request_rate( metrics.request_rate() );
history.push( ( now, cloned_metrics ) );
let cutoff = now - Duration::from_secs( 24 * 60 * 60 );
history.retain( | ( timestamp, _ ) | *timestamp > cutoff );
}
pub fn prediction_window_minutes( &self ) -> u64
{
self.prediction_window_minutes
}
pub fn should_scale( &self, current_metrics : &DeploymentMetrics ) -> Option< ScalingDecision >
{
let cpu_util = current_metrics.cpu_utilization();
let memory_util = current_metrics.memory_utilization();
let current_instances = current_metrics.instance_count.load( Ordering::Relaxed );
if let Some( last_action ) = *self.last_scaling_action.lock().unwrap()
{
let time_since_last = SystemTime::now()
.duration_since( last_action )
.unwrap_or_default();
let required_cooldown = if cpu_util > self.config.target_cpu_utilization
{
self.config.scale_up_cooldown
} else {
self.config.scale_down_cooldown
};
if time_since_last < required_cooldown
{
return None; }
}
if cpu_util > self.config.target_cpu_utilization || memory_util > self.config.target_memory_utilization
{
if current_instances < self.config.max_instances
{
let predicted_load = self.predict_future_load();
let recommended_instances = self.calculate_optimal_instances( predicted_load );
if recommended_instances > current_instances
{
return Some( ScalingDecision::ScaleUp {
target_instances : recommended_instances.min( self.config.max_instances ),
reason : format!( "CPU: {:.1}%, Memory : {:.1}%, Target CPU: {:.1}%",
cpu_util, memory_util, self.config.target_cpu_utilization ),
} );
}
}
}
if cpu_util < self.config.target_cpu_utilization * 0.5 && memory_util < self.config.target_memory_utilization * 0.5
{
if current_instances > self.config.min_instances
{
let predicted_load = self.predict_future_load();
let recommended_instances = self.calculate_optimal_instances( predicted_load );
if recommended_instances < current_instances
{
return Some( ScalingDecision::ScaleDown {
target_instances : recommended_instances.max( self.config.min_instances ),
reason : format!( "CPU: {:.1}%, Memory : {:.1}%, low utilization detected",
cpu_util, memory_util ),
} );
}
}
}
None
}
fn predict_future_load( &self ) -> f64
{
let history = self.metrics_history.lock().unwrap();
if history.is_empty()
{
return 1.0; }
let recent_metrics : Vec< _ > = history
.iter()
.rev()
.take( 10 ) .collect();
if recent_metrics.len() < 2
{
return 1.0;
}
let avg_cpu : f64 = recent_metrics
.iter()
.map( | ( _, metrics ) | metrics.cpu_utilization() )
.sum::< f64 >() / recent_metrics.len() as f64;
( avg_cpu / 100.0 ).max( 0.1 ).min( 2.0 )
}
fn calculate_optimal_instances( &self, predicted_load : f64 ) -> usize
{
let base_instances = self.config.min_instances;
let load_factor = predicted_load;
let target_instances = ( base_instances as f64 * load_factor ).ceil() as usize;
target_instances
.max( self.config.min_instances )
.min( self.config.max_instances )
}
pub fn record_scaling_action( &self )
{
*self.last_scaling_action.lock().unwrap() = Some( SystemTime::now() );
}
}
#[ derive( Debug, Clone ) ]
pub enum ScalingDecision
{
ScaleUp {
target_instances : usize,
reason : String,
},
ScaleDown {
target_instances : usize,
reason : String,
},
}