Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<RwLock<T>>` 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:
Expand Down Expand Up @@ -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<RwLock<T>>` 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 :
Expand Down
93 changes: 56 additions & 37 deletions server_manager/src/interface/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,43 +331,62 @@ async fn dashboard(State(state): State<SharedState>, 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");
Expand Down