This commit is contained in:
Christoffer Martinsson 2025-10-12 17:29:29 +02:00
parent 088c42e55a
commit 49aee702f2
2 changed files with 28 additions and 25 deletions

View File

@ -18,6 +18,7 @@ pub struct ServiceCollector {
pub services: Vec<String>,
pub timeout_ms: u64,
pub cpu_tracking: std::sync::Arc<tokio::sync::Mutex<std::collections::HashMap<u32, CpuSample>>>,
pub description_cache: std::sync::Arc<tokio::sync::Mutex<std::collections::HashMap<String, Vec<String>>>>,
}
#[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<Vec<String>> {
async fn get_service_description_with_cache(&self, service: &str) -> Option<Vec<String>> {
// 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<Vec<String>> {

View File

@ -35,7 +35,6 @@ struct HostRuntimeState {
smart: Option<SmartMetrics>,
services: Option<ServiceMetrics>,
backup: Option<BackupMetrics>,
service_description_cache: HashMap<String, Vec<String>>, // 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);