diff --git a/WatcherAgent/src/main.rs b/WatcherAgent/src/main.rs index 231e3af..3e79d40 100644 --- a/WatcherAgent/src/main.rs +++ b/WatcherAgent/src/main.rs @@ -323,17 +323,62 @@ impl MetricsCollector { // Disk let disk = self.sys.disks().first(); - let (disk_size, disk_used) = if let Some(d) = disk { - let total = d.total_space(); - let available = d.available_space(); - ( - (total as f64) / 1024.0 / 1024.0 / 1024.0, // Convert to GB - (total - available) as f64 / total as f64 * 100.0, - ) - } else { - (0.0, 0.0) - }; + // In collect_metrics(): + let (disk_size, disk_usage, disk_temp) = { + let mut total_size = 0u64; + let mut total_used = 0u64; + let mut temp = 0.0; + let mut count = 0; + for disk in self.sys.disks() { + total_size += disk.total_space(); + total_used += disk.total_space() - disk.available_space(); + count += 1; + } + + // Disk temperature (Linux only) + #[cfg(target_os = "linux")] + { + if let Ok(dir) = fs::read_dir("/sys/block") { + for entry in dir.flatten() { + if let Some(disk_name) = entry.file_name().to_str() { + if disk_name.starts_with("sd") || disk_name.starts_with("nvme") { + let temp_path = format!( + "/sys/block/{}/device/hwmon/hwmon*/temp1_input", + disk_name + ); + if let Ok(paths) = glob::glob(&temp_path) { + for path in paths.flatten() { + if let Ok(content) = fs::read_to_string(path) { + if let Ok(t) = content.trim().parse::() { + temp += t / 1000.0; // Convert millidegrees + break; + } + } + } + } + } + } + } + } + } + + let size_gb = if count > 0 { + (total_size as f64) / 1024.0 / 1024.0 / 1024.0 + } else { + 0.0 + }; + + let usage = if total_size > 0 { + (total_used as f64 / total_size as f64) * 100.0 + } else { + 0.0 + }; + + let avg_temp = if count > 0 { temp / count as f64 } else { 0.0 }; + + (size_gb, usage, avg_temp) + }; // GPU (NVIDIA) let (gpu_temp, gpu_load, vram_used, vram_total) = if let Some(nvml) = &self.nvml { if let Ok(device) = nvml.device_by_index(0) { @@ -381,7 +426,7 @@ impl MetricsCollector { ram_load, ram_size, disk_size, - disk_usage: disk_used, + disk_usage: disk_usage, disk_temp: 0.0, // not supported net_in: net_in_bits, net_out: net_out_bits, @@ -390,39 +435,103 @@ impl MetricsCollector { } fn get_cpu_temp() -> Option { - let output = Command::new("sensors").output().ok()?; - let stdout = String::from_utf8_lossy(&output.stdout); - for line in stdout.lines() { - if line.to_lowercase().contains("package id") || line.to_lowercase().contains("cpu temp") { - if let Some(temp_str) = line.split_whitespace().find(|s| s.contains("°C")) { - let number: String = temp_str - .chars() - .filter(|c| c.is_digit(10) || *c == '.') - .collect(); - return number.parse::().ok(); + #[cfg(target_os = "linux")] + { + // Linux: sensors command or sysfs + if let Ok(output) = Command::new("sensors").output() { + let stdout = String::from_utf8_lossy(&output.stdout); + for line in stdout.lines() { + if line.to_lowercase().contains("package id") + || line.to_lowercase().contains("cpu temp") + { + if let Some(temp_str) = line.split_whitespace().find(|s| s.contains("°C")) { + let number: String = temp_str + .chars() + .filter(|c| c.is_ascii_digit() || *c == '.') + .collect(); + return number.parse::().ok(); + } + } + } + } + + // Fallback to sysfs (common path for Intel/AMD) + if let Ok(content) = fs::read_to_string("/sys/class/thermal/thermal_zone0/temp") { + if let Ok(temp) = content.trim().parse::() { + return Some(temp / 1000.0); // Convert millidegrees to degrees } } } + + #[cfg(target_os = "windows")] + { + // Windows: WMI query + let output = Command::new("wmic") + .args(&["cpu", "get", "Temperature", "/Value"]) + .output() + .ok()?; + + let stdout = String::from_utf8_lossy(&output.stdout); + for line in stdout.lines() { + if line.starts_with("Temperature=") { + if let Ok(temp) = line.replace("Temperature=", "").trim().parse::() { + return Some(temp); // Returns in Celsius + } + } + } + } + None } fn get_network_traffic() -> Option<(u64, u64)> { - let content = fs::read_to_string("/proc/net/dev").ok()?; - let mut rx_total = 0u64; - let mut tx_total = 0u64; + #[cfg(target_os = "linux")] + { + let content = fs::read_to_string("/proc/net/dev").ok()?; + let mut rx_total = 0u64; + let mut tx_total = 0u64; - for line in content.lines().skip(2) { - let parts: Vec<&str> = line.split_whitespace().collect(); - if parts.len() < 17 { - continue; + for line in content.lines().skip(2) { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() < 10 || parts[0].ends_with(":") { + continue; + } + if parts[0].contains("lo:") { + continue; + } + rx_total += parts[1].parse::().unwrap_or(0); + tx_total += parts[9].parse::().unwrap_or(0); } - if parts[0].contains("lo:") { - continue; - } - rx_total += parts[1].parse::().ok()?; - tx_total += parts[9].parse::().ok()?; + Some((rx_total, tx_total)) + } + + #[cfg(target_os = "windows")] + { + use std::process::Stdio; + let output = Command::new("netstat") + .args(&["-e"]) + .stdout(Stdio::piped()) + .output() + .ok()?; + + let stdout = String::from_utf8_lossy(&output.stdout); + let mut lines = stdout.lines(); + + // Find the line with statistics + while let Some(line) = lines.next() { + if line.contains("Bytes") { + if let Some(stats_line) = lines.next() { + let parts: Vec<&str> = stats_line.split_whitespace().collect(); + if parts.len() >= 2 { + let rx = parts[0].parse::().unwrap_or(0); + let tx = parts[1].parse::().unwrap_or(0); + return Some((rx, tx)); + } + } + } + } + None } - Some((rx_total, tx_total)) } #[tokio::main]