diff --git a/agent/src/collectors/service.rs b/agent/src/collectors/service.rs index 7fc2298..456f686 100644 --- a/agent/src/collectors/service.rs +++ b/agent/src/collectors/service.rs @@ -18,6 +18,7 @@ pub struct ServiceCollector { pub services: Vec, pub timeout_ms: u64, pub cpu_tracking: std::sync::Arc>>, + pub description_cache: std::sync::Arc>>>, } #[derive(Debug, Clone)] @@ -35,6 +36,7 @@ impl ServiceCollector { services, timeout_ms: 10000, // 10 second timeout for service checks cpu_tracking: std::sync::Arc::new(tokio::sync::Mutex::new(std::collections::HashMap::new())), + description_cache: std::sync::Arc::new(tokio::sync::Mutex::new(std::collections::HashMap::new())), } } @@ -99,9 +101,9 @@ impl ServiceCollector { 0.0 }; - // Get service-specific description (only for running services, and throttled) + // Get service-specific description (only for running services) let description = if matches!(status, ServiceStatus::Running) { - self.get_service_description_throttled(service).await + self.get_service_description_with_cache(service).await } else { None }; @@ -507,7 +509,26 @@ impl ServiceCollector { } } - async fn get_service_description_throttled(&self, service: &str) -> Option> { + async fn get_service_description_with_cache(&self, service: &str) -> Option> { + // Check if we should update the cache (throttled) + let should_update = self.should_update_description(service).await; + + if should_update { + tracing::debug!("Service {} updating description (throttle check passed)", service); + if let Some(new_description) = self.get_service_description(service).await { + // Update cache + let mut cache = self.description_cache.lock().await; + cache.insert(service.to_string(), new_description.clone()); + return Some(new_description); + } + } + + // Always return cached description if available + let cache = self.description_cache.lock().await; + cache.get(service).cloned() + } + + async fn should_update_description(&self, service: &str) -> bool { // Simple time-based throttling - only run expensive descriptions every ~30 seconds // Use a hash of the current time to spread out when different services get described let now = std::time::SystemTime::now() @@ -525,14 +546,12 @@ impl ServiceCollector { let should_update = (now + service_offset) % update_interval == 0; - if should_update { - tracing::debug!("Service {} updating description (throttle check passed)", service); - self.get_service_description(service).await - } else { + if !should_update { let next_update = update_interval - ((now + service_offset) % update_interval); tracing::trace!("Service {} throttled, next update in {} seconds", service, next_update); - None // Return None to indicate no new description this cycle } + + should_update } async fn get_service_description(&self, service: &str) -> Option> { diff --git a/dashboard/src/app.rs b/dashboard/src/app.rs index 3ab0f4c..dfe493e 100644 --- a/dashboard/src/app.rs +++ b/dashboard/src/app.rs @@ -35,7 +35,6 @@ struct HostRuntimeState { smart: Option, services: Option, backup: Option, - service_description_cache: HashMap>, // service_name -> last_known_descriptions } /// Top-level application state container. @@ -262,22 +261,7 @@ impl App { } let mut snapshot = service_metrics.clone(); - // Update description cache and fill in missing descriptions - for service in &mut snapshot.services { - // If service has a new description, cache it - if let Some(ref description) = service.description { - if !description.is_empty() { - state.service_description_cache.insert(service.name.clone(), description.clone()); - } - } - - // If service has no description but we have a cached one, use it - if service.description.is_none() || service.description.as_ref().map_or(true, |d| d.is_empty()) { - if let Some(cached_description) = state.service_description_cache.get(&service.name) { - service.description = Some(cached_description.clone()); - } - } - } + // No more need for dashboard-side description caching since agent handles it self.history.record_services(service_metrics); state.services = Some(snapshot);