Improve dashboard display and fix service issues
- Remove unreachable descriptions from failed nginx sites - Show complete site URLs instead of truncating at first dot - Implement service-specific disk quotas (docker: 4GB, immich: 4GB, others: 1-2GB) - Truncate process names to show only executable name without full path - Display only highest C-state instead of all C-states for cleaner output - Format system RAM as xxxMB/GB (totalGB) to match services format
This commit is contained in:
@@ -303,6 +303,9 @@ impl ServiceCollector {
|
||||
|
||||
async fn get_service_disk_quota(&self, service: &str) -> Result<f32, CollectorError> {
|
||||
// Check systemd service properties for NixOS hardening-related disk restrictions
|
||||
let mut private_tmp = false;
|
||||
let mut protect_system = false;
|
||||
|
||||
let systemd_output = Command::new("/run/current-system/sw/bin/systemctl")
|
||||
.args(["show", service, "--property=PrivateTmp,ProtectHome,ProtectSystem,ReadOnlyPaths,InaccessiblePaths,BindPaths,BindReadOnlyPaths", "--no-pager"])
|
||||
.stdout(Stdio::piped())
|
||||
@@ -315,8 +318,6 @@ impl ServiceCollector {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
// Parse systemd properties that might indicate disk restrictions
|
||||
let mut private_tmp = false;
|
||||
let mut protect_system = false;
|
||||
let mut readonly_paths = Vec::new();
|
||||
|
||||
for line in stdout.lines() {
|
||||
@@ -328,39 +329,33 @@ impl ServiceCollector {
|
||||
readonly_paths.push(paths.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// If service has significant restrictions, it might have implicit disk limits
|
||||
// This is heuristic-based since systemd doesn't have direct disk quotas
|
||||
if private_tmp && protect_system {
|
||||
// Heavily sandboxed services might have practical disk limits
|
||||
// Return a conservative estimate based on typical service needs
|
||||
return Ok(1.0); // 1 GB as reasonable limit for sandboxed services
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for service-specific disk configurations in NixOS
|
||||
match service {
|
||||
"docker" => {
|
||||
// Docker might have storage driver limits in NixOS config
|
||||
if let Ok(limit) = self.get_docker_storage_quota().await {
|
||||
return Ok(limit);
|
||||
// Check for service-specific disk configurations - use service-appropriate defaults
|
||||
let service_quota = match service {
|
||||
"docker" => 4.0, // Docker containers need more space
|
||||
"gitea" => 1.0, // Gitea repositories, but database is external
|
||||
"postgresql" | "postgres" => 1.0, // Database storage
|
||||
"mysql" | "mariadb" => 1.0, // Database storage
|
||||
"immich-server" => 4.0, // Photo storage app needs more space
|
||||
"unifi" => 2.0, // Network management with logs and configs
|
||||
"vaultwarden" => 1.0, // Password manager
|
||||
"gitea-runner-default" => 1.0, // CI/CD runner
|
||||
"nginx" => 1.0, // Web server
|
||||
"mosquitto" => 1.0, // MQTT broker
|
||||
"redis-immich" => 1.0, // Redis cache
|
||||
_ => {
|
||||
// Default based on sandboxing - sandboxed services get smaller quotas
|
||||
if private_tmp && protect_system {
|
||||
1.0 // 1 GB for sandboxed services
|
||||
} else {
|
||||
2.0 // 2 GB for non-sandboxed services
|
||||
}
|
||||
},
|
||||
"postgresql" | "postgres" => {
|
||||
// PostgreSQL might have tablespace or data directory limits
|
||||
// Check for database-specific storage configuration
|
||||
},
|
||||
"mysql" | "mariadb" => {
|
||||
// MySQL might have data directory size limits
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// No quota found
|
||||
Err(CollectorError::ParseError {
|
||||
message: format!("No disk quota found for service {}", service),
|
||||
})
|
||||
Ok(service_quota)
|
||||
}
|
||||
|
||||
async fn check_filesystem_quota(&self, path: &str) -> Result<f32, CollectorError> {
|
||||
@@ -1282,7 +1277,7 @@ impl Collector for ServiceCollector {
|
||||
let (site_status, site_description) = match (latency, is_healthy) {
|
||||
(Some(_ms), true) => (ServiceStatus::Running, None),
|
||||
(Some(_ms), false) => (ServiceStatus::Stopped, None), // Show error status but no description
|
||||
(None, _) => (ServiceStatus::Stopped, Some(vec!["unreachable".to_string()])),
|
||||
(None, _) => (ServiceStatus::Stopped, None), // No description for unreachable sites
|
||||
};
|
||||
|
||||
// Update counters based on site status
|
||||
|
||||
@@ -204,29 +204,36 @@ impl SystemCollector {
|
||||
order_a.cmp(&order_b)
|
||||
});
|
||||
|
||||
// Format C-states as description lines (2 C-states per row)
|
||||
let mut result = Vec::new();
|
||||
let mut current_line = Vec::new();
|
||||
// Find the highest C-state with significant usage (>= 0.1%)
|
||||
let mut highest_cstate = None;
|
||||
let mut highest_order = -1;
|
||||
|
||||
for (name, time) in cstate_times {
|
||||
let percent = (time as f32 / total_time as f32) * 100.0;
|
||||
if percent >= 0.1 { // Only show states with at least 0.1% time
|
||||
current_line.push(format!("{}: {:.1}%", name, percent));
|
||||
for (name, time) in &cstate_times {
|
||||
let percent = (*time as f32 / total_time as f32) * 100.0;
|
||||
if percent >= 0.1 { // Only consider states with at least 0.1% time
|
||||
let order = match name.as_str() {
|
||||
"POLL" => 0,
|
||||
"C1" => 1,
|
||||
"C1E" => 2,
|
||||
"C3" => 3,
|
||||
"C6" => 4,
|
||||
"C7s" => 5,
|
||||
"C8" => 6,
|
||||
"C9" => 7,
|
||||
"C10" => 8,
|
||||
_ => -1,
|
||||
};
|
||||
|
||||
// Split into rows when we have 2 items
|
||||
if current_line.len() == 2 {
|
||||
result.push(current_line.join(", "));
|
||||
current_line.clear();
|
||||
if order > highest_order {
|
||||
highest_order = order;
|
||||
highest_cstate = Some(format!("{}: {:.1}%", name, percent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add remaining items as final line
|
||||
if !current_line.is_empty() {
|
||||
result.push(current_line.join(", "));
|
||||
if let Some(cstate) = highest_cstate {
|
||||
return Some(vec![format!("C-State: {}", cstate)]);
|
||||
}
|
||||
|
||||
return Some(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,7 +288,13 @@ impl SystemCollector {
|
||||
let command = fields[10];
|
||||
// Skip kernel threads (in brackets) and low CPU processes
|
||||
if !command.starts_with('[') && cpu_percent.parse::<f32>().unwrap_or(0.0) > 0.1 {
|
||||
return Some(format!("{} {:.1}%", command, cpu_percent.parse::<f32>().unwrap_or(0.0)));
|
||||
// Extract just the process name from the full path
|
||||
let process_name = if let Some(last_slash) = command.rfind('/') {
|
||||
&command[last_slash + 1..]
|
||||
} else {
|
||||
command
|
||||
};
|
||||
return Some(format!("{} {:.1}%", process_name, cpu_percent.parse::<f32>().unwrap_or(0.0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,7 +321,13 @@ impl SystemCollector {
|
||||
let command = fields[10];
|
||||
// Skip kernel threads (in brackets) and low memory processes
|
||||
if !command.starts_with('[') && mem_percent.parse::<f32>().unwrap_or(0.0) > 0.1 {
|
||||
return Some(format!("{} {:.1}%", command, mem_percent.parse::<f32>().unwrap_or(0.0)));
|
||||
// Extract just the process name from the full path
|
||||
let process_name = if let Some(last_slash) = command.rfind('/') {
|
||||
&command[last_slash + 1..]
|
||||
} else {
|
||||
command
|
||||
};
|
||||
return Some(format!("{} {:.1}%", process_name, mem_percent.parse::<f32>().unwrap_or(0.0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user