|
| 1 | +#[cfg(target_os = "linux")] |
| 2 | +use std::fs; |
| 3 | + |
| 4 | +#[derive(Debug, Clone)] |
| 5 | +pub struct MemoryStats { |
| 6 | + pub process_rss_mb: f64, |
| 7 | + pub process_swap_mb: f64, |
| 8 | + pub system_free_mb: f64, |
| 9 | + pub system_total_mb: f64, |
| 10 | + pub system_cached_mb: f64, |
| 11 | +} |
| 12 | + |
| 13 | +impl MemoryStats { |
| 14 | + /// Format memory stats in a compact format for display |
| 15 | + /// Example: "Mem: 1.2G/4G (proc: 250M+50Msw cache: 1.5G)" |
| 16 | + pub fn format_compact(&self) -> String { |
| 17 | + let system_used_mb = self.system_total_mb - self.system_free_mb - self.system_cached_mb; |
| 18 | + |
| 19 | + format!( |
| 20 | + "Mem: {}/{} (proc: {}{} cache: {})", |
| 21 | + format_mb(system_used_mb), |
| 22 | + format_mb(self.system_total_mb), |
| 23 | + format_mb(self.process_rss_mb), |
| 24 | + if self.process_swap_mb > 0.0 { |
| 25 | + format!("+{}sw", format_mb(self.process_swap_mb)) |
| 26 | + } else { |
| 27 | + String::new() |
| 28 | + }, |
| 29 | + format_mb(self.system_cached_mb), |
| 30 | + ) |
| 31 | + } |
| 32 | +} |
| 33 | + |
| 34 | +/// Format MB value to human-readable format (M or G) |
| 35 | +fn format_mb(mb: f64) -> String { |
| 36 | + if mb >= 1024.0 { |
| 37 | + format!("{:.1}G", mb / 1024.0) |
| 38 | + } else { |
| 39 | + format!("{:.0}M", mb) |
| 40 | + } |
| 41 | +} |
| 42 | + |
| 43 | +/// Get memory statistics from the system |
| 44 | +/// Returns None if reading fails or not on Linux |
| 45 | +pub fn get_memory_stats() -> Option<MemoryStats> { |
| 46 | + #[cfg(target_os = "linux")] |
| 47 | + { |
| 48 | + get_memory_stats_linux() |
| 49 | + } |
| 50 | + |
| 51 | + #[cfg(not(target_os = "linux"))] |
| 52 | + { |
| 53 | + None |
| 54 | + } |
| 55 | +} |
| 56 | + |
| 57 | +#[cfg(target_os = "linux")] |
| 58 | +fn get_memory_stats_linux() -> Option<MemoryStats> { |
| 59 | + // Read process memory from /proc/self/status |
| 60 | + let (process_rss_mb, process_swap_mb) = read_process_memory()?; |
| 61 | + |
| 62 | + // Read system memory from /proc/meminfo |
| 63 | + let (system_total_mb, system_free_mb, system_cached_mb) = read_system_memory()?; |
| 64 | + |
| 65 | + Some(MemoryStats { |
| 66 | + process_rss_mb, |
| 67 | + process_swap_mb, |
| 68 | + system_free_mb, |
| 69 | + system_total_mb, |
| 70 | + system_cached_mb, |
| 71 | + }) |
| 72 | +} |
| 73 | + |
| 74 | +#[cfg(target_os = "linux")] |
| 75 | +fn read_process_memory() -> Option<(f64, f64)> { |
| 76 | + let status = fs::read_to_string("/proc/self/status").ok()?; |
| 77 | + |
| 78 | + let mut rss_kb = 0u64; |
| 79 | + let mut swap_kb = 0u64; |
| 80 | + |
| 81 | + for line in status.lines() { |
| 82 | + if line.starts_with("VmRSS:") { |
| 83 | + // VmRSS: 123456 kB |
| 84 | + rss_kb = line.split_whitespace().nth(1)?.parse().ok()?; |
| 85 | + } else if line.starts_with("VmSwap:") { |
| 86 | + // VmSwap: 12345 kB |
| 87 | + swap_kb = line.split_whitespace().nth(1)?.parse().ok()?; |
| 88 | + } |
| 89 | + } |
| 90 | + |
| 91 | + let rss_mb = rss_kb as f64 / 1024.0; |
| 92 | + let swap_mb = swap_kb as f64 / 1024.0; |
| 93 | + |
| 94 | + Some((rss_mb, swap_mb)) |
| 95 | +} |
| 96 | + |
| 97 | +#[cfg(target_os = "linux")] |
| 98 | +fn read_system_memory() -> Option<(f64, f64, f64)> { |
| 99 | + let meminfo = fs::read_to_string("/proc/meminfo").ok()?; |
| 100 | + |
| 101 | + let mut total_kb = 0u64; |
| 102 | + let mut free_kb = 0u64; |
| 103 | + let mut available_kb = 0u64; |
| 104 | + let mut cached_kb = 0u64; |
| 105 | + let mut buffers_kb = 0u64; |
| 106 | + |
| 107 | + for line in meminfo.lines() { |
| 108 | + let parts: Vec<&str> = line.split_whitespace().collect(); |
| 109 | + if parts.len() < 2 { |
| 110 | + continue; |
| 111 | + } |
| 112 | + |
| 113 | + let value: u64 = parts[1].parse().ok()?; |
| 114 | + |
| 115 | + match parts[0] { |
| 116 | + "MemTotal:" => total_kb = value, |
| 117 | + "MemFree:" => free_kb = value, |
| 118 | + "MemAvailable:" => available_kb = value, |
| 119 | + "Cached:" => cached_kb = value, |
| 120 | + "Buffers:" => buffers_kb = value, |
| 121 | + _ => {} |
| 122 | + } |
| 123 | + } |
| 124 | + |
| 125 | + // Use MemAvailable if present (more accurate), otherwise use MemFree |
| 126 | + let effective_free_kb = if available_kb > 0 { |
| 127 | + available_kb |
| 128 | + } else { |
| 129 | + free_kb |
| 130 | + }; |
| 131 | + |
| 132 | + // Cache includes buffers and cached |
| 133 | + let effective_cached_kb = cached_kb + buffers_kb; |
| 134 | + |
| 135 | + let total_mb = total_kb as f64 / 1024.0; |
| 136 | + let free_mb = effective_free_kb as f64 / 1024.0; |
| 137 | + let cached_mb = effective_cached_kb as f64 / 1024.0; |
| 138 | + |
| 139 | + Some((total_mb, free_mb, cached_mb)) |
| 140 | +} |
0 commit comments