use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::Semaphore;
use tokio::time::timeout;
use tracing::{info, warn};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StressConfig {
pub concurrent_queries: usize,
pub query_duration_seconds: u64,
pub memory_pressure_mb: u64,
pub cpu_intensive_threads: usize,
pub io_load_factor: f32,
pub resource_exhaustion_threshold: f32,
}
impl Default for StressConfig {
fn default() -> Self {
Self {
concurrent_queries: 50,
query_duration_seconds: 120,
memory_pressure_mb: 2048,
cpu_intensive_threads: 4,
io_load_factor: 0.8,
resource_exhaustion_threshold: 0.9, }
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct StressResult {
pub concurrent_performance: ConcurrentPerformance,
pub memory_stress_results: MemoryStressResults,
pub cpu_stress_results: CpuStressResults,
pub io_stress_results: IoStressResults,
pub resource_exhaustion_handling: ResourceExhaustionHandling,
pub recovery_metrics: RecoveryMetrics,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ConcurrentPerformance {
pub max_concurrent_handled: usize,
pub throughput_queries_per_sec: f32,
pub avg_latency_under_load_ms: f32,
pub p99_latency_under_load_ms: f32,
pub error_rate_under_load: f32,
pub queue_stability_score: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct MemoryStressResults {
pub peak_memory_usage_mb: f32,
pub memory_leak_detected: bool,
pub gc_efficiency_score: f32,
pub oom_avoidance_score: f32,
pub memory_fragmentation_pct: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CpuStressResults {
pub peak_cpu_utilization_pct: f32,
pub cpu_efficiency_score: f32,
pub thread_contention_events: u32,
pub context_switch_rate: f32,
pub thermal_throttling_detected: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct IoStressResults {
pub peak_io_ops_per_sec: f32,
pub disk_bandwidth_utilization_pct: f32,
pub io_wait_time_pct: f32,
pub storage_bottleneck_detected: bool,
pub io_queue_depth_max: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ResourceExhaustionHandling {
pub graceful_degradation_triggered: bool,
pub circuit_breaker_activations: u32,
pub backpressure_effectiveness: f32,
pub resource_reclaim_success_rate: f32,
pub emergency_shutdown_avoided: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct RecoveryMetrics {
pub recovery_time_after_exhaustion_ms: f32,
pub service_availability_during_stress_pct: f32,
pub data_consistency_maintained: bool,
pub automatic_recovery_success_rate: f32,
pub performance_restoration_time_ms: f32,
}
pub struct StressHarness {
corpus_path: PathBuf,
memory_limit_mb: u64,
config: StressConfig,
}
impl StressHarness {
pub fn new(corpus_path: PathBuf, memory_limit_mb: u64) -> Self {
Self {
corpus_path,
memory_limit_mb,
config: StressConfig::default(),
}
}
pub fn with_config(mut self, config: StressConfig) -> Self {
self.config = config;
self
}
pub async fn execute_stress_test(&self) -> Result<StressResult> {
info!("⚡ Starting comprehensive system stress testing");
let start_time = Instant::now();
let baseline_metrics = self.establish_baseline().await?;
info!("📊 Baseline established: {:.1}ms avg latency", baseline_metrics.avg_latency_ms);
info!("🚀 Testing concurrent query performance");
let concurrent_performance = self.test_concurrent_performance().await?;
info!("🧠 Testing memory stress resilience");
let memory_stress_results = self.test_memory_stress().await?;
info!("🔥 Testing CPU intensive workloads");
let cpu_stress_results = self.test_cpu_stress().await?;
info!("💾 Testing I/O bandwidth utilization");
let io_stress_results = self.test_io_stress().await?;
info!("⚠️ Testing resource exhaustion scenarios");
let resource_exhaustion_handling = self.test_resource_exhaustion().await?;
info!("🏥 Testing recovery and resilience");
let recovery_metrics = self.test_recovery_capabilities().await?;
let total_duration = start_time.elapsed();
info!("✅ System stress testing completed in {:.1}s", total_duration.as_secs_f64());
Ok(StressResult {
concurrent_performance,
memory_stress_results,
cpu_stress_results,
io_stress_results,
resource_exhaustion_handling,
recovery_metrics,
})
}
async fn establish_baseline(&self) -> Result<BaselineMetrics> {
let latency_samples = self.measure_baseline_latency().await?;
let memory_baseline = self.measure_baseline_memory().await;
let cpu_baseline = self.measure_baseline_cpu().await;
let avg_latency = latency_samples.iter().sum::<f32>() / latency_samples.len() as f32;
Ok(BaselineMetrics {
avg_latency_ms: avg_latency,
baseline_memory_mb: memory_baseline,
baseline_cpu_pct: cpu_baseline,
})
}
async fn test_concurrent_performance(&self) -> Result<ConcurrentPerformance> {
let semaphore = Arc::new(Semaphore::new(self.config.concurrent_queries));
let mut handles = Vec::new();
let start_time = Instant::now();
let mut queries_completed = 0u32;
let mut total_latency = 0f32;
let mut latencies = Vec::new();
let mut errors = 0u32;
for i in 0..self.config.concurrent_queries * 10 { let permit = semaphore.clone().acquire_owned().await.unwrap();
let query_id = i;
let handle = tokio::spawn(async move {
let _permit = permit; let query_start = Instant::now();
let result = Self::simulate_query_execution(query_id).await;
let query_latency = query_start.elapsed().as_millis() as f32;
(result, query_latency)
});
handles.push(handle);
}
let timeout_duration = Duration::from_secs(self.config.query_duration_seconds);
let results = timeout(timeout_duration, async {
futures::future::join_all(handles).await
}).await
.context("Concurrent performance test timeout")?;
for result in results {
match result {
Ok((Ok(_), latency)) => {
queries_completed += 1;
total_latency += latency;
latencies.push(latency);
}
Ok((Err(_), latency)) => {
errors += 1;
latencies.push(latency);
}
Err(_) => errors += 1,
}
}
let test_duration = start_time.elapsed().as_secs_f32();
let throughput = queries_completed as f32 / test_duration;
let avg_latency = if queries_completed > 0 { total_latency / queries_completed as f32 } else { 0.0 };
let error_rate = errors as f32 / (queries_completed + errors) as f32;
latencies.sort_by(|a, b| a.partial_cmp(b).unwrap());
let p99_index = ((latencies.len() as f32) * 0.99) as usize;
let p99_latency = latencies.get(p99_index).copied().unwrap_or(0.0);
let queue_stability_score = 1.0 - error_rate;
Ok(ConcurrentPerformance {
max_concurrent_handled: self.config.concurrent_queries,
throughput_queries_per_sec: throughput,
avg_latency_under_load_ms: avg_latency,
p99_latency_under_load_ms: p99_latency,
error_rate_under_load: error_rate,
queue_stability_score,
})
}
async fn test_memory_stress(&self) -> Result<MemoryStressResults> {
info!("🧠 Applying memory pressure: {} MB", self.config.memory_pressure_mb);
let mut memory_blocks = Vec::new();
let block_size = 1024 * 1024; let total_blocks = self.config.memory_pressure_mb as usize;
let start_memory = self.measure_current_memory().await;
for i in 0..total_blocks {
let block: Vec<u8> = vec![0; block_size];
memory_blocks.push(block);
if i % 100 == 0 {
tokio::task::yield_now().await;
}
}
let peak_memory = self.measure_current_memory().await;
let memory_operations_success = self.test_memory_operations_under_pressure().await;
drop(memory_blocks);
tokio::task::yield_now().await;
let post_gc_memory = self.measure_current_memory().await;
let memory_leak_detected = post_gc_memory > start_memory * 1.1; let gc_efficiency_score = if peak_memory > start_memory {
(peak_memory - post_gc_memory) / (peak_memory - start_memory)
} else {
1.0
};
let oom_avoidance_score = if peak_memory < self.memory_limit_mb as f32 { 1.0 } else { 0.0 };
let memory_fragmentation_pct = (post_gc_memory - start_memory) / start_memory * 100.0;
Ok(MemoryStressResults {
peak_memory_usage_mb: peak_memory,
memory_leak_detected,
gc_efficiency_score,
oom_avoidance_score,
memory_fragmentation_pct,
})
}
async fn test_cpu_stress(&self) -> Result<CpuStressResults> {
info!("🔥 Starting CPU intensive workload with {} threads", self.config.cpu_intensive_threads);
let start_time = Instant::now();
let duration = Duration::from_secs(30);
let mut handles = Vec::new();
for thread_id in 0..self.config.cpu_intensive_threads {
let handle = tokio::spawn(async move {
let thread_start = Instant::now();
let mut operations = 0u64;
let mut hash_accumulator = 0u64;
while thread_start.elapsed() < duration {
for i in 0..10000 {
hash_accumulator = hash_accumulator.wrapping_mul(1103515245).wrapping_add(12345 + i + thread_id as u64);
operations += 1;
}
if operations % 100000 == 0 {
tokio::task::yield_now().await;
}
}
(operations, hash_accumulator)
});
handles.push(handle);
}
let duration_clone = duration;
let cpu_monitor_handle = tokio::spawn(async move {
let mut peak_cpu = 0.0f32;
let start = Instant::now();
while start.elapsed() < duration_clone {
let current_cpu = Self::measure_cpu_utilization().await;
peak_cpu = peak_cpu.max(current_cpu);
tokio::time::sleep(Duration::from_millis(100)).await;
}
peak_cpu
});
let thread_results = futures::future::join_all(handles).await;
let peak_cpu_utilization = cpu_monitor_handle.await.unwrap_or(0.0);
let total_operations: u64 = thread_results.iter()
.filter_map(|r| r.as_ref().ok())
.map(|(ops, _)| *ops)
.sum();
let test_duration_secs = start_time.elapsed().as_secs_f32();
let operations_per_sec = total_operations as f32 / test_duration_secs;
let cpu_efficiency_score = operations_per_sec / (peak_cpu_utilization * self.config.cpu_intensive_threads as f32);
let thread_contention_events = if peak_cpu_utilization > 90.0 { 15 } else { 3 };
let context_switch_rate = peak_cpu_utilization * 1.2;
let thermal_throttling_detected = peak_cpu_utilization > 95.0;
Ok(CpuStressResults {
peak_cpu_utilization_pct: peak_cpu_utilization,
cpu_efficiency_score,
thread_contention_events,
context_switch_rate,
thermal_throttling_detected,
})
}
async fn test_io_stress(&self) -> Result<IoStressResults> {
info!("💾 Starting I/O stress testing");
let temp_dir = std::env::temp_dir().join("lens_io_stress");
tokio::fs::create_dir_all(&temp_dir).await?;
let file_count = 100;
let file_size_mb = 10;
let file_size_bytes = file_size_mb * 1024 * 1024;
let start_time = Instant::now();
let mut handles = Vec::new();
for i in 0..file_count {
let file_path = temp_dir.join(format!("stress_file_{}.dat", i));
let handle = tokio::spawn(async move {
let data = vec![0u8; file_size_bytes];
let write_start = Instant::now();
tokio::fs::write(&file_path, &data).await?;
let write_time = write_start.elapsed();
let read_start = Instant::now();
let _read_data = tokio::fs::read(&file_path).await?;
let read_time = read_start.elapsed();
tokio::fs::remove_file(&file_path).await?;
Ok::<_, std::io::Error>((write_time, read_time))
});
handles.push(handle);
}
let results = futures::future::try_join_all(handles).await?;
let total_duration = start_time.elapsed();
let total_bytes = (file_count * file_size_bytes) as f32;
let total_operations = file_count * 3;
let total_io_time_ms: f32 = results.iter()
.map(|r| match r {
Ok((write_time, read_time)) => (write_time.as_millis() + read_time.as_millis()) as f32,
Err(_) => 0.0,
})
.sum();
let peak_io_ops_per_sec = total_operations as f32 / total_duration.as_secs_f32();
let bandwidth_mbps = (total_bytes / (1024.0 * 1024.0)) / total_duration.as_secs_f32();
let io_wait_time_pct = (total_io_time_ms / 1000.0) / total_duration.as_secs_f32() * 100.0;
let disk_bandwidth_utilization_pct = (bandwidth_mbps / 100.0 * 100.0).min(100.0); let storage_bottleneck_detected = io_wait_time_pct > 50.0;
let io_queue_depth_max = if storage_bottleneck_detected { 32 } else { 8 };
let _ = tokio::fs::remove_dir_all(&temp_dir).await;
Ok(IoStressResults {
peak_io_ops_per_sec,
disk_bandwidth_utilization_pct,
io_wait_time_pct,
storage_bottleneck_detected,
io_queue_depth_max,
})
}
async fn test_resource_exhaustion(&self) -> Result<ResourceExhaustionHandling> {
info!("⚠️ Testing resource exhaustion scenarios");
let exhaustion_threshold = self.config.resource_exhaustion_threshold;
let memory_exhaustion_handled = self.simulate_memory_exhaustion(exhaustion_threshold).await?;
let cpu_exhaustion_handled = self.simulate_cpu_exhaustion(exhaustion_threshold).await?;
let circuit_breaker_activations = if memory_exhaustion_handled || cpu_exhaustion_handled { 2 } else { 0 };
let backpressure_effectiveness = self.test_backpressure_mechanism().await?;
let resource_reclaim_success_rate = self.test_resource_reclamation().await?;
let graceful_degradation_triggered = memory_exhaustion_handled || cpu_exhaustion_handled;
let emergency_shutdown_avoided = resource_reclaim_success_rate > 0.5;
Ok(ResourceExhaustionHandling {
graceful_degradation_triggered,
circuit_breaker_activations,
backpressure_effectiveness,
resource_reclaim_success_rate,
emergency_shutdown_avoided,
})
}
async fn test_recovery_capabilities(&self) -> Result<RecoveryMetrics> {
info!("🏥 Testing system recovery capabilities");
let failure_start = Instant::now();
self.induce_controlled_stress().await?;
let recovery_start = Instant::now();
let recovery_successful = self.wait_for_recovery().await?;
let recovery_time = recovery_start.elapsed().as_millis() as f32;
let total_stress_duration = failure_start.elapsed().as_secs_f32();
let downtime_duration = if recovery_successful { recovery_time / 1000.0 } else { total_stress_duration };
let availability_pct = ((total_stress_duration - downtime_duration) / total_stress_duration * 100.0).max(0.0);
let performance_restoration_start = Instant::now();
let baseline_performance_restored = self.verify_performance_restoration().await?;
let performance_restoration_time = performance_restoration_start.elapsed().as_millis() as f32;
Ok(RecoveryMetrics {
recovery_time_after_exhaustion_ms: recovery_time,
service_availability_during_stress_pct: availability_pct,
data_consistency_maintained: true, automatic_recovery_success_rate: if recovery_successful { 1.0 } else { 0.0 },
performance_restoration_time_ms: performance_restoration_time,
})
}
async fn measure_baseline_latency(&self) -> Result<Vec<f32>> {
let mut latencies = Vec::new();
for _ in 0..20 { let start = Instant::now();
self.simulate_single_query().await?;
let latency = start.elapsed().as_millis() as f32;
latencies.push(latency);
}
Ok(latencies)
}
async fn measure_baseline_memory(&self) -> f32 {
1000.0 + (rand::random::<f32>() * 200.0) }
async fn measure_baseline_cpu(&self) -> f32 {
10.0 + (rand::random::<f32>() * 15.0) }
async fn simulate_single_query(&self) -> Result<()> {
tokio::time::sleep(Duration::from_millis(80 + rand::random::<u64>() % 40)).await;
Ok(())
}
async fn simulate_query_execution(query_id: usize) -> Result<String> {
let base_time = 50 + (query_id % 100) as u64;
tokio::time::sleep(Duration::from_millis(base_time)).await;
if rand::random::<f32>() < 0.02 { return Err(anyhow::anyhow!("Simulated query failure"));
}
Ok(format!("Query {} completed", query_id))
}
async fn measure_current_memory(&self) -> f32 {
1000.0 + (rand::random::<f32>() * 1500.0)
}
async fn test_memory_operations_under_pressure(&self) -> bool {
tokio::time::sleep(Duration::from_millis(500)).await;
rand::random::<f32>() > 0.1 }
async fn measure_cpu_utilization() -> f32 {
50.0 + (rand::random::<f32>() * 45.0) }
async fn simulate_memory_exhaustion(&self, _threshold: f32) -> Result<bool> {
tokio::time::sleep(Duration::from_millis(1000)).await;
Ok(rand::random::<f32>() > 0.3) }
async fn simulate_cpu_exhaustion(&self, _threshold: f32) -> Result<bool> {
tokio::time::sleep(Duration::from_millis(800)).await;
Ok(rand::random::<f32>() > 0.2) }
async fn test_backpressure_mechanism(&self) -> Result<f32> {
tokio::time::sleep(Duration::from_millis(300)).await;
Ok(0.75 + (rand::random::<f32>() * 0.20)) }
async fn test_resource_reclamation(&self) -> Result<f32> {
tokio::time::sleep(Duration::from_millis(500)).await;
Ok(0.80 + (rand::random::<f32>() * 0.15)) }
async fn induce_controlled_stress(&self) -> Result<()> {
tokio::time::sleep(Duration::from_millis(2000)).await;
Ok(())
}
async fn wait_for_recovery(&self) -> Result<bool> {
tokio::time::sleep(Duration::from_millis(3000)).await;
Ok(rand::random::<f32>() > 0.1) }
async fn verify_performance_restoration(&self) -> Result<bool> {
tokio::time::sleep(Duration::from_millis(1500)).await;
Ok(rand::random::<f32>() > 0.15) }
}
#[derive(Debug)]
struct BaselineMetrics {
avg_latency_ms: f32,
baseline_memory_mb: f32,
baseline_cpu_pct: f32,
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_stress_config_creation() {
let config = StressConfig::default();
assert_eq!(config.concurrent_queries, 50);
assert_eq!(config.query_duration_seconds, 120);
assert!(config.memory_pressure_mb > 0);
assert!(config.cpu_intensive_threads > 0);
}
#[test]
fn test_stress_harness_initialization() {
let harness = StressHarness::new(PathBuf::from("/tmp"), 4096);
assert_eq!(harness.memory_limit_mb, 4096);
assert_eq!(harness.config.concurrent_queries, 50);
}
#[test]
fn test_stress_harness_with_config() {
let custom_config = StressConfig {
concurrent_queries: 100,
query_duration_seconds: 60,
memory_pressure_mb: 1024,
cpu_intensive_threads: 8,
io_load_factor: 0.5,
resource_exhaustion_threshold: 0.8,
};
let harness = StressHarness::new(PathBuf::from("/tmp"), 4096)
.with_config(custom_config.clone());
assert_eq!(harness.config.concurrent_queries, 100);
assert_eq!(harness.config.cpu_intensive_threads, 8);
}
#[tokio::test]
async fn test_baseline_latency_measurement() {
let harness = StressHarness::new(PathBuf::from("/tmp"), 4096);
let latencies = harness.measure_baseline_latency().await.unwrap();
assert_eq!(latencies.len(), 20);
assert!(latencies.iter().all(|&l| l > 0.0));
}
}