diff --git a/README.md b/README.md index 6187960..afa7ccf 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,15 @@ Server Manager adjusts configuration via `HardwareManager`: *Note: Swap presence is analyzed to avoid OOM on borderline configurations (e.g., 6GB RAM without swap -> Low).* +### ⚡ Optimizations & Performance + +Server Manager includes several under-the-hood optimizations to ensure low resource usage and high responsiveness: + +* **Sysctl Tuning**: Automatically applies kernel parameters (via `sysctl`) optimized for high-bandwidth media streaming (BBR congestion control, increased buffer sizes) and reduced swappiness. +* **Async Architecture**: The Web UI and core logic are built on `tokio` and `axum`, ensuring that heavy operations (like password hashing or user creation) never block the main event loop. +* **Smart Caching**: Configuration and User data are cached in memory with intelligent file modification checks (stat) to prevent unnecessary disk reads. +* **Efficient Monitoring**: System resource usage is refreshed only when needed and throttled to prevent CPU spikes. + ### 🔒 Secrets Management Passwords are stored in `secrets.yaml`. @@ -277,6 +286,15 @@ Server Manager ajuste la configuration via `HardwareManager` : *Note : La présence de Swap est analysée pour éviter les OOM sur les configurations limites (ex: 6GB RAM sans swap -> Low).* +### ⚡ Optimisations & Performances + +Server Manager inclut plusieurs optimisations internes pour garantir une faible utilisation des ressources et une grande réactivité : + +* **Réglage Sysctl** : Applique automatiquement les paramètres du noyau (via `sysctl`) optimisés pour le streaming multimédia à haut débit (contrôle de congestion BBR, buffers augmentés) et une utilisation réduite du swap. +* **Architecture Asynchrone** : L'interface Web et la logique centrale sont basées sur `tokio` et `axum`, garantissant que les opérations lourdes (comme le hachage des mots de passe ou la création d'utilisateurs) ne bloquent jamais la boucle d'événements principale. +* **Mise en Cache Intelligente** : Les données de configuration et d'utilisateur sont mises en cache en mémoire avec des vérifications intelligentes de modification de fichier (stat) pour éviter les lectures disque inutiles. +* **Surveillance Efficace** : L'utilisation des ressources système n'est actualisée que lorsque nécessaire et limitée pour éviter les pics de CPU. + ### 🔒 Gestion des Secrets Les mots de passe sont stockés dans `secrets.yaml`. diff --git a/server_manager/src/core/hardware.rs b/server_manager/src/core/hardware.rs index 09f3ff2..9acbf31 100644 --- a/server_manager/src/core/hardware.rs +++ b/server_manager/src/core/hardware.rs @@ -31,6 +31,7 @@ impl HardwareInfo { sys.refresh_memory(); sys.refresh_cpu(); sys.refresh_disks_list(); + sys.refresh_disks(); let total_memory = sys.total_memory(); // Bytes let ram_gb = total_memory / 1024 / 1024 / 1024; diff --git a/server_manager/src/interface/web.rs b/server_manager/src/interface/web.rs index 1f677c3..e47660b 100644 --- a/server_manager/src/interface/web.rs +++ b/server_manager/src/interface/web.rs @@ -131,8 +131,9 @@ pub async fn start_server(port: u16) -> anyhow::Result<()> { .with_expiry(Expiry::OnInactivity(Duration::hours(24))); // Initialize System once - let mut sys = System::new_all(); - sys.refresh_all(); + let sys = System::new(); + // We do NOT refresh everything here to save startup time. + // Dashboard will trigger refresh on first access because we init last_system_refresh to EPOCH. let initial_config = Config::load().unwrap_or_default(); let initial_config_mtime = std::fs::metadata("config.yaml") @@ -151,7 +152,7 @@ pub async fn start_server(port: u16) -> anyhow::Result<()> { let app_state = Arc::new(AppState { system: Mutex::new(sys), - last_system_refresh: Mutex::new(SystemTime::now()), + last_system_refresh: Mutex::new(SystemTime::UNIX_EPOCH), config_cache: RwLock::new(CachedConfig { config: initial_config, last_modified: initial_config_mtime, @@ -344,6 +345,7 @@ async fn dashboard(State(state): State, session: Session) -> impl I { sys.refresh_cpu(); sys.refresh_memory(); + sys.refresh_disks_list(); sys.refresh_disks(); *last_refresh = now; } @@ -627,21 +629,37 @@ async fn add_user_handler(State(state): State, session: Session, Fo }; let mut cache = state.users_cache.write().await; - let res = tokio::task::block_in_place(|| { - cache.manager.add_user(&payload.username, &payload.password, role_enum, quota_val) - }); - - if let Err(e) = res { - error!("Failed to add user: {}", e); - // In a real app we'd flash a message. Here just redirect. - } else { - info!("User {} added via Web UI by {}", payload.username, session_user.username); - // Update mtime to prevent unnecessary reload - let path = std::path::Path::new("users.yaml"); - let fallback_path = std::path::Path::new("/opt/server_manager/users.yaml"); - let file_path = if path.exists() { path } else { fallback_path }; - if let Ok(m) = std::fs::metadata(file_path) { - cache.last_modified = m.modified().ok(); + let mut manager = cache.manager.clone(); + let username = payload.username.clone(); + let password = payload.password.clone(); + + // Perform heavy lifting (hashing, useradd) in a blocking thread + let res = tokio::task::spawn_blocking(move || -> anyhow::Result { + manager.add_user(&username, &password, role_enum, quota_val)?; + Ok(manager) + }) + .await; + + match res { + Ok(Ok(updated_manager)) => { + cache.manager = updated_manager; + info!( + "User {} added via Web UI by {}", + payload.username, session_user.username + ); + // Update mtime to prevent unnecessary reload + let path = std::path::Path::new("users.yaml"); + let fallback_path = std::path::Path::new("/opt/server_manager/users.yaml"); + let file_path = if path.exists() { path } else { fallback_path }; + if let Ok(m) = std::fs::metadata(file_path) { + cache.last_modified = m.modified().ok(); + } + } + Ok(Err(e)) => { + error!("Failed to add user: {}", e); + } + Err(e) => { + error!("Task join error: {}", e); } } @@ -659,20 +677,36 @@ async fn delete_user_handler(State(state): State, session: Session, } let mut cache = state.users_cache.write().await; - let res = tokio::task::block_in_place(|| { - cache.manager.delete_user(&username) - }); - - if let Err(e) = res { - error!("Failed to delete user: {}", e); - } else { - info!("User {} deleted via Web UI by {}", username, session_user.username); - // Update mtime to prevent unnecessary reload - let path = std::path::Path::new("users.yaml"); - let fallback_path = std::path::Path::new("/opt/server_manager/users.yaml"); - let file_path = if path.exists() { path } else { fallback_path }; - if let Ok(m) = std::fs::metadata(file_path) { - cache.last_modified = m.modified().ok(); + let mut manager = cache.manager.clone(); + let username_clone = username.clone(); + + // Perform heavy lifting (userdel) in a blocking thread + let res = tokio::task::spawn_blocking(move || -> anyhow::Result { + manager.delete_user(&username_clone)?; + Ok(manager) + }) + .await; + + match res { + Ok(Ok(updated_manager)) => { + cache.manager = updated_manager; + info!( + "User {} deleted via Web UI by {}", + username, session_user.username + ); + // Update mtime to prevent unnecessary reload + let path = std::path::Path::new("users.yaml"); + let fallback_path = std::path::Path::new("/opt/server_manager/users.yaml"); + let file_path = if path.exists() { path } else { fallback_path }; + if let Ok(m) = std::fs::metadata(file_path) { + cache.last_modified = m.modified().ok(); + } + } + Ok(Err(e)) => { + error!("Failed to delete user: {}", e); + } + Err(e) => { + error!("Task join error: {}", e); } }