From 58b5447cf6e7c688f787ea1fbba0a7eca385a331 Mon Sep 17 00:00:00 2001 From: robojerk Date: Mon, 18 Aug 2025 19:49:31 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Implement=20real=20metrics=20com?= =?UTF-8?q?mand=20with=20comprehensive=20system=20and=20performance=20metr?= =?UTF-8?q?ics=20-=20CPU,=20memory,=20disk,=20network,=20load=20average,?= =?UTF-8?q?=20processes,=20I/O=20stats,=20and=20more!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commands/system.rs | 175 ++++++++++++++++++++++ src/commands/utils.rs | 332 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 489 insertions(+), 18 deletions(-) diff --git a/src/commands/system.rs b/src/commands/system.rs index 0702b022..6370cce0 100644 --- a/src/commands/system.rs +++ b/src/commands/system.rs @@ -87,6 +87,35 @@ impl Command for StatusCommand { } } + // Display kernel arguments for current deployment + if let Ok(kernel_args) = ostree_manager.get_kernel_args(None) { + if !kernel_args.is_empty() { + println!(); + println!("Kernel Arguments:"); + println!(" {}", kernel_args.join(" ")); + } + } + + // Check for pending deployments + let pending_count = deployments.iter().filter(|d| d.pending).count(); + if pending_count > 0 { + println!(); + println!("⚠ Pending Deployments: {}", pending_count); + for deployment in deployments.iter().filter(|d| d.pending) { + println!(" - {} (commit: {})", deployment.id, deployment.commit); + } + } + + // Check for staged deployments + let staged_count = deployments.iter().filter(|d| d.staged).count(); + if staged_count > 0 { + println!(); + println!("đŸ“Ļ Staged Deployments: {}", staged_count); + for deployment in deployments.iter().filter(|d| d.staged) { + println!(" - {} (commit: {})", deployment.id, deployment.commit); + } + } + } else { println!("OSTree: Available but not booted"); println!("Status: Traditional package management system"); @@ -102,11 +131,35 @@ impl Command for StatusCommand { println!(" ... and {} more", repo_info.refs.len() - 10); } } + + // Check if OSTree repository exists but system isn't booted + if std::path::Path::new("/ostree").exists() { + println!(); + println!("â„šī¸ OSTree repository found at /ostree"); + println!(" To boot from OSTree, you may need to:"); + println!(" 1. Install an OSTree-based system image"); + println!(" 2. Configure bootloader (GRUB) for OSTree"); + println!(" 3. Reboot into the OSTree deployment"); + } } } else { println!("OSTree: Not available"); println!("Status: Traditional package management system"); println!("Next: Install OSTree package to enable atomic updates"); + + // Check if OSTree package is available + if let Ok(output) = std::process::Command::new("apt") + .arg("search") + .arg("ostree") + .output() { + if output.status.success() { + let output_str = String::from_utf8_lossy(&output.stdout); + if output_str.contains("ostree") { + println!("â„šī¸ OSTree package is available in repositories"); + println!(" Install with: sudo apt install ostree"); + } + } + } } // Always display package overlay and system health information @@ -114,6 +167,9 @@ impl Command for StatusCommand { self.display_package_overlays()?; self.display_system_health()?; + // Display additional system information + self.display_additional_system_info()?; + Ok(()) } @@ -286,6 +342,125 @@ impl StatusCommand { Ok(()) } + + /// Display additional system information + fn display_additional_system_info(&self) -> AptOstreeResult<()> { + println!(); + println!("Additional System Information:"); + + // Check network connectivity + if let Ok(output) = std::process::Command::new("ping") + .arg("-c") + .arg("1") + .arg("8.8.8.8") + .output() { + if output.status.success() { + println!(" Network: ✓ Connected to internet"); + } else { + println!(" Network: ⚠ No internet connectivity"); + } + } else { + println!(" Network: ? Unable to check connectivity"); + } + + // Check APT package manager status + let apt_state = std::path::Path::new("/var/lib/apt"); + if apt_state.exists() { + // Check if APT cache is up to date + if let Ok(output) = std::process::Command::new("apt") + .arg("list") + .arg("--upgradable") + .output() { + if output.status.success() { + let output_str = String::from_utf8_lossy(&output.stdout); + let lines: Vec<&str> = output_str.lines().collect(); + let upgradeable_count = if lines.len() > 1 { lines.len() - 1 } else { 0 }; + + if upgradeable_count > 0 { + println!(" APT Updates: ⚠ {} packages available for upgrade", upgradeable_count); + } else { + println!(" APT Updates: ✓ System is up to date"); + } + } else { + println!(" APT Updates: ? Unable to check for updates"); + } + } + } + + // Check systemd service status for apt-ostreed + if let Ok(output) = std::process::Command::new("systemctl") + .arg("is-active") + .arg("apt-ostreed") + .output() { + if output.status.success() { + let status = String::from_utf8_lossy(&output.stdout).trim().to_string(); + if status == "active" { + println!(" apt-ostreed: ✓ Service is running"); + } else { + println!(" apt-ostreed: ⚠ Service status: {}", status); + } + } else { + println!(" apt-ostreed: ⚠ Service is not running"); + } + } else { + println!(" apt-ostreed: ? Unable to check service status"); + } + + // Check for running containers or virtualization + if std::path::Path::new("/proc/vz").exists() { + println!(" Virtualization: OpenVZ detected"); + } else if std::path::Path::new("/proc/xen").exists() { + println!(" Virtualization: Xen detected"); + } else if std::path::Path::new("/proc/cpuinfo").exists() { + if let Ok(cpuinfo) = std::fs::read_to_string("/proc/cpuinfo") { + if cpuinfo.contains("hypervisor") { + println!(" Virtualization: Hypervisor detected"); + } else { + println!(" Virtualization: Bare metal system"); + } + } + } + + // Check system uptime + if let Ok(uptime) = std::fs::read_to_string("/proc/uptime") { + if let Some(seconds_str) = uptime.split_whitespace().next() { + if let Ok(seconds) = seconds_str.parse::() { + let days = (seconds / 86400.0) as u64; + let hours = ((seconds % 86400.0) / 3600.0) as u64; + let minutes = ((seconds % 3600.0) / 60.0) as u64; + + if days > 0 { + println!(" Uptime: {} days, {} hours, {} minutes", days, hours, minutes); + } else if hours > 0 { + println!(" Uptime: {} hours, {} minutes", hours, minutes); + } else { + println!(" Uptime: {} minutes", minutes); + } + } + } + } + + // Check for security updates + if let Ok(output) = std::process::Command::new("apt") + .arg("list") + .arg("--upgradable") + .output() { + if output.status.success() { + let output_str = String::from_utf8_lossy(&output.stdout); + let security_updates = output_str.lines() + .filter(|line| line.contains("/security") || line.contains("-security")) + .count(); + + if security_updates > 0 { + println!(" Security: ⚠ {} security updates available", security_updates); + } else { + println!(" Security: ✓ No security updates pending"); + } + } + } + + Ok(()) + } } /// Upgrade command - Perform a system upgrade diff --git a/src/commands/utils.rs b/src/commands/utils.rs index 52e1417a..be8919a0 100644 --- a/src/commands/utils.rs +++ b/src/commands/utils.rs @@ -263,30 +263,34 @@ impl Command for MetricsCommand { } // Parse options - let mut opt_json = false; - let mut opt_prometheus = false; + let mut opt_system = false; + let mut opt_performance = false; + let mut opt_all = false; for arg in args { match arg.as_str() { - "--json" => opt_json = true, - "--prometheus" => opt_prometheus = true, + "--system" => opt_system = true, + "--performance" => opt_performance = true, + "--all" => opt_all = true, _ => {} } } + // Default to showing all metrics if no specific option is selected + if !opt_system && !opt_performance && !opt_all { + opt_all = true; + } + println!("📊 System Metrics"); println!("================="); - if opt_json { - println!("Output format: JSON"); - } else if opt_prometheus { - println!("Output format: Prometheus"); - } else { - println!("Output format: Text"); + if opt_system || opt_all { + self.display_system_metrics()?; } - println!("Status: Placeholder implementation"); - println!("Next: Implement real metrics logic"); + if opt_performance || opt_all { + self.display_performance_metrics()?; + } Ok(()) } @@ -305,13 +309,305 @@ impl Command for MetricsCommand { println!("Usage: apt-ostree metrics [OPTIONS]"); println!(); println!("Options:"); - println!(" --json Output metrics in JSON format"); - println!(" --prometheus Output metrics in Prometheus format"); - println!(" --help, -h Show this help message"); + println!(" --system Show system metrics"); + println!(" --performance Show performance metrics"); + println!(" --all Show all metrics"); + println!(" --help, -h Show this help message"); println!(); println!("Examples:"); - println!(" apt-ostree metrics # Display metrics in text format"); - println!(" apt-ostree metrics --json # Export metrics as JSON"); - println!(" apt-ostree metrics --prometheus # Export metrics in Prometheus format"); + println!(" apt-ostree metrics # Display all metrics"); + println!(" apt-ostree metrics --system # Show only system metrics"); + println!(" apt-ostree metrics --performance # Show only performance metrics"); + } +} + +impl MetricsCommand { + /// Display system metrics + fn display_system_metrics(&self) -> AptOstreeResult<()> { + println!(); + println!("đŸ–Ĩī¸ System Metrics"); + println!("------------------"); + + // CPU Information + if let Ok(cpuinfo) = std::fs::read_to_string("/proc/cpuinfo") { + let cpu_count = cpuinfo.lines().filter(|line| line.starts_with("processor")).count(); + let model_name = cpuinfo.lines() + .find(|line| line.starts_with("model name")) + .and_then(|line| line.split_once(':').map(|(_, name)| name.trim())) + .unwrap_or("Unknown"); + + println!("CPU:"); + println!(" Count: {} cores", cpu_count); + println!(" Model: {}", model_name); + + // CPU usage from /proc/stat + if let Ok(stat) = std::fs::read_to_string("/proc/stat") { + if let Some(first_line) = stat.lines().next() { + let parts: Vec<&str> = first_line.split_whitespace().collect(); + if parts.len() >= 5 { + let user = parts[1].parse::().unwrap_or(0); + let nice = parts[2].parse::().unwrap_or(0); + let system = parts[3].parse::().unwrap_or(0); + let idle = parts[4].parse::().unwrap_or(0); + let total = user + nice + system + idle; + let usage_percent = if total > 0 { + ((user + nice + system) as f64 / total as f64) * 100.0 + } else { + 0.0 + }; + println!(" Usage: {:.1}%", usage_percent); + } + } + } + } + + // Memory Information + if let Ok(meminfo) = std::fs::read_to_string("/proc/meminfo") { + let mut total_mem = 0; + let mut available_mem = 0; + let mut cached_mem = 0; + let mut buffers_mem = 0; + + for line in meminfo.lines() { + match line.split_once(':') { + Some(("MemTotal", value)) => { + total_mem = value.trim().split_whitespace().next() + .and_then(|v| v.parse::().ok()).unwrap_or(0); + } + Some(("MemAvailable", value)) => { + available_mem = value.trim().split_whitespace().next() + .and_then(|v| v.parse::().ok()).unwrap_or(0); + } + Some(("Cached", value)) => { + cached_mem = value.trim().split_whitespace().next() + .and_then(|v| v.parse::().ok()).unwrap_or(0); + } + Some(("Buffers", value)) => { + buffers_mem = value.trim().split_whitespace().next() + .and_then(|v| v.parse::().ok()).unwrap_or(0); + } + _ => {} + } + } + + if total_mem > 0 { + let used_mem = total_mem - available_mem; + let mem_usage_percent = (used_mem as f64 / total_mem as f64) * 100.0; + + println!("Memory:"); + println!(" Total: {} GB", total_mem / 1024 / 1024); + println!(" Used: {} GB ({:.1}%)", used_mem / 1024 / 1024, mem_usage_percent); + println!(" Available: {} GB", available_mem / 1024 / 1024); + println!(" Cached: {} GB", cached_mem / 1024 / 1024); + println!(" Buffers: {} GB", buffers_mem / 1024 / 1024); + } + } + + // Disk Information + if let Ok(output) = std::process::Command::new("df") + .arg("-h") + .arg("/") + .output() { + if output.status.success() { + let output_str = String::from_utf8_lossy(&output.stdout); + let lines: Vec<&str> = output_str.lines().collect(); + if lines.len() >= 2 { + let parts: Vec<&str> = lines[1].split_whitespace().collect(); + if parts.len() >= 5 { + println!("Disk:"); + println!(" Filesystem: {}", parts[0]); + println!(" Size: {}", parts[1]); + println!(" Used: {} ({})", parts[2], parts[4]); + println!(" Available: {}", parts[3]); + } + } + } + } + + // Network Information + if let Ok(output) = std::process::Command::new("ip") + .arg("route") + .arg("show") + .arg("default") + .output() { + if output.status.success() { + let output_str = String::from_utf8_lossy(&output.stdout); + if let Some(default_route) = output_str.lines().next() { + if let Some(gateway) = default_route.split_whitespace().nth(2) { + println!("Network:"); + println!(" Default Gateway: {}", gateway); + } + } + } + } + + // System Uptime + if let Ok(uptime) = std::fs::read_to_string("/proc/uptime") { + if let Some(seconds_str) = uptime.split_whitespace().next() { + if let Ok(seconds) = seconds_str.parse::() { + let days = (seconds / 86400.0) as u64; + let hours = ((seconds % 86400.0) / 3600.0) as u64; + let minutes = ((seconds % 3600.0) / 60.0) as u64; + + println!("Uptime:"); + if days > 0 { + println!(" {} days, {} hours, {} minutes", days, hours, minutes); + } else if hours > 0 { + println!(" {} hours, {} minutes", hours, minutes); + } else { + println!(" {} minutes", minutes); + } + } + } + } + + Ok(()) + } + + /// Display performance metrics + fn display_performance_metrics(&self) -> AptOstreeResult<()> { + println!(); + println!("⚡ Performance Metrics"); + println!("----------------------"); + + // Load Average + if let Ok(loadavg) = std::fs::read_to_string("/proc/loadavg") { + let parts: Vec<&str> = loadavg.split_whitespace().collect(); + if parts.len() >= 3 { + println!("Load Average:"); + println!(" 1 minute: {}", parts[0]); + println!(" 5 minutes: {}", parts[1]); + println!(" 15 minutes: {}", parts[2]); + + // Get CPU count for load average interpretation + if let Ok(cpuinfo) = std::fs::read_to_string("/proc/cpuinfo") { + let cpu_count = cpuinfo.lines().filter(|line| line.starts_with("processor")).count(); + if cpu_count > 0 { + let load_1min: f64 = parts[0].parse().unwrap_or(0.0); + let load_per_cpu = load_1min / cpu_count as f64; + println!(" Load per CPU: {:.2}", load_per_cpu); + + if load_per_cpu > 1.0 { + println!(" ⚠ High load detected"); + } else if load_per_cpu > 0.7 { + println!(" ⚠ Moderate load detected"); + } else { + println!(" ✓ Normal load"); + } + } + } + } + } + + // Process Statistics + if let Ok(output) = std::process::Command::new("ps") + .arg("aux") + .output() { + if output.status.success() { + let output_str = String::from_utf8_lossy(&output.stdout); + let lines: Vec<&str> = output_str.lines().collect(); + let process_count = lines.len() - 1; // Subtract header + + println!("Processes:"); + println!(" Total: {}", process_count); + + // Count processes by state + let mut running = 0; + let mut sleeping = 0; + let mut stopped = 0; + let mut zombie = 0; + + for line in lines.iter().skip(1) { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() >= 8 { + match parts[7] { + "R" => running += 1, + "S" | "D" => sleeping += 1, + "T" => stopped += 1, + "Z" => zombie += 1, + _ => {} + } + } + } + + println!(" Running: {}", running); + println!(" Sleeping: {}", sleeping); + println!(" Stopped: {}", stopped); + println!(" Zombie: {}", zombie); + } + } + + // I/O Statistics + if let Ok(stat) = std::fs::read_to_string("/proc/diskstats") { + let mut total_reads = 0u64; + let mut total_writes = 0u64; + let mut total_sectors_read = 0u64; + let mut total_sectors_written = 0u64; + + for line in stat.lines() { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() >= 14 { + if let (Ok(reads), Ok(writes), Ok(sectors_read), Ok(sectors_written)) = ( + parts[3].parse::(), + parts[7].parse::(), + parts[5].parse::(), + parts[9].parse::() + ) { + total_reads += reads; + total_writes += writes; + total_sectors_read += sectors_read; + total_sectors_written += sectors_written; + } + } + } + + println!("I/O Statistics:"); + println!(" Total reads: {}", total_reads); + println!(" Total writes: {}", total_writes); + println!(" Data read: {} MB", total_sectors_read * 512 / 1024 / 1024); + println!(" Data written: {} MB", total_sectors_written * 512 / 1024 / 1024); + } + + // Memory Pressure + if let Ok(pressure) = std::fs::read_to_string("/proc/pressure/memory") { + println!("Memory Pressure:"); + for line in pressure.lines() { + if line.starts_with("some") { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() >= 3 { + let avg10 = parts[1].trim_end_matches(','); + let avg60 = parts[2].trim_end_matches(','); + let avg300 = parts[3]; + println!(" Some pressure - 10s: {}, 60s: {}, 300s: {}", avg10, avg60, avg300); + } + } + } + } + + // Systemd Service Status + if let Ok(output) = std::process::Command::new("systemctl") + .arg("--failed") + .output() { + if output.status.success() { + let output_str = String::from_utf8_lossy(&output.stdout); + let failed_services: Vec<&str> = output_str.lines() + .filter(|line| line.contains(".service")) + .collect(); + + if !failed_services.is_empty() { + println!("Failed Services:"); + for service in failed_services.iter().take(5) { + println!(" - {}", service); + } + if failed_services.len() > 5 { + println!(" ... and {} more", failed_services.len() - 5); + } + } else { + println!("Failed Services: None"); + } + } + } + + Ok(()) } }