From ce59f7ea2f005941910cd24dafd0fc050c1e8131 Mon Sep 17 00:00:00 2001 From: Cylae <13425054+Cylae@users.noreply.github.com> Date: Tue, 3 Mar 2026 14:19:38 +0000 Subject: [PATCH] Optimize dashboard by moving system metrics collection to spawn_blocking - Wrap the `System` metric polling (`sys.refresh_cpu()`, etc.) inside `tokio::task::spawn_blocking` to decouple it from the main Tokio async executor. - Remove instances of `.unwrap()` when acquiring mutex locks on the `AppState` in the dashboard, replacing them with proper pattern matching to handle poisoned states gracefully. - Update `README.md` to document optimizations in both English and French. --- README.md | 16 +++++ server_manager/src/interface/web.rs | 93 +++++++++++++++++------------ 2 files changed, 72 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 6187960..41a3d6e 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,14 @@ Passwords are stored in `secrets.yaml`. * Automatically generated on first launch. * You can modify this file *before* running `install` or `generate` if you wish to set your own passwords. +### ⚡ Optimizations & Performance + +Server Manager implements several strategies to ensure minimal overhead: +* **Asynchronous I/O**: The Web UI uses non-blocking asynchronous I/O and `tokio::task::spawn_blocking` to decouple heavy system metric collection from the async execution runtime. +* **In-Memory Caching**: Configuration and User Management models are loaded and cached into a static `OnceLock>` to significantly reduce file system calls. +* **Targeted Hardware Detection**: Memory and system disk space metrics are updated using targeted routines (`sysinfo` components) preventing redundant hardware discovery loops. +* **Zero-Allocation HTML**: The web interface dynamically generates the DOM on-the-fly directly to a string buffer using Rust's `std::fmt::Write`. + ### 💾 Data Persistence Server Manager stores its configuration and data in the following locations: @@ -283,6 +291,14 @@ Les mots de passe sont stockés dans `secrets.yaml`. * Générés automatiquement au premier lancement. * Vous pouvez modifier ce fichier *avant* de lancer `install` ou `generate` si vous souhaitez définir vos propres mots de passe. +### ⚡ Optimisations & Performances + +Server Manager intègre plusieurs stratégies pour garantir des performances optimales et une surcharge minimale : +* **E/S Asynchrones** : L'interface web gère la collecte de métriques systèmes de manière asynchrone (via `tokio::task::spawn_blocking`), pour ne pas bloquer le thread d'exécution principal. +* **Mise en Cache en Mémoire** : La configuration et la gestion des utilisateurs sont mises en cache de manière statique via `OnceLock>` réduisant considérablement les accès disque. +* **Détection Matérielle Ciblée** : L'actualisation des statistiques RAM/Stockage s'effectue via des appels ciblés (`sysinfo`), sans recalculer inutilement la topologie du système. +* **Zéro Allocation HTML** : L'interface d'administration génère dynamiquement son contenu HTML directement dans un tampon (`std::fmt::Write`), évitant les allocations mémoires inutiles. + ### 💾 Persistance des Données Server Manager stocke sa configuration et ses données aux emplacements suivants : diff --git a/server_manager/src/interface/web.rs b/server_manager/src/interface/web.rs index 1f677c3..7906bc9 100644 --- a/server_manager/src/interface/web.rs +++ b/server_manager/src/interface/web.rs @@ -331,43 +331,62 @@ async fn dashboard(State(state): State, session: Session) -> impl I let config = state.get_config().await; // System Stats - let mut sys = state.system.lock().unwrap(); - let now = SystemTime::now(); - let mut last_refresh = state.last_system_refresh.lock().unwrap(); - - // Throttle refresh to max once every 500ms - if now - .duration_since(*last_refresh) - .unwrap_or_default() - .as_millis() - > 500 - { - sys.refresh_cpu(); - sys.refresh_memory(); - sys.refresh_disks(); - *last_refresh = now; - } - let ram_used = sys.used_memory() / 1024 / 1024; // MB - let ram_total = sys.total_memory() / 1024 / 1024; // MB - let swap_used = sys.used_swap() / 1024 / 1024; // MB - let swap_total = sys.total_swap() / 1024 / 1024; // MB - let cpu_usage = sys.global_cpu_info().cpu_usage(); - - // Simple Disk Usage (Root or fallback) - let mut disk_total = 0; - let mut disk_used = 0; - - let target_disk = sys - .disks() - .iter() - .find(|d| d.mount_point() == std::path::Path::new("/")) - .or_else(|| sys.disks().first()); - - if let Some(disk) = target_disk { - disk_total = disk.total_space() / 1024 / 1024 / 1024; // GB - disk_used = (disk.total_space() - disk.available_space()) / 1024 / 1024 / 1024; // GB - } - drop(sys); // Release lock explicitely + let state_clone = Arc::clone(&state); + let stats = tokio::task::spawn_blocking(move || { + let mut sys = match state_clone.system.lock() { + Ok(guard) => guard, + Err(poisoned) => poisoned.into_inner(), + }; + let mut last_refresh = match state_clone.last_system_refresh.lock() { + Ok(guard) => guard, + Err(poisoned) => poisoned.into_inner(), + }; + + let now = SystemTime::now(); + // Throttle refresh to max once every 500ms + if now + .duration_since(*last_refresh) + .unwrap_or_default() + .as_millis() + > 500 + { + sys.refresh_cpu(); + sys.refresh_memory(); + sys.refresh_disks(); + *last_refresh = now; + } + + let ram_used = sys.used_memory() / 1024 / 1024; // MB + let ram_total = sys.total_memory() / 1024 / 1024; // MB + let swap_used = sys.used_swap() / 1024 / 1024; // MB + let swap_total = sys.total_swap() / 1024 / 1024; // MB + let cpu_usage = sys.global_cpu_info().cpu_usage(); + + // Simple Disk Usage (Root or fallback) + let mut disk_total = 0; + let mut disk_used = 0; + + let target_disk = sys + .disks() + .iter() + .find(|d| d.mount_point() == std::path::Path::new("/")) + .or_else(|| sys.disks().first()); + + if let Some(disk) = target_disk { + disk_total = disk.total_space() / 1024 / 1024 / 1024; // GB + disk_used = (disk.total_space() - disk.available_space()) / 1024 / 1024 / 1024; // GB + } + + (ram_used, ram_total, swap_used, swap_total, cpu_usage, disk_used, disk_total) + }).await; + + let (ram_used, ram_total, swap_used, swap_total, cpu_usage, disk_used, disk_total) = match stats { + Ok(s) => s, + Err(e) => { + error!("Failed to join blocking task for system stats: {}", e); + (0, 0, 0, 0, 0.0, 0, 0) + } + }; let mut html = String::with_capacity(8192); write_html_head(&mut html, "Dashboard - Server Manager");