From dc0e0a9c51cd09b761f24e2547a647b9fcac3916 Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (Pragmatismo)" Date: Wed, 5 Nov 2025 12:28:34 -0300 Subject: [PATCH] feat: add sysinfo dependency and system metrics support Added the sysinfo crate (v0.37.2) to gather system metrics. This includes: - New dependencies: sysinfo, ntapi, objc2-core-foundation, objc2-io-kit - Updated windows-core to specific version 0.62.2 - Initial system metrics integration in bot module The change enables monitoring system resources which will be used for performance optimization and health monitoring. --- Cargo.lock | 110 +++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + src/bot/mod.rs | 47 +++++++++++++++++++- src/lib.rs | 1 + src/main.rs | 2 +- src/nvidia/mod.rs | 86 ++++++++++++++++++++++++++++++++++++ 6 files changed, 243 insertions(+), 4 deletions(-) create mode 100644 src/nvidia/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 26b7f1d2..d999385a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1151,6 +1151,7 @@ dependencies = [ "serde_json", "sha2", "smartstring", + "sysinfo", "tempfile", "time", "tokio", @@ -2821,7 +2822,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core", + "windows-core 0.62.2", ] [[package]] @@ -3642,6 +3643,15 @@ dependencies = [ "nom 8.0.0", ] +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -3695,6 +3705,25 @@ dependencies = [ "autocfg", ] +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + [[package]] name = "object" version = "0.32.2" @@ -5229,6 +5258,20 @@ dependencies = [ "syn", ] +[[package]] +name = "sysinfo" +version = "0.37.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows", +] + [[package]] name = "system-configuration" version = "0.6.1" @@ -6144,6 +6187,41 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + [[package]] name = "windows-core" version = "0.62.2" @@ -6157,6 +6235,17 @@ dependencies = [ "windows-strings 0.5.1", ] +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + [[package]] name = "windows-implement" version = "0.60.2" @@ -6191,6 +6280,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + [[package]] name = "windows-registry" version = "0.5.3" @@ -6331,6 +6430,15 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" diff --git a/Cargo.toml b/Cargo.toml index 546638ad..e6930396 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,6 +89,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" sha2 = "0.10.9" smartstring = "1.0" +sysinfo = "0.37.2" tempfile = "3" time = "0.3.44" tokio = { version = "1.41", features = ["full"] } diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 8e74a5a8..4372872c 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -1,16 +1,19 @@ use crate::config::ConfigManager; use crate::drive_monitor::DriveMonitor; use crate::llm_models; +use crate::nvidia::get_system_metrics; use crate::shared::models::{BotResponse, Suggestion, UserMessage, UserSession}; use crate::shared::state::AppState; use actix_web::{web, HttpRequest, HttpResponse, Result}; use actix_ws::Message as WsMessage; -use chrono::Utc; +use chrono::{Utc}; use diesel::PgConnection; use log::{error, info, trace, warn}; use serde_json; +use tokio::time::Instant; use std::collections::HashMap; use std::sync::Arc; +use std::time::Duration; use tokio::sync::mpsc; use tokio::sync::Mutex as AsyncMutex; use uuid::Uuid; @@ -436,9 +439,10 @@ impl BotOrchestrator { response_tx.send(thinking_response).await?; } + let prompt_clone = prompt.clone(); tokio::spawn(async move { if let Err(e) = llm - .generate_stream(&prompt, &serde_json::Value::Null, stream_tx) + .generate_stream(&prompt_clone, &serde_json::Value::Null, stream_tx) .await { error!("LLM streaming error: {}", e); @@ -450,8 +454,32 @@ impl BotOrchestrator { let mut in_analysis = false; let mut chunk_count = 0; let mut first_word_received = false; + let mut last_progress_update = Instant::now(); + let progress_interval = Duration::from_secs(1); + // Calculate initial token count + let initial_tokens = crate::shared::utils::estimate_token_count(&prompt); let config_manager = ConfigManager::new(Arc::clone(&self.state.conn)); + let max_context_size = config_manager + .get_config( + &Uuid::parse_str(&message.bot_id).unwrap_or_default(), + "llm-server-ctx-size", + None, + ) + .unwrap_or_default() + .parse::() + .unwrap_or(0); + + // Show initial progress + if let Ok(metrics) = get_system_metrics(initial_tokens, max_context_size) { + eprintln!( + "\nNVIDIA: {:.1}% | CPU: {:.1}% | Tokens: {}/{}", + metrics.gpu_usage.unwrap_or(0.0), + metrics.cpu_usage, + initial_tokens, + max_context_size + ); + } let model = config_manager .get_config( &Uuid::parse_str(&message.bot_id).unwrap_or_default(), @@ -498,6 +526,21 @@ impl BotOrchestrator { if !in_analysis { full_response.push_str(&chunk); + // Update progress if interval elapsed + if last_progress_update.elapsed() >= progress_interval { + let current_tokens = initial_tokens + crate::shared::utils::estimate_token_count(&full_response); + if let Ok(metrics) = get_system_metrics(current_tokens, max_context_size) { + eprintln!( + "\nNVIDIA: {:.1}% | CPU: {:.1}% | Tokens: {}/{}", + metrics.gpu_usage.unwrap_or(0.0), + metrics.cpu_usage, + current_tokens, + max_context_size + ); + } + last_progress_update = Instant::now(); + } + let partial = BotResponse { bot_id: message.bot_id.clone(), user_id: message.user_id.clone(), diff --git a/src/lib.rs b/src/lib.rs index 50371f6c..087bc329 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,3 +20,4 @@ pub mod tests; pub mod web_automation; pub mod web_server; pub mod auth; +pub mod nvidia; diff --git a/src/main.rs b/src/main.rs index 3e1612cf..7ee9c6e0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,7 +31,7 @@ pub mod tests; #[cfg(feature = "web_automation")] mod web_automation; mod web_server; - +mod nvidia; use crate::auth::auth_handler; use crate::automation::AutomationService; diff --git a/src/nvidia/mod.rs b/src/nvidia/mod.rs new file mode 100644 index 00000000..6525b79e --- /dev/null +++ b/src/nvidia/mod.rs @@ -0,0 +1,86 @@ +use anyhow::Result; +use log::warn; +use std::collections::HashMap; +use sysinfo::{System}; + +/// System monitoring data +pub struct SystemMetrics { + pub gpu_usage: Option, + pub cpu_usage: f32, + pub token_ratio: f32, +} + +/// Gets current system metrics +pub fn get_system_metrics(current_tokens: usize, max_tokens: usize) -> Result { + let mut sys = System::new(); + sys.refresh_cpu_usage(); + + // Get CPU usage (average across all cores) + let cpu_usage = sys.global_cpu_usage(); + + // Get GPU usage if available + let gpu_usage = if has_nvidia_gpu() { + get_gpu_utilization()?.get("gpu").copied() + } else { + None + }; + + // Calculate token ratio + let token_ratio = if max_tokens > 0 { + current_tokens as f32 / max_tokens as f32 * 100.0 + } else { + 0.0 + }; + + Ok(SystemMetrics { + gpu_usage, + cpu_usage, + token_ratio, + }) +} + +/// Checks if NVIDIA GPU is available +pub fn has_nvidia_gpu() -> bool { + match std::process::Command::new("nvidia-smi") + .arg("--query-gpu=utilization.gpu") + .arg("--format=csv,noheader,nounits") + .output() + { + Ok(output) => output.status.success(), + Err(_) => { + warn!("No NVIDIA GPU detected or nvidia-smi not available"); + false + } + } +} + +/// Gets current GPU utilization percentages +pub fn get_gpu_utilization() -> Result> { + let output = std::process::Command::new("nvidia-smi") + .arg("--query-gpu=utilization.gpu,utilization.memory") + .arg("--format=csv,noheader,nounits") + .output()?; + + if !output.status.success() { + return Err(anyhow::anyhow!("Failed to query GPU utilization")); + } + + let output_str = String::from_utf8(output.stdout)?; + let mut util = HashMap::new(); + + for line in output_str.lines() { + let parts: Vec<&str> = line.split(',').collect(); + if parts.len() >= 2 { + util.insert( + "gpu".to_string(), + parts[0].trim().parse::().unwrap_or(0.0), + ); + util.insert( + "memory".to_string(), + parts[1].trim().parse::().unwrap_or(0.0), + ); + } + } + + Ok(util) +}