From 0cdab2443d4f86d556981fe594d9d37ecbd0491a Mon Sep 17 00:00:00 2001 From: donpat1to Date: Fri, 8 Aug 2025 14:19:12 +0200 Subject: [PATCH] put all files in their expected file --- WatcherAgent/Cargo.toml | 2 +- WatcherAgent/src/api.rs | 149 ++++++ WatcherAgent/src/hardware.rs | 79 +++ WatcherAgent/src/library.rs | 23 - WatcherAgent/src/main.rs | 938 +---------------------------------- WatcherAgent/src/metrics.rs | 507 +++++++++++++++++++ WatcherAgent/src/models.rs | 81 +++ 7 files changed, 835 insertions(+), 944 deletions(-) create mode 100644 WatcherAgent/src/api.rs create mode 100644 WatcherAgent/src/hardware.rs delete mode 100644 WatcherAgent/src/library.rs create mode 100644 WatcherAgent/src/metrics.rs create mode 100644 WatcherAgent/src/models.rs diff --git a/WatcherAgent/Cargo.toml b/WatcherAgent/Cargo.toml index 3c79d8d..8c46c74 100644 --- a/WatcherAgent/Cargo.toml +++ b/WatcherAgent/Cargo.toml @@ -16,7 +16,7 @@ sysinfo = "0.36.1" metrics = "0.24.2" chrono = "0.4" nvml-wrapper = "0.10" - +anyhow = "1.0.98" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["winuser", "pdh", "ifmib", "iphlpapi", "winerror" ,"wbemcli", "combaseapi"] } diff --git a/WatcherAgent/src/api.rs b/WatcherAgent/src/api.rs new file mode 100644 index 0000000..044ce3f --- /dev/null +++ b/WatcherAgent/src/api.rs @@ -0,0 +1,149 @@ +use std::time::Duration; + +use crate::models::{HardwareDto, HeartbeatDto, IdResponse, MetricDto, RegistrationDto}; +use anyhow::Result; +use reqwest::{Client, StatusCode}; +use std::error::Error; +use tokio::time::{interval, sleep}; + +pub async fn register_with_server(base_url: &str) -> Result<(i32, String), Box> { + // First get local IP + let ip = local_ip_address::local_ip()?.to_string(); + println!("Local IP address detected: {}", ip); + + // Get server ID from backend (this will retry until successful) + let (server_id, registered_ip) = get_server_id_by_ip(base_url, &ip).await?; + + // Create HTTP client for registration + let client = Client::builder() + .danger_accept_invalid_certs(true) + .build()?; + + // Collect hardware info + let hardware = HardwareDto::collect().await?; + + // Prepare registration data + let registration = RegistrationDto { + id: server_id, + ip_address: registered_ip.clone(), + cpu_type: hardware.cpu_type, + cpu_cores: hardware.cpu_cores, + gpu_type: hardware.gpu_type, + ram_size: hardware.ram_size, + }; + + // Try to register (will retry on failure) + loop { + println!("Attempting to register with server..."); + let url = format!("{}/monitoring/register-agent-by-id", base_url); + match client.post(&url).json(®istration).send().await { + Ok(resp) if resp.status().is_success() => { + println!("✅ Successfully registered with server."); + return Ok((server_id, registered_ip)); + } + Ok(resp) => { + let status = resp.status(); + let text = resp.text().await.unwrap_or_default(); + println!( + "⚠️ Registration failed ({}): {} (will retry in 10 seconds)", + status, text + ); + } + Err(err) => { + println!("⚠️ Registration error: {} (will retry in 10 seconds)", err); + } + } + sleep(Duration::from_secs(10)).await; + } +} + +async fn get_server_id_by_ip(base_url: &str, ip: &str) -> Result<(i32, String), Box> { + let client = Client::builder() + .danger_accept_invalid_certs(true) + .build()?; + + let url = format!("{}/monitoring/server-id-by-ip?ipAddress={}", base_url, ip); + + loop { + println!("Attempting to fetch server ID for IP {}...", ip); + match client.get(&url).send().await { + Ok(resp) if resp.status().is_success() => { + let text = resp.text().await?; + println!("Raw response: {}", text); // Debug output + + let id_resp: IdResponse = serde_json::from_str(&text).map_err(|e| { + println!("Failed to parse response: {}", e); + e + })?; + + println!( + "✅ Received ID {} for IP {}", + id_resp.id, id_resp.ip_address + ); + return Ok((id_resp.id, id_resp.ip_address)); + } + Ok(resp) if resp.status() == StatusCode::NOT_FOUND => { + println!( + "❌ Server with IP {} not found in database (will retry in 10 seconds)", + ip + ); + sleep(Duration::from_secs(10)).await; + } + Ok(resp) => { + println!( + "⚠️ Server responded with status: {} - {}", + resp.status(), + resp.text().await? + ); + sleep(Duration::from_secs(10)).await; + } + Err(err) => { + println!("⚠️ Request failed: {} (will retry in 10 seconds)", err); + sleep(Duration::from_secs(10)).await; + } + } + } +} + +pub async fn heartbeat_loop(base_url: &str, ip: &str) -> Result<(), Box> { + let client = Client::builder() + .danger_accept_invalid_certs(true) + .build()?; + let url = format!("{}/heartbeat/receive", base_url); + + loop { + let payload = HeartbeatDto { + ip_address: ip.to_string(), + }; + + match client.post(&url).json(&payload).send().await { + Ok(res) if res.status().is_success() => { + println!("Heartbeat sent successfully."); + } + Ok(res) => eprintln!("Server responded with status: {}", res.status()), + Err(e) => eprintln!("Heartbeat error: {}", e), + } + + sleep(Duration::from_secs(20)).await; + } +} + +pub async fn send_metrics(base_url: &str, metrics: &MetricDto) -> Result<(), Box> { + let client = Client::new(); + let url = format!("{}/monitoring/metric", base_url); + let mut interval = interval(Duration::from_secs(20)); + + loop { + interval.tick().await; + let metric = metrics; + + match client.post(&url).json(&metric).send().await { + Ok(res) => println!( + "✅ Sent metrics for server {} | Status: {}", + metric.server_id, + res.status() + ), + Err(err) => eprintln!("❌ Failed to send metrics: {}", err), + } + } +} diff --git a/WatcherAgent/src/hardware.rs b/WatcherAgent/src/hardware.rs new file mode 100644 index 0000000..cf6126c --- /dev/null +++ b/WatcherAgent/src/hardware.rs @@ -0,0 +1,79 @@ +use crate::models::HardwareDto; +use nvml_wrapper::Nvml; +use sysinfo::System; + +impl HardwareDto { + pub async fn collect() -> 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/library.rs b/WatcherAgent/src/library.rs deleted file mode 100644 index 5145f0a..0000000 --- a/WatcherAgent/src/library.rs +++ /dev/null @@ -1,23 +0,0 @@ -/* -$ rustc --crate-type=lib rary.rs -$ ls lib* -library.rlib - -// extern crate rary; // May be required for Rust 2015 edition or earlier - -fn main() { - rary::public_function(); - - // Error! `private_function` is private - //rary::private_function(); - - rary::indirect_access(); -} - -# Where library.rlib is the path to the compiled library, assumed that it's -# in the same directory here: -$ rustc executable.rs --extern rary=library.rlib && ./executable -called rary's `public_function()` -called rary's `indirect_access()`, that -> called rary's `private_function()` -*/ diff --git a/WatcherAgent/src/main.rs b/WatcherAgent/src/main.rs index 0a33294..b32c3d4 100644 --- a/WatcherAgent/src/main.rs +++ b/WatcherAgent/src/main.rs @@ -1,940 +1,38 @@ /// WatcherAgent - A Rust-based system monitoring agent /// This agent collects hardware metrics and sends them to a backend server. /// It supports CPU, GPU, RAM, disk, and network metrics. -//use chrono::Utc; -use nvml_wrapper::Nvml; -use reqwest::{Client, StatusCode}; -use serde::{Deserialize, Serialize}; -use std::{error::Error, time::Duration}; -use sysinfo::{Components, Disks, System}; -use tokio::time::{interval, sleep, Instant}; +mod api; +mod hardware; +mod metrics; +mod models; -// Windows specific imports - -// Data structures matching the C# DTOs -#[derive(Serialize, Debug)] -struct RegistrationDto { - #[serde(rename = "id")] - id: i32, - #[serde(rename = "ipAddress")] - ip_address: String, - #[serde(rename = "cpuType")] - cpu_type: String, - #[serde(rename = "cpuCores")] - cpu_cores: i32, - #[serde(rename = "gpuType")] - gpu_type: String, - #[serde(rename = "ramSize")] - ram_size: f64, -} - -#[derive(Serialize, Debug)] -struct MetricDto { - #[serde(rename = "serverId")] - server_id: i32, - #[serde(rename = "ipAddress")] - ip_address: String, - #[serde(rename = "cpu_Load")] - cpu_load: f64, - #[serde(rename = "cpu_Temp")] - cpu_temp: f64, - #[serde(rename = "gpu_Load")] - gpu_load: f64, - #[serde(rename = "gpu_Temp")] - gpu_temp: f64, - #[serde(rename = "gpu_Vram_Size")] - gpu_vram_size: f64, - #[serde(rename = "gpu_Vram_Usage")] - gpu_vram_usage: f64, - #[serde(rename = "ram_Load")] - ram_load: f64, - #[serde(rename = "ram_Size")] - ram_size: f64, - #[serde(rename = "disk_Size")] - disk_size: f64, - #[serde(rename = "disk_Usage")] - disk_usage: f64, - #[serde(rename = "disk_Temp")] - disk_temp: f64, - #[serde(rename = "net_In")] - net_in: f64, - #[serde(rename = "net_Out")] - net_out: f64, -} - -#[derive(Deserialize)] -struct IdResponse { - id: i32, - #[serde(rename = "ipAddress")] - ip_address: String, -} - -#[derive(Serialize)] -struct HeartbeatPayload { - #[serde(rename = "IpAddress")] - ip_address: String, -} - -#[derive(Serialize, Debug)] -struct HardwareInfo { - cpu_type: String, - cpu_cores: i32, - gpu_type: String, - ram_size: f64, - ip_address: String, -} - -struct NetworkState { - prev_rx: u64, - prev_tx: u64, - last_update: Instant, -} - -impl NetworkState { - fn new() -> Self { - Self { - prev_rx: 0, - prev_tx: 0, - last_update: Instant::now(), - } - } -} - -impl HardwareInfo { - async fn collect() -> 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 { - Self::try_nvml_gpu_name() - .or_else(Self::fallback_gpu_name) - .unwrap_or_else(|| "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()) - } - - fn fallback_gpu_name() -> Option { - #[cfg(target_os = "linux")] - { - let output = std::process::Command::new("lshw") - .args(&["-C", "display"]) - .output() - .ok()?; - Some( - String::from_utf8_lossy(&output.stdout) - .lines() - .find(|l| l.contains("product:")) - .map(|l| l.trim().replace("product:", "").trim().to_string()) - .unwrap_or("Unknown GPU".to_string()), - ) - } - - #[cfg(target_os = "windows")] - { - let output = std::process::Command::new("wmic") - .args(&["path", "win32_VideoController", "get", "name"]) - .output() - .ok()?; - Some( - String::from_utf8_lossy(&output.stdout) - .lines() - .nth(1) - .map(|s| s.trim().to_string()) - .unwrap_or("Unknown GPU".to_string()), - ) - } - } -} - -async fn get_server_id_by_ip(base_url: &str, ip: &str) -> Result<(i32, String), Box> { - let client = Client::builder() - .danger_accept_invalid_certs(true) - .build()?; - - let url = format!("{}/monitoring/server-id-by-ip?ipAddress={}", base_url, ip); - - loop { - println!("Attempting to fetch server ID for IP {}...", ip); - match client.get(&url).send().await { - Ok(resp) if resp.status().is_success() => { - let text = resp.text().await?; - println!("Raw response: {}", text); // Debug output - - let id_resp: IdResponse = serde_json::from_str(&text).map_err(|e| { - println!("Failed to parse response: {}", e); - e - })?; - - println!( - "✅ Received ID {} for IP {}", - id_resp.id, id_resp.ip_address - ); - return Ok((id_resp.id, id_resp.ip_address)); - } - Ok(resp) if resp.status() == StatusCode::NOT_FOUND => { - println!( - "❌ Server with IP {} not found in database (will retry in 10 seconds)", - ip - ); - sleep(Duration::from_secs(10)).await; - } - Ok(resp) => { - println!( - "⚠️ Server responded with status: {} - {}", - resp.status(), - resp.text().await? - ); - sleep(Duration::from_secs(10)).await; - } - Err(err) => { - println!("⚠️ Request failed: {} (will retry in 10 seconds)", err); - sleep(Duration::from_secs(10)).await; - } - } - } -} - -async fn register_with_server(base_url: &str) -> Result<(i32, String), Box> { - // First get local IP - let ip = local_ip_address::local_ip()?.to_string(); - println!("Local IP address detected: {}", ip); - - // Get server ID from backend (this will retry until successful) - let (server_id, registered_ip) = get_server_id_by_ip(base_url, &ip).await?; - - // Create HTTP client for registration - let client = Client::builder() - .danger_accept_invalid_certs(true) - .build()?; - - // Collect hardware info - let hardware = HardwareInfo::collect().await?; - - // Prepare registration data - let registration = RegistrationDto { - id: server_id, - ip_address: registered_ip.clone(), - cpu_type: hardware.cpu_type, - cpu_cores: hardware.cpu_cores, - gpu_type: hardware.gpu_type, - ram_size: hardware.ram_size, - }; - - // Try to register (will retry on failure) - loop { - println!("Attempting to register with server..."); - let url = format!("{}/monitoring/register-agent-by-id", base_url); - match client.post(&url).json(®istration).send().await { - Ok(resp) if resp.status().is_success() => { - println!("✅ Successfully registered with server."); - return Ok((server_id, registered_ip)); - } - Ok(resp) => { - let status = resp.status(); - let text = resp.text().await.unwrap_or_default(); - println!( - "⚠️ Registration failed ({}): {} (will retry in 10 seconds)", - status, text - ); - } - Err(err) => { - println!("⚠️ Registration error: {} (will retry in 10 seconds)", err); - } - } - sleep(Duration::from_secs(10)).await; - } -} - -async fn heartbeat_loop(base_url: &str, ip: &str) -> Result<(), Box> { - let client = Client::builder() - .danger_accept_invalid_certs(true) - .build()?; - let url = format!("{}/heartbeat/receive", base_url); - - loop { - let payload = HeartbeatPayload { - ip_address: ip.to_string(), - }; - - match client.post(&url).json(&payload).send().await { - Ok(res) if res.status().is_success() => { - println!("Heartbeat sent successfully."); - } - Ok(res) => eprintln!("Server responded with status: {}", res.status()), - Err(e) => eprintln!("Heartbeat error: {}", e), - } - - sleep(Duration::from_secs(20)).await; - } -} - -struct MetricsCollector { - sys: System, - nvml: Option, - server_id: i32, - ip_address: String, - network_state: NetworkState, -} - -impl MetricsCollector { - 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(), - } - } - - async fn collect_and_send_loop(&mut self, base_url: &str) -> Result<(), Box> { - let client = Client::new(); - let url = format!("{}/monitoring/metric", base_url); - let mut interval = interval(Duration::from_secs(20)); - - loop { - interval.tick().await; - let metric = self.collect_metrics(); - println!("Collected metrics: {:?}", metric); - - match client.post(&url).json(&metric).send().await { - Ok(res) => println!( - "✅ Sent metrics for server {} | Status: {}", - metric.server_id, - res.status() - ), - Err(err) => eprintln!("❌ Failed to send metrics: {}", err), - } - } - } - - fn collect_metrics(&mut self) -> MetricDto { - self.sys.refresh_all(); - - // 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 { - 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, - } - } -} - -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_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 -} - -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 -} +use anyhow::Result; +use std::error::Error; #[tokio::main] async fn main() -> Result<(), Box> { - let server_base_url = "http://localhost:5000"; + let server_url = "http://localhost:5000"; - // Registration phase - println!("Starting registration process..."); - let (server_id, ip_address) = register_with_server(server_base_url).await?; + // Registration + let (server_id, ip) = api::register_with_server(server_url).await?; + // Start background tasks // Start heartbeat in background let heartbeat_handle = tokio::spawn({ - let ip = ip_address.clone(); + let ip = ip.clone(); async move { - if let Err(e) = heartbeat_loop(server_base_url, &ip).await { + if let Err(e) = api::heartbeat_loop(server_url, &ip).await { eprintln!("Heartbeat loop failed: {}", e); } } }); - // Start metrics collection - println!("Starting metrics collection..."); - let mut metrics_collector = MetricsCollector::new(server_id, ip_address); - metrics_collector - .collect_and_send_loop(server_base_url) - .await?; - heartbeat_handle.await?; + + // Main metrics loop + println!("Starting metrics collection..."); + let mut collector = metrics::Collector::new(server_id, ip); + collector.run(server_url).await?; + Ok(()) } - -#[cfg(test)] -mod tests { - use super::*; - - // Test CPU temperature collection - #[test] - fn test_cpu_temp() { - let temp = get_cpu_temp(); - println!("CPU Temperature: {:?}°C", temp); - - // Basic validation - temp should be between 0-100°C if available - if let Some(t) = temp { - assert!( - t >= 0.0 && t <= 100.0, - "CPU temperature out of reasonable range" - ); - } - } - - // Test disk information collection - #[test] - fn test_disk_info() { - let (size, usage, _temp) = get_disk_info(); - println!("Disk Size: {:.2}GB, Usage: {:.2}%", size, usage); - - assert!(size >= 0.0, "Disk size should be non-negative"); - assert!( - usage >= 0.0 && usage <= 100.0, - "Disk usage should be 0-100%" - ); - } - - // Test hardware info collection - #[tokio::test] - async fn test_hardware_info() { - let hardware = HardwareInfo::collect().await.unwrap(); - println!("Hardware Info: {:?}", hardware); - - assert!( - !hardware.cpu_type.is_empty(), - "CPU type should not be empty" - ); - assert!(hardware.cpu_cores > 0, "CPU cores should be positive"); - assert!( - !hardware.gpu_type.is_empty(), - "GPU type should not be empty" - ); - assert!(hardware.ram_size > 0.0, "RAM size should be positive"); - assert!( - !hardware.ip_address.is_empty(), - "IP address should not be empty" - ); - } - - // Test metrics collector - #[tokio::test] - async fn test_metrics_collector() { - let mut collector = MetricsCollector::new(1, "127.0.0.1".to_string()); - let metrics = collector.collect_metrics(); - println!("Collected Metrics: {:?}", metrics); - - // Validate basic metrics ranges - assert!( - metrics.cpu_load >= 0.0 && metrics.cpu_load <= 100.0, - "CPU load should be 0-100%" - ); - assert!( - metrics.ram_load >= 0.0 && metrics.ram_load <= 100.0, - "RAM load should be 0-100%" - ); - assert!(metrics.ram_size > 0.0, "RAM size should be positive"); - } - - // Test registration flow (mock server needed for full test) - #[tokio::test] - async fn test_registration_flow() { - // Note: This would require a mock server for proper testing - // Currently just testing the hardware detection part - let hardware = HardwareInfo::collect().await.unwrap(); - let registration = RegistrationDto { - id: 1, - ip_address: hardware.ip_address.clone(), - cpu_type: hardware.cpu_type, - cpu_cores: hardware.cpu_cores, - gpu_type: hardware.gpu_type, - ram_size: hardware.ram_size, - }; - - println!("Registration DTO: {:?}", registration); - assert_eq!(registration.id, 1); - assert!(!registration.ip_address.is_empty()); - } - - // Test error cases - #[test] - fn test_error_handling() { - // Test with invalid paths - #[cfg(target_os = "linux")] - { - let temp = get_cpu_temp_with_path("/invalid/path"); - assert!(temp.is_none(), "Should handle invalid paths gracefully"); - } - } - - // Helper function for testing with custom paths - #[cfg(target_os = "linux")] - fn get_cpu_temp_with_path(path: &str) -> Option { - fs::read_to_string(path) - .ok()? - .trim() - .parse::() - .map(|t| t / 1000.0) - .ok() - } -} diff --git a/WatcherAgent/src/metrics.rs b/WatcherAgent/src/metrics.rs new file mode 100644 index 0000000..e379f12 --- /dev/null +++ b/WatcherAgent/src/metrics.rs @@ -0,0 +1,507 @@ +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; + +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(); + 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(); + + // 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 { + 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, + } + } +} + +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 new file mode 100644 index 0000000..4f47fda --- /dev/null +++ b/WatcherAgent/src/models.rs @@ -0,0 +1,81 @@ +use serde::{Deserialize, Serialize}; +use tokio::time::Instant; + +// Data structures matching the C# DTOs +#[derive(Serialize, Debug)] +pub struct RegistrationDto { + #[serde(rename = "id")] + pub id: i32, + #[serde(rename = "ipAddress")] + pub ip_address: String, + #[serde(rename = "cpuType")] + pub cpu_type: String, + #[serde(rename = "cpuCores")] + pub cpu_cores: i32, + #[serde(rename = "gpuType")] + pub gpu_type: String, + #[serde(rename = "ramSize")] + pub ram_size: f64, +} + +#[derive(Serialize, Debug)] +pub struct MetricDto { + #[serde(rename = "serverId")] + pub server_id: i32, + #[serde(rename = "ipAddress")] + pub ip_address: String, + #[serde(rename = "cpu_Load")] + pub cpu_load: f64, + #[serde(rename = "cpu_Temp")] + pub cpu_temp: f64, + #[serde(rename = "gpu_Load")] + pub gpu_load: f64, + #[serde(rename = "gpu_Temp")] + pub gpu_temp: f64, + #[serde(rename = "gpu_Vram_Size")] + pub gpu_vram_size: f64, + #[serde(rename = "gpu_Vram_Usage")] + pub gpu_vram_usage: f64, + #[serde(rename = "ram_Load")] + pub ram_load: f64, + #[serde(rename = "ram_Size")] + pub ram_size: f64, + #[serde(rename = "disk_Size")] + pub disk_size: f64, + #[serde(rename = "disk_Usage")] + pub disk_usage: f64, + #[serde(rename = "disk_Temp")] + pub disk_temp: f64, + #[serde(rename = "net_In")] + pub net_in: f64, + #[serde(rename = "net_Out")] + pub net_out: f64, +} + +#[derive(Deserialize)] +pub struct IdResponse { + pub id: i32, + #[serde(rename = "ipAddress")] + pub ip_address: String, +} + +#[derive(Serialize)] +pub struct HeartbeatDto { + #[serde(rename = "IpAddress")] + pub ip_address: String, +} + +#[derive(Serialize, Debug)] +pub struct HardwareDto { + pub cpu_type: String, + pub cpu_cores: i32, + pub gpu_type: String, + pub ram_size: f64, + pub ip_address: String, +} + +pub struct NetworkState { + pub prev_rx: u64, + pub prev_tx: u64, + pub last_update: Instant, +}