use crate::core::config::ConfigManager; #[cfg(feature = "nvidia")] use crate::nvidia::get_system_metrics; use crate::security::command_guard::SafeCommand; use crate::core::shared::models::schema::bots::dsl::*; use crate::core::shared::state::AppState; use diesel::prelude::*; use std::sync::Arc; use sysinfo::System; /// Cache for component status to avoid spawning pgrep processes too frequently struct ComponentStatusCache { statuses: Vec<(String, bool, String)>, // (name, is_running, port) last_check: std::time::Instant, } impl ComponentStatusCache { fn new() -> Self { Self { statuses: Vec::new(), last_check: std::time::Instant::now() - std::time::Duration::from_secs(60), } } fn needs_refresh(&self) -> bool { // Only check component status every 10 seconds self.last_check.elapsed() > std::time::Duration::from_secs(10) } fn refresh(&mut self) { let components = vec![ ("Tables", "postgres", "5432"), ("Cache", "valkey-server", "6379"), ("Drive", "minio", "9000"), ("LLM", "llama-server", "8081"), ]; self.statuses.clear(); for (comp_name, process, port) in components { let is_running = Self::check_component_running(process); self.statuses .push((comp_name.to_string(), is_running, port.to_string())); } self.last_check = std::time::Instant::now(); } fn check_component_running(process_name: &str) -> bool { SafeCommand::new("pgrep") .and_then(|c| c.arg("-f")) .and_then(|c| c.arg(process_name)) .ok() .and_then(|cmd| cmd.execute().ok()) .map(|output| !output.stdout.is_empty()) .unwrap_or(false) } fn get_statuses(&self) -> &[(String, bool, String)] { &self.statuses } } /// Cache for bot list to avoid database queries too frequently struct BotListCache { bot_list: Vec<(String, uuid::Uuid)>, last_check: std::time::Instant, } impl BotListCache { fn new() -> Self { Self { bot_list: Vec::new(), last_check: std::time::Instant::now() - std::time::Duration::from_secs(60), } } fn needs_refresh(&self) -> bool { // Only query database every 5 seconds self.last_check.elapsed() > std::time::Duration::from_secs(5) } fn refresh(&mut self, app_state: &Arc) { if let Ok(mut conn) = app_state.conn.get() { if let Ok(list) = bots .filter(is_active.eq(true)) .select((name, id)) .load::<(String, uuid::Uuid)>(&mut *conn) { self.bot_list = list; } } self.last_check = std::time::Instant::now(); } fn get_bots(&self) -> &[(String, uuid::Uuid)] { &self.bot_list } } pub struct StatusPanel { app_state: Arc, last_update: std::time::Instant, last_system_refresh: std::time::Instant, cached_content: String, system: System, component_cache: ComponentStatusCache, bot_cache: BotListCache, } impl std::fmt::Debug for StatusPanel { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("StatusPanel") .field("app_state", &"Arc") .field("last_update", &self.last_update) .field("cached_content_len", &self.cached_content.len()) .field("system", &"System") .finish() } } impl StatusPanel { pub fn new(app_state: Arc) -> Self { // Only initialize with CPU and memory info, not all system info let mut system = System::new(); system.refresh_cpu_all(); system.refresh_memory(); Self { app_state, last_update: std::time::Instant::now(), last_system_refresh: std::time::Instant::now(), cached_content: String::new(), system, component_cache: ComponentStatusCache::new(), bot_cache: BotListCache::new(), } } pub fn update(&mut self) -> Result<(), std::io::Error> { // Only refresh system metrics every 2 seconds instead of every call // This is the main CPU hog - refresh_all() is very expensive if self.last_system_refresh.elapsed() > std::time::Duration::from_secs(2) { // Only refresh CPU and memory, not ALL system info self.system.refresh_cpu_all(); self.system.refresh_memory(); self.last_system_refresh = std::time::Instant::now(); } // Refresh component status cache if needed (every 10 seconds) if self.component_cache.needs_refresh() { self.component_cache.refresh(); } // Refresh bot list cache if needed (every 5 seconds) if self.bot_cache.needs_refresh() { self.bot_cache.refresh(&self.app_state); } self.cached_content = self.render(None); self.last_update = std::time::Instant::now(); Ok(()) } pub fn render(&mut self, selected_bot: Option) -> String { let mut lines = vec![ "╔═══════════════════════════════════════╗".to_string(), "║ SYSTEM METRICS ║".to_string(), "╚═══════════════════════════════════════╝".to_string(), String::new(), ]; // Use cached CPU usage - don't refresh here let cpu_usage = self.system.global_cpu_usage(); let cpu_bar = Self::create_progress_bar(cpu_usage, 20); lines.push(format!(" CPU: {:5.1}% {}", cpu_usage, cpu_bar)); #[cfg(feature = "nvidia")] { let system_metrics = get_system_metrics().unwrap_or_default(); if let Some(gpu_usage) = system_metrics.gpu_usage { let gpu_bar = Self::create_progress_bar(gpu_usage, 20); lines.push(format!(" GPU: {:5.1}% {}", gpu_usage, gpu_bar)); } else { lines.push(" GPU: Not available".to_string()); } } #[cfg(not(feature = "nvidia"))] { lines.push(" GPU: Feature not enabled".to_string()); } let total_mem = self.system.total_memory() as f32 / 1024.0 / 1024.0 / 1024.0; let used_mem = self.system.used_memory() as f32 / 1024.0 / 1024.0 / 1024.0; let mem_percentage = if total_mem > 0.0 { (used_mem / total_mem) * 100.0 } else { 0.0 }; let mem_bar = Self::create_progress_bar(mem_percentage, 20); lines.push(format!( " MEM: {:5.1}% {} ({:.1}/{:.1} GB)", mem_percentage, mem_bar, used_mem, total_mem )); lines.push("".to_string()); lines.push("╔═══════════════════════════════════════╗".to_string()); lines.push("║ COMPONENTS STATUS ║".to_string()); lines.push("╚═══════════════════════════════════════╝".to_string()); lines.push("".to_string()); // Use cached component statuses instead of spawning pgrep every time for (comp_name, is_running, port) in self.component_cache.get_statuses() { let status = if *is_running { format!(" ONLINE [Port: {}]", port) } else { " OFFLINE".to_string() }; lines.push(format!(" {:<10} {}", comp_name, status)); } lines.push("".to_string()); lines.push("╔═══════════════════════════════════════╗".to_string()); lines.push("║ ACTIVE BOTS ║".to_string()); lines.push("╚═══════════════════════════════════════╝".to_string()); lines.push("".to_string()); // Use cached bot list instead of querying database every time let bot_list = self.bot_cache.get_bots(); if bot_list.is_empty() { lines.push(" No active bots".to_string()); } else { for (bot_name, bot_id) in bot_list { let marker = if let Some(ref selected) = selected_bot { if selected == bot_name { "►" } else { " " } } else { " " }; lines.push(format!(" {} {}", marker, bot_name)); if let Some(ref selected) = selected_bot { if selected == bot_name { lines.push("".to_string()); lines.push(" ┌─ Bot Configuration ─────────┐".to_string()); let config_manager = ConfigManager::new(self.app_state.conn.clone()); let llm_model = config_manager .get_config(bot_id, "llm-model", None) .unwrap_or_else(|_| "N/A".to_string()); lines.push(format!(" Model: {}", llm_model)); let ctx_size = config_manager .get_config(bot_id, "llm-server-ctx-size", None) .unwrap_or_else(|_| "N/A".to_string()); lines.push(format!(" Context: {}", ctx_size)); let temp = config_manager .get_config(bot_id, "llm-temperature", None) .unwrap_or_else(|_| "N/A".to_string()); lines.push(format!(" Temp: {}", temp)); lines.push(" └─────────────────────────────┘".to_string()); } } } } lines.push("".to_string()); lines.push("╔═══════════════════════════════════════╗".to_string()); lines.push("║ SESSIONS ║".to_string()); lines.push("╚═══════════════════════════════════════╝".to_string()); let session_count = self .app_state .response_channels .try_lock() .map(|channels| channels.len()) .unwrap_or(0); lines.push(format!(" Active Sessions: {}", session_count)); lines.join("\n") } fn create_progress_bar(percentage: f32, width: usize) -> String { let filled = (percentage / 100.0 * width as f32).round() as usize; let empty = width.saturating_sub(filled); let filled_chars = "█".repeat(filled); let empty_chars = "░".repeat(empty); format!("[{}{}]", filled_chars, empty_chars) } pub fn check_component_running(process_name: &str) -> bool { ComponentStatusCache::check_component_running(process_name) } }