From abfa0b6fc03daa6a80aba28f7dc01f1026c1e0bb Mon Sep 17 00:00:00 2001 From: donpat1to Date: Fri, 8 Aug 2025 18:59:57 +0200 Subject: [PATCH] modulized everthing --- .github/workflows/build.yml | 2 +- WatcherAgent/src/api.rs | 11 +- WatcherAgent/src/hardware.rs | 79 ----- WatcherAgent/src/hardware/cpu.rs | 256 ++++++++++++++ WatcherAgent/src/hardware/disk.rs | 113 ++++++ WatcherAgent/src/hardware/gpu.rs | 41 +++ WatcherAgent/src/hardware/memory.rs | 25 ++ WatcherAgent/src/hardware/mod.rs | 35 ++ WatcherAgent/src/hardware/network.rs | 191 ++++++++++ WatcherAgent/src/metrics.rs | 497 ++------------------------- WatcherAgent/src/models.rs | 10 +- 11 files changed, 690 insertions(+), 570 deletions(-) delete mode 100644 WatcherAgent/src/hardware.rs create mode 100644 WatcherAgent/src/hardware/mod.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7f9b583..f60912c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -123,7 +123,7 @@ jobs: docker-build: name: Build Linux Docker Image - needs: [native-build, windows-cross] + needs: [native-build, windows-cross, detect-project] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/WatcherAgent/src/api.rs b/WatcherAgent/src/api.rs index 044ce3f..baf7fe6 100644 --- a/WatcherAgent/src/api.rs +++ b/WatcherAgent/src/api.rs @@ -1,5 +1,6 @@ use std::time::Duration; +use crate::hardware::HardwareInfo; use crate::models::{HardwareDto, HeartbeatDto, IdResponse, MetricDto, RegistrationDto}; use anyhow::Result; use reqwest::{Client, StatusCode}; @@ -20,16 +21,16 @@ pub async fn register_with_server(base_url: &str) -> Result<(i32, String), Box anyhow::Result { - let mut sys = System::new_all(); - sys.refresh_cpu_all(); - sys.refresh_memory(); - - let cpus = sys.cpus(); - let cpu_type = if !cpus.is_empty() { - cpus[0].brand().to_string() - } else { - "Unknown CPU".to_string() - }; - let cpu_cores = cpus.len() as i32; - let ram_bytes = sys.total_memory() as f64; - let gpu_type = Self::detect_gpu_name(); - let ip_address = local_ip_address::local_ip()?.to_string(); - - Ok(Self { - cpu_type, - cpu_cores, - gpu_type, - ram_size: ram_bytes, - ip_address, - }) - } - - fn detect_gpu_name() -> String { - // First try NVML (NVIDIA Management Library) - if let Some(name) = Self::try_nvml_gpu_name() { - return name; - } - - // Fallback to OS-specific commands - #[cfg(target_os = "linux")] - { - if let Ok(output) = std::process::Command::new("lshw") - .args(&["-C", "display"]) - .output() - { - if let Some(name) = String::from_utf8_lossy(&output.stdout) - .lines() - .find(|l| l.contains("product:")) - .map(|l| l.trim().replace("product:", "").trim().to_string()) - { - return name; - } - } - } - - #[cfg(target_os = "windows")] - { - if let Ok(output) = std::process::Command::new("wmic") - .args(&["path", "win32_VideoController", "get", "name"]) - .output() - { - if let Some(name) = String::from_utf8_lossy(&output.stdout) - .lines() - .nth(1) - .map(|s| s.trim().to_string()) - { - return name; - } - } - } - - // If all else fails - "Unknown GPU".to_string() - } - - fn try_nvml_gpu_name() -> Option { - let nvml = Nvml::init().ok()?; - let device = nvml.device_by_index(0).ok()?; - device.name().ok().map(|s| s.to_string()) - } -} diff --git a/WatcherAgent/src/hardware/cpu.rs b/WatcherAgent/src/hardware/cpu.rs index e69de29..1774bc3 100644 --- a/WatcherAgent/src/hardware/cpu.rs +++ b/WatcherAgent/src/hardware/cpu.rs @@ -0,0 +1,256 @@ +use anyhow::Result; +use std::error::Error; +//use std::result::Result; +use sysinfo::System; + +#[derive(Debug)] +pub struct CpuInfo { + pub name: Option, + pub cores: Option, + pub current_load: Option, + pub current_temp: Option, +} + +pub async fn get_cpu_info() -> Result> { + let mut sys = System::new(); + sys.refresh_cpu_all(); + + let cpus = sys.cpus(); + Ok(CpuInfo { + name: Some( + cpus.first() + .map(|c| c.brand().to_string()) + .unwrap_or_default(), + ), + cores: Some(cpus.len() as i32), + current_load: get_cpu_load(&mut sys).await.ok(), + current_temp: get_cpu_temp().await.ok(), + }) +} + +pub async fn get_cpu_load(sys: &mut System) -> Result> { + sys.refresh_cpu_all(); + tokio::task::yield_now().await; // Allow other tasks to run + Ok(sys.global_cpu_usage() as f64) +} + +pub async fn get_cpu_temp() -> Result> { + println!("Attempting to get CPU temperature..."); + + #[cfg(target_os = "linux")] + { + use std::fs; + use std::process::Command; + println!(""); + if let Ok(output) = Command::new("sensors").output() { + let stdout = String::from_utf8_lossy(&output.stdout); + for line in stdout.lines() { + if line.contains("Package id") || line.contains("Tdie") || line.contains("CPU Temp") + { + if let Some(temp_str) = line + .split('+') + .nth(1) + .and_then(|s| s.split_whitespace().next()) + { + if let Ok(temp) = temp_str.replace("°C", "").parse::() { + return Some(temp); + } + } + } + } + } + + // 2. Sysfs (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); + } + } + + // 3. Alternative Sysfs-Pfade + let paths = [ + "/sys/class/hwmon/hwmontemp1_input", + "/sys/class/hwmon/hwmondevice/temp1_input", + ]; + + for path_pattern in &paths { + if let Ok(paths) = glob::glob(path_pattern) { + for path in paths.flatten() { + if let Ok(content) = fs::read_to_string(&path) { + if let Ok(temp) = content.trim().parse::() { + return Some(temp / 1000.0); + } + } + } + } + } + + Err(anyhow::anyhow!( + "Could not find CPU temperature using sensors or sysfs" + )) + } + + #[cfg(target_os = "windows")] + fn failed(hr: winapi::shared::winerror::HRESULT) -> bool { + hr < 0 + } + + #[cfg(target_os = "windows")] + { + use com::runtime::init_runtime; + use com::sys::CLSCTX_INPROC_SERVER; + use widestring::U16CString; + use winapi::shared::rpcdce::*; + use winapi::shared::wtypes::VT_I4; + use winapi::um::oaidl::VARIANT; + use winapi::um::objidlbase::EOAC_NONE; + use winapi::um::{combaseapi, wbemcli}; + + init_runtime().ok(); + + unsafe { + use anyhow::Ok; + + let mut locator: *mut wbemcli::IWbemLocator = std::ptr::null_mut(); + let hr = combaseapi::CoCreateInstance( + &wbemcli::CLSID_WbemLocator, + std::ptr::null_mut(), + CLSCTX_INPROC_SERVER, + &wbemcli::IID_IWbemLocator, + &mut locator as *mut _ as *mut _, + ); + + if hr != 0 { + eprintln!("Failed to create WbemLocator (HRESULT: {})", hr); + return Err(("Failed to create WbemLocator").into()); + } + + let mut services: *mut wbemcli::IWbemServices = std::ptr::null_mut(); + let namespace = U16CString::from_str("root\\cimv2").unwrap(); // Changed to more common namespace + let hr = (*locator).ConnectServer( + namespace.as_ptr().cast_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + 0, + std::ptr::null_mut(), + std::ptr::null_mut(), + &mut services, + ); + + if hr != 0 { + eprintln!("Failed to connect to WMI (HRESULT: {})", hr); + (*locator).Release(); + return Err(("Failed to connect to WMI").into()); + } + + // Set security levels + let hr = combaseapi::CoSetProxyBlanket( + services as *mut _, + RPC_C_AUTHN_WINNT, + RPC_C_AUTHZ_NONE, + std::ptr::null_mut(), + RPC_C_AUTHN_LEVEL_CALL, + RPC_C_IMP_LEVEL_IMPERSONATE, + std::ptr::null_mut(), + EOAC_NONE, + ); + + if hr != 0 { + eprintln!("Failed to set proxy blanket (HRESULT: {})", hr); + (*services).Release(); + (*locator).Release(); + return Err(("Failed to set proxy blanket").into()); + } + + // Try different temperature queries - some systems might have different WMI classes + let queries = [ + "SELECT * FROM Win32_PerfFormattedData_Counters_ThermalZoneInformation", + "SELECT * FROM MSAcpi_ThermalZoneTemperature", + "SELECT * FROM Win32_TemperatureProbe", + ]; + + let mut result = None; + + for query_str in queries.iter() { + let query = U16CString::from_str(query_str).unwrap(); + let mut enumerator: *mut wbemcli::IEnumWbemClassObject = std::ptr::null_mut(); + let hr = (*services).ExecQuery( + U16CString::from_str("WQL").unwrap().as_ptr().cast_mut(), + query.as_ptr().cast_mut(), + wbemcli::WBEM_FLAG_FORWARD_ONLY as i32, + std::ptr::null_mut(), + &mut enumerator, + ); + + if hr != 0 { + continue; // Try next query if this one fails + } + + let mut obj: *mut wbemcli::IWbemClassObject = std::ptr::null_mut(); + let mut returned = 0; + let hr = (*enumerator).Next( + wbemcli::WBEM_INFINITE as i32, // Fixed: cast directly to i32 + 1, + &mut obj, + &mut returned, + ); + + if failed(hr) { + eprintln!("Failed to enumerate WMI objects (HRESULT: {})", hr); + (*enumerator).Release(); + continue; + } + + if returned == 0 { + // No more items + (*enumerator).Release(); + continue; + } + + if hr == 0 && returned > 0 { + let mut variant = std::mem::zeroed::(); + // Try different possible property names + let property_names = ["CurrentTemperature", "Temperature", "CurrentReading"]; + + for prop in property_names.iter() { + let hr = (*obj).Get( + U16CString::from_str(prop).unwrap().as_ptr(), + 0, + &mut variant, + std::ptr::null_mut(), + std::ptr::null_mut(), + ); + + if hr == 0 && variant.n1.n2().vt as u32 == VT_I4 { + let temp_kelvin = *variant.n1.n2().n3.intVal() as f32 / 10.0; + result = Some(temp_kelvin - 273.15); // Convert to Celsius + break; + } + } + + (*obj).Release(); + (*enumerator).Release(); + if result.is_some() { + break; + } + } + + if !enumerator.is_null() { + (*enumerator).Release(); + } + } + + (*services).Release(); + (*locator).Release(); + + return Ok(result.unwrap() as f64).map_err(|e| e.into()); + } + } + + #[cfg(not(any(target_os = "linux", target_os = "windows")))] + { + println!("CPU temperature retrieval not supported on this OS."); + Err(anyhow::anyhow!("CPU temperature retrieval not supported on this OS").into()) + } +} diff --git a/WatcherAgent/src/hardware/disk.rs b/WatcherAgent/src/hardware/disk.rs index e69de29..bbf7aec 100644 --- a/WatcherAgent/src/hardware/disk.rs +++ b/WatcherAgent/src/hardware/disk.rs @@ -0,0 +1,113 @@ +use anyhow::Result; +use sysinfo::{Components, Disks, System}; + +#[derive(Debug)] +pub struct DiskInfo { + pub total: Option, + pub used: Option, + pub free: Option, +} + +pub async fn get_disk_info() -> Result { + let disks = Disks::new_with_refreshed_list(); + let mut total = 0; + let mut used = 0; + + for disk in disks.list() { + if disk.total_space() > 100 * 1024 * 1024 { + // > 100MB + total += disk.total_space(); + used += disk.total_space() - disk.available_space(); + } + } + + Ok(DiskInfo { + total: Some(total as f64), + used: Some(used as f64), + free: Some((total - used) as f64), + }) +} + +pub fn get_disk_usage() -> (f64, f64, f64) { + let mut sys = System::new(); + sys.refresh_all(); + //sys.refresh_disks_list(); + + let mut total_size = 0u64; + let mut total_used = 0u64; + let mut count = 0; + + let disks = Disks::new_with_refreshed_list(); + for disk in disks.list() { + // Ignoriere CD-ROMs und kleine Systempartitionen + println!( + "Disk_Name: {:?}, Disk_Kind: {}, Total: {}, Available: {}", + disk.name(), + disk.kind(), + disk.total_space(), + disk.available_space() + ); + if disk.total_space() > 100 * 1024 * 1024 { + // > 100MB + total_size += disk.total_space(); + total_used += disk.total_space() - disk.available_space(); + count += 1; + } + } + let components = Components::new_with_refreshed_list(); + for component in &components { + if let Some(temperature) = component.temperature() { + println!( + "Component_Label: {}, Temperature: {}°C", + component.label(), + temperature + ); + } + } + + // Berechnungen + let size_b = if count > 0 { + total_size as f64 // in Bytes + } else { + // Fallback: Versuche df unter Linux + println!("Fallback: Using 'df' command to get disk info."); + #[cfg(target_os = "linux")] + { + use std::process::Command; + if let Ok(output) = Command::new("df") + .arg("-B1") + .arg("--output=size,used") + .output() + { + let stdout = String::from_utf8_lossy(&output.stdout); + for line in stdout.lines().skip(1) { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() == 2 { + if let (Ok(size), Ok(used)) = + (parts[0].parse::(), parts[1].parse::()) + { + total_size += size; + total_used += used; + count += 1; + } + } + } + total_size as f64 // in Bytes + } else { + 0.0 + } + } + #[cfg(not(target_os = "linux"))] + { + 0.0 + } + }; + + let usage = if total_size > 0 { + (total_used as f64 / total_size as f64) * 100.0 + } else { + 0.0 + }; + + (size_b, usage, 0.0) // Disk-Temp bleibt 0.0 ohne spezielle Hardware +} diff --git a/WatcherAgent/src/hardware/gpu.rs b/WatcherAgent/src/hardware/gpu.rs index e69de29..e49c3c0 100644 --- a/WatcherAgent/src/hardware/gpu.rs +++ b/WatcherAgent/src/hardware/gpu.rs @@ -0,0 +1,41 @@ +use anyhow::Result; +use nvml_wrapper::Nvml; +use std::error::Error; + +#[derive(Debug)] +pub struct GpuInfo { + pub name: Option, + pub current_load: Option, + pub current_temp: Option, + pub vram_total: Option, + pub vram_used: Option, +} + +pub async fn get_gpu_info() -> Result> { + let nvml = Nvml::init()?; + let device = nvml.device_by_index(0)?; + + let (used, total) = get_gpu_vram_usage(&device)?; + Ok(GpuInfo { + name: device.name().ok(), + current_load: get_gpu_load(&device).ok(), + current_temp: get_gpu_temp(&device).ok(), + vram_total: Some(total as f64), + vram_used: Some(used as f64), + }) +} + +pub fn get_gpu_load(device: &nvml_wrapper::Device) -> Result> { + Ok(device.utilization_rates().unwrap().gpu as f64) +} + +pub fn get_gpu_temp(device: &nvml_wrapper::Device) -> Result> { + Ok(device + .temperature(nvml_wrapper::enum_wrappers::device::TemperatureSensor::Gpu) + .unwrap() as f64) +} + +pub fn get_gpu_vram_usage(device: &nvml_wrapper::Device) -> Result<(f64, f64), Box> { + let mem_info = device.memory_info().unwrap(); + Ok((mem_info.used as f64, mem_info.total as f64)) +} diff --git a/WatcherAgent/src/hardware/memory.rs b/WatcherAgent/src/hardware/memory.rs index e69de29..65be545 100644 --- a/WatcherAgent/src/hardware/memory.rs +++ b/WatcherAgent/src/hardware/memory.rs @@ -0,0 +1,25 @@ +use anyhow::Result; +use sysinfo::System; + +#[derive(Debug)] +pub struct MemoryInfo { + pub total: Option, + pub used: Option, + pub free: Option, +} + +pub async fn get_memory_info() -> Result { + let mut sys = System::new(); + sys.refresh_memory(); + + Ok(MemoryInfo { + total: Some(sys.total_memory() as f64), + used: Some(sys.used_memory() as f64), + free: Some(sys.free_memory() as f64), + }) +} + +pub fn get_memory_usage(sys: &mut System) -> f64 { + sys.refresh_memory(); + (sys.used_memory() as f64 / sys.total_memory() as f64) * 100.0 +} diff --git a/WatcherAgent/src/hardware/mod.rs b/WatcherAgent/src/hardware/mod.rs new file mode 100644 index 0000000..72fe525 --- /dev/null +++ b/WatcherAgent/src/hardware/mod.rs @@ -0,0 +1,35 @@ +//use anyhow::Result; +use std::error::Error; + +mod cpu; +mod disk; +mod gpu; +mod memory; +mod network; + +pub use cpu::get_cpu_info; +pub use disk::get_disk_info; +pub use gpu::get_gpu_info; +pub use memory::get_memory_info; +pub use network::get_network_info; + +#[derive(Debug)] +pub struct HardwareInfo { + pub cpu: cpu::CpuInfo, + pub gpu: gpu::GpuInfo, + pub memory: memory::MemoryInfo, + pub disk: disk::DiskInfo, + pub network: network::NetworkInfo, +} + +impl HardwareInfo { + pub async fn collect() -> anyhow::Result> { + Ok(Self { + cpu: get_cpu_info().await?, + gpu: get_gpu_info().await?, + memory: get_memory_info().await?, + disk: get_disk_info().await?, + network: get_network_info().await?, + }) + } +} diff --git a/WatcherAgent/src/hardware/network.rs b/WatcherAgent/src/hardware/network.rs index e69de29..2fb3066 100644 --- a/WatcherAgent/src/hardware/network.rs +++ b/WatcherAgent/src/hardware/network.rs @@ -0,0 +1,191 @@ +use std::error::Error; +use std::result::Result; +use std::time::Instant; + +#[derive(Debug)] +pub struct NetworkInfo { + pub interfaces: Option>, + pub rx_bytes: Option, + pub tx_bytes: Option, +} + +pub struct NetworkMonitor { + prev_rx: u64, + prev_tx: u64, + last_update: Instant, +} + +impl NetworkMonitor { + pub fn new() -> Self { + Self { + prev_rx: 0, + prev_tx: 0, + last_update: Instant::now(), + } + } + + pub fn get_usage(&mut self) -> Result<(f64, f64), Box> { + let (current_rx, current_tx) = get_network_bytes()?; + let elapsed = self.last_update.elapsed().as_secs_f64(); + self.last_update = Instant::now(); + + let rx_rate = if current_rx >= self.prev_rx { + (current_rx - self.prev_rx) as f64 / elapsed + } else { + 0.0 + }; + + let tx_rate = if current_tx >= self.prev_tx { + (current_tx - self.prev_tx) as f64 / elapsed + } else { + 0.0 + }; + + self.prev_rx = current_rx; + self.prev_tx = current_tx; + + Ok((rx_rate, tx_rate)) + } +} + +pub async fn get_network_info() -> Result> { + let (rx, tx) = get_network_bytes()?; + Ok(NetworkInfo { + interfaces: Some(get_network_interfaces()), + rx_bytes: Some(rx as f64), + tx_bytes: Some(tx as f64), + }) +} + +fn get_network_bytes() -> Result<(u64, u64), Box> { + #[cfg(target_os = "windows")] + { + use std::ptr::null_mut; + use winapi::shared::ifmib::MIB_IFTABLE; + use winapi::um::iphlpapi::GetIfTable; + + unsafe { + // Erste Abfrage zur Bestimmung der benötigten Puffergröße + let mut buffer_size = 0u32; + if GetIfTable(null_mut(), &mut buffer_size, 0) + != winapi::shared::winerror::ERROR_INSUFFICIENT_BUFFER + { + return Err( + anyhow::anyhow!("Failed to get buffer size for network interfaces").into(), + ); + } + + // Puffer allozieren + let mut buffer = vec![0u8; buffer_size as usize]; + let if_table = buffer.as_mut_ptr() as *mut MIB_IFTABLE; + + // Tatsächliche Daten abrufen + if GetIfTable(if_table, &mut buffer_size, 0) != 0 { + return Err(anyhow::anyhow!("Failed to get network interface table").into()); + } + + // Daten auswerten + let mut rx_total = 0u64; + let mut tx_total = 0u64; + + for i in 0..(*if_table).dwNumEntries { + let row = &*((*if_table).table.as_ptr().offset(i as isize)); + rx_total += row.dwInOctets as u64; + tx_total += row.dwOutOctets as u64; + } + + if rx_total == 0 && tx_total == 0 { + return Err(anyhow::anyhow!("No network data available").into()); + } else { + Ok((rx_total, tx_total)) + } + } + } + + #[cfg(target_os = "linux")] + { + use std::fs; + + let mut rx_total = 0u64; + let mut tx_total = 0u64; + if let Ok(dir) = fs::read_dir("/sys/class/net") { + for entry in dir.flatten() { + let iface = entry.file_name(); + let iface_name = iface.to_string_lossy(); + + // Ignoriere virtuelle Interfaces + if !iface_name.starts_with("lo") && !iface_name.starts_with("virbr") { + if let (Ok(rx), Ok(tx)) = ( + fs::read_to_string(entry.path().join("statistics/rx_bytes")), + fs::read_to_string(entry.path().join("statistics/tx_bytes")), + ) { + rx_total += rx.trim().parse::().unwrap_or(0); + tx_total += tx.trim().parse::().unwrap_or(0); + } + } + } + } + + if rx_total == 0 && tx_total == 0 { + return Err(anyhow::anyhow!("No network data available").into()); + } else { + return Ok((rx_total, tx_total)); + } + } + + #[cfg(not(any(target_os = "windows", target_os = "linux")))] + Err("No network data available for this OS".into()) +} + +fn get_network_interfaces() -> Vec { + #[cfg(target_os = "windows")] + { + use std::ffi::CStr; + use std::ptr::null_mut; + use winapi::shared::ifmib::MIB_IFTABLE; + use winapi::um::iphlpapi::GetIfTable; + + unsafe { + let mut buffer_size = 0u32; + if GetIfTable(null_mut(), &mut buffer_size, 0) + != winapi::shared::winerror::ERROR_INSUFFICIENT_BUFFER + { + return vec![]; + } + + let mut buffer = vec![0u8; buffer_size as usize]; + let if_table = buffer.as_mut_ptr() as *mut MIB_IFTABLE; + + if GetIfTable(if_table, &mut buffer_size, 0) != 0 { + return vec![]; + } + + (0..(*if_table).dwNumEntries) + .map(|i| { + let row = &*((*if_table).table.as_ptr().offset(i as isize)); + let descr = CStr::from_ptr(row.bDescr.as_ptr() as *const i8) + .to_string_lossy() + .into_owned(); + descr.trim().to_string() + }) + .collect() + } + } + + #[cfg(target_os = "linux")] + { + use std::fs; + + let mut interfaces = vec![]; + if let Ok(dir) = fs::read_dir("/sys/class/net") { + for entry in dir.flatten() { + let iface = entry.file_name(); + interfaces.push(iface.to_string_lossy().to_string()); + } + } + interfaces + } + + #[cfg(not(any(target_os = "windows", target_os = "linux")))] + vec![] +} diff --git a/WatcherAgent/src/metrics.rs b/WatcherAgent/src/metrics.rs index e379f12..b5ccbb1 100644 --- a/WatcherAgent/src/metrics.rs +++ b/WatcherAgent/src/metrics.rs @@ -1,507 +1,50 @@ use std::error::Error; use std::time::Duration; -use sysinfo::{Components, Disks, System}; -use tokio::time::Instant; - use crate::api; -use crate::models::{MetricDto, NetworkState}; -use nvml_wrapper::Nvml; +use crate::hardware::HardwareInfo; +use crate::models::MetricDto; pub struct Collector { - sys: System, - nvml: Option, server_id: i32, ip_address: String, - network_state: NetworkState, -} - -impl NetworkState { - fn new() -> Self { - Self { - prev_rx: 0, - prev_tx: 0, - last_update: Instant::now(), - } - } } impl Collector { pub fn new(server_id: i32, ip_address: String) -> Self { Self { - sys: System::new(), - nvml: Nvml::init().ok(), server_id, ip_address, - network_state: NetworkState::new(), } } pub async fn run(&mut self, base_url: &str) -> anyhow::Result<(), Box> { loop { - let metrics = self.collect(); + let metrics = self.collect().await?; api::send_metrics(base_url, &metrics).await?; tokio::time::sleep(Duration::from_secs(20)).await; } } - pub fn collect(&mut self) -> MetricDto { - self.sys.refresh_all(); + pub async fn collect(&mut self) -> Result> { + let hardware = HardwareInfo::collect().await?; - // CPU - updated for sysinfo 0.35 - let cpu_load = self.sys.global_cpu_usage() as f64; - let cpu_temp = get_cpu_temp().unwrap_or(0.0) as f64; - - // RAM - updated for sysinfo 0.35 - let total_memory = self.sys.total_memory() as f64; - let used_memory = self.sys.used_memory() as f64; - let ram_load = (used_memory / total_memory) * 100.0; - let ram_size = total_memory; - - // Disk - updated for sysinfo 0.35 - let (disk_size, disk_usage, disk_temp) = get_disk_info(); - // 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) { - let temp = device - .temperature(nvml_wrapper::enum_wrappers::device::TemperatureSensor::Gpu) - .unwrap_or(0) as f64; - let load = device - .utilization_rates() - .map(|u| u.gpu as f64) - .unwrap_or(0.0); - let mem = device.memory_info().ok(); - let used = mem.clone().map(|m| (m.used as f64)).unwrap_or(0.0); // B - let total = mem.map(|m| (m.total as f64)).unwrap_or(0.0); // B - (temp, load, used, total) - } else { - (0.0, 0.0, 0.0, 0.0) - } - } else { - (0.0, 0.0, 0.0, 0.0) - }; - - // Network metrics - let (current_rx, current_tx) = get_network_traffic().unwrap_or((0, 0)); - let elapsed_secs = self.network_state.last_update.elapsed().as_secs_f64(); - self.network_state.last_update = Instant::now(); - - // Calculate the difference since the last call - let net_in = if current_rx >= self.network_state.prev_rx { - ((current_rx - self.network_state.prev_rx) as f64 * 8.0) / elapsed_secs - } else { - 0.0 - }; - - let net_out = if current_tx >= self.network_state.prev_tx { - ((current_tx - self.network_state.prev_tx) as f64 * 8.0) / elapsed_secs - } else { - 0.0 - }; - - // Store the current values for the next call - self.network_state.prev_rx = current_rx; - self.network_state.prev_tx = current_tx; - - MetricDto { + Ok(MetricDto { server_id: self.server_id, ip_address: self.ip_address.clone(), - cpu_load, - cpu_temp, - gpu_load, - gpu_temp, - gpu_vram_size: vram_total, - gpu_vram_usage: if vram_total > 0.0 { - (vram_used / vram_total) * 100.0 - } else { - 0.0 - }, - ram_load, - ram_size, - disk_size, - disk_usage: disk_usage, - disk_temp: disk_temp, // not supported - net_in, - net_out, - } + cpu_load: hardware.cpu.current_load.unwrap_or_default(), + cpu_temp: hardware.cpu.current_temp.unwrap_or_default(), + gpu_load: hardware.gpu.current_load.unwrap_or_default(), + gpu_temp: hardware.gpu.current_temp.unwrap_or_default(), + gpu_vram_size: hardware.gpu.vram_total.unwrap_or_default(), + gpu_vram_usage: hardware.gpu.vram_used.unwrap_or_default(), + ram_load: hardware.memory.used.unwrap_or_default(), + ram_size: hardware.memory.total.unwrap_or_default(), + disk_size: hardware.disk.total.unwrap_or_default(), + disk_usage: hardware.disk.used.unwrap_or_default(), + disk_temp: 0.0, // not supported + net_rx: hardware.network.rx_bytes.unwrap_or_default(), + net_tx: hardware.network.tx_bytes.unwrap_or_default(), + }) } } - -pub fn get_cpu_temp() -> Option { - println!("Attempting to get CPU temperature..."); - - #[cfg(target_os = "linux")] - { - use std::fs; - use std::process::Command; - println!(""); - if let Ok(output) = Command::new("sensors").output() { - let stdout = String::from_utf8_lossy(&output.stdout); - for line in stdout.lines() { - if line.contains("Package id") || line.contains("Tdie") || line.contains("CPU Temp") - { - if let Some(temp_str) = line - .split('+') - .nth(1) - .and_then(|s| s.split_whitespace().next()) - { - if let Ok(temp) = temp_str.replace("°C", "").parse::() { - return Some(temp); - } - } - } - } - } - - // 2. Sysfs (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); - } - } - - // 3. Alternative Sysfs-Pfade - let paths = [ - "/sys/class/hwmon/hwmontemp1_input", - "/sys/class/hwmon/hwmondevice/temp1_input", - ]; - - for path_pattern in &paths { - if let Ok(paths) = glob::glob(path_pattern) { - for path in paths.flatten() { - if let Ok(content) = fs::read_to_string(&path) { - if let Ok(temp) = content.trim().parse::() { - return Some(temp / 1000.0); - } - } - } - } - } - - None - } - - #[cfg(target_os = "windows")] - fn failed(hr: winapi::shared::winerror::HRESULT) -> bool { - hr < 0 - } - - #[cfg(target_os = "windows")] - { - use com::runtime::init_runtime; - use com::sys::CLSCTX_INPROC_SERVER; - use widestring::U16CString; - use winapi::shared::rpcdce::*; - use winapi::shared::wtypes::VT_I4; - use winapi::um::oaidl::VARIANT; - use winapi::um::objidlbase::EOAC_NONE; - use winapi::um::{combaseapi, wbemcli}; - - init_runtime().ok()?; - - unsafe { - let mut locator: *mut wbemcli::IWbemLocator = std::ptr::null_mut(); - let hr = combaseapi::CoCreateInstance( - &wbemcli::CLSID_WbemLocator, - std::ptr::null_mut(), - CLSCTX_INPROC_SERVER, - &wbemcli::IID_IWbemLocator, - &mut locator as *mut _ as *mut _, - ); - - if hr != 0 { - eprintln!("Failed to create WbemLocator (HRESULT: {})", hr); - return None; - } - - let mut services: *mut wbemcli::IWbemServices = std::ptr::null_mut(); - let namespace = U16CString::from_str("root\\cimv2").unwrap(); // Changed to more common namespace - let hr = (*locator).ConnectServer( - namespace.as_ptr().cast_mut(), - std::ptr::null_mut(), - std::ptr::null_mut(), - std::ptr::null_mut(), - 0, - std::ptr::null_mut(), - std::ptr::null_mut(), - &mut services, - ); - - if hr != 0 { - eprintln!("Failed to connect to WMI (HRESULT: {})", hr); - (*locator).Release(); - return None; - } - - // Set security levels - let hr = combaseapi::CoSetProxyBlanket( - services as *mut _, - RPC_C_AUTHN_WINNT, - RPC_C_AUTHZ_NONE, - std::ptr::null_mut(), - RPC_C_AUTHN_LEVEL_CALL, - RPC_C_IMP_LEVEL_IMPERSONATE, - std::ptr::null_mut(), - EOAC_NONE, - ); - - if hr != 0 { - eprintln!("Failed to set proxy blanket (HRESULT: {})", hr); - (*services).Release(); - (*locator).Release(); - return None; - } - - // Try different temperature queries - some systems might have different WMI classes - let queries = [ - "SELECT * FROM Win32_PerfFormattedData_Counters_ThermalZoneInformation", - "SELECT * FROM MSAcpi_ThermalZoneTemperature", - "SELECT * FROM Win32_TemperatureProbe", - ]; - - let mut result = None; - - for query_str in queries.iter() { - let query = U16CString::from_str(query_str).unwrap(); - let mut enumerator: *mut wbemcli::IEnumWbemClassObject = std::ptr::null_mut(); - let hr = (*services).ExecQuery( - U16CString::from_str("WQL").unwrap().as_ptr().cast_mut(), - query.as_ptr().cast_mut(), - wbemcli::WBEM_FLAG_FORWARD_ONLY as i32, - std::ptr::null_mut(), - &mut enumerator, - ); - - if hr != 0 { - continue; // Try next query if this one fails - } - - let mut obj: *mut wbemcli::IWbemClassObject = std::ptr::null_mut(); - let mut returned = 0; - let hr = (*enumerator).Next( - wbemcli::WBEM_INFINITE as i32, // Fixed: cast directly to i32 - 1, - &mut obj, - &mut returned, - ); - - if failed(hr) { - eprintln!("Failed to enumerate WMI objects (HRESULT: {})", hr); - (*enumerator).Release(); - continue; - } - - if returned == 0 { - // No more items - (*enumerator).Release(); - continue; - } - - if hr == 0 && returned > 0 { - let mut variant = std::mem::zeroed::(); - // Try different possible property names - let property_names = ["CurrentTemperature", "Temperature", "CurrentReading"]; - - for prop in property_names.iter() { - let hr = (*obj).Get( - U16CString::from_str(prop).unwrap().as_ptr(), - 0, - &mut variant, - std::ptr::null_mut(), - std::ptr::null_mut(), - ); - - if hr == 0 && variant.n1.n2().vt as u32 == VT_I4 { - let temp_kelvin = *variant.n1.n2().n3.intVal() as f32 / 10.0; - result = Some(temp_kelvin - 273.15); // Convert to Celsius - break; - } - } - - (*obj).Release(); - (*enumerator).Release(); - if result.is_some() { - break; - } - } - - if !enumerator.is_null() { - (*enumerator).Release(); - } - } - - (*services).Release(); - (*locator).Release(); - - result - } - } - - #[cfg(not(any(target_os = "linux", target_os = "windows")))] - { - println!("CPU temperature retrieval not supported on this OS."); - None - } -} - -fn get_network_traffic() -> Option<(u64, u64)> { - #[cfg(target_os = "windows")] - { - use std::ptr::null_mut; - use winapi::shared::ifmib::MIB_IFTABLE; - use winapi::um::iphlpapi::GetIfTable; - - unsafe { - // Erste Abfrage zur Bestimmung der benötigten Puffergröße - let mut buffer_size = 0u32; - if GetIfTable(null_mut(), &mut buffer_size, 0) - != winapi::shared::winerror::ERROR_INSUFFICIENT_BUFFER - { - return None; - } - - // Puffer allozieren - let mut buffer = vec![0u8; buffer_size as usize]; - let if_table = buffer.as_mut_ptr() as *mut MIB_IFTABLE; - - // Tatsächliche Daten abrufen - if GetIfTable(if_table, &mut buffer_size, 0) != 0 { - return None; - } - - // Daten auswerten - let mut rx_total = 0u64; - let mut tx_total = 0u64; - - for i in 0..(*if_table).dwNumEntries { - let row = &*((*if_table).table.as_ptr().offset(i as isize)); - rx_total += row.dwInOctets as u64; - tx_total += row.dwOutOctets as u64; - } - - if rx_total == 0 && tx_total == 0 { - return None; - } else { - return Some((rx_total, tx_total)); - } - } - } - - #[cfg(target_os = "linux")] - { - use std::fs; - - let mut rx_total = 0u64; - let mut tx_total = 0u64; - if let Ok(dir) = fs::read_dir("/sys/class/net") { - for entry in dir.flatten() { - let iface = entry.file_name(); - let iface_name = iface.to_string_lossy(); - - // Ignoriere virtuelle Interfaces - if !iface_name.starts_with("lo") && !iface_name.starts_with("virbr") { - if let (Ok(rx), Ok(tx)) = ( - fs::read_to_string(entry.path().join("statistics/rx_bytes")), - fs::read_to_string(entry.path().join("statistics/tx_bytes")), - ) { - rx_total += rx.trim().parse::().unwrap_or(0); - tx_total += tx.trim().parse::().unwrap_or(0); - } - } - } - } - - if rx_total == 0 && tx_total == 0 { - return None; - } else { - return Some((rx_total, tx_total)); - } - } - - #[cfg(not(any(target_os = "windows", target_os = "linux")))] - None -} - -pub fn get_disk_info() -> (f64, f64, f64) { - let mut sys = System::new(); - sys.refresh_all(); - //sys.refresh_disks_list(); - - let mut total_size = 0u64; - let mut total_used = 0u64; - let mut count = 0; - - let disks = Disks::new_with_refreshed_list(); - for disk in disks.list() { - // Ignoriere CD-ROMs und kleine Systempartitionen - println!( - "Disk_Name: {:?}, Disk_Kind: {}, Total: {}, Available: {}", - disk.name(), - disk.kind(), - disk.total_space(), - disk.available_space() - ); - if disk.total_space() > 100 * 1024 * 1024 { - // > 100MB - total_size += disk.total_space(); - total_used += disk.total_space() - disk.available_space(); - count += 1; - } - } - let components = Components::new_with_refreshed_list(); - for component in &components { - if let Some(temperature) = component.temperature() { - println!( - "Component_Label: {}, Temperature: {}°C", - component.label(), - temperature - ); - } - } - - // Berechnungen - let size_b = if count > 0 { - total_size as f64 // in Bytes - } else { - // Fallback: Versuche df unter Linux - println!("Fallback: Using 'df' command to get disk info."); - #[cfg(target_os = "linux")] - { - use std::process::Command; - if let Ok(output) = Command::new("df") - .arg("-B1") - .arg("--output=size,used") - .output() - { - let stdout = String::from_utf8_lossy(&output.stdout); - for line in stdout.lines().skip(1) { - let parts: Vec<&str> = line.split_whitespace().collect(); - if parts.len() == 2 { - if let (Ok(size), Ok(used)) = - (parts[0].parse::(), parts[1].parse::()) - { - total_size += size; - total_used += used; - count += 1; - } - } - } - total_size as f64 // in Bytes - } else { - 0.0 - } - } - #[cfg(not(target_os = "linux"))] - { - 0.0 - } - }; - - let usage = if total_size > 0 { - (total_used as f64 / total_size as f64) * 100.0 - } else { - 0.0 - }; - - (size_b, usage, 0.0) // Disk-Temp bleibt 0.0 ohne spezielle Hardware -} diff --git a/WatcherAgent/src/models.rs b/WatcherAgent/src/models.rs index 4f47fda..56880ca 100644 --- a/WatcherAgent/src/models.rs +++ b/WatcherAgent/src/models.rs @@ -47,9 +47,9 @@ pub struct MetricDto { #[serde(rename = "disk_Temp")] pub disk_temp: f64, #[serde(rename = "net_In")] - pub net_in: f64, + pub net_rx: f64, #[serde(rename = "net_Out")] - pub net_out: f64, + pub net_tx: f64, } #[derive(Deserialize)] @@ -73,9 +73,3 @@ pub struct HardwareDto { pub ram_size: f64, pub ip_address: String, } - -pub struct NetworkState { - pub prev_rx: u64, - pub prev_tx: u64, - pub last_update: Instant, -}