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.
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-11-05 12:28:34 -03:00
parent ac9c1509d5
commit dc0e0a9c51
6 changed files with 243 additions and 4 deletions

110
Cargo.lock generated
View file

@ -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"

View file

@ -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"] }

View file

@ -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::<usize>()
.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(),

View file

@ -20,3 +20,4 @@ pub mod tests;
pub mod web_automation;
pub mod web_server;
pub mod auth;
pub mod nvidia;

View file

@ -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;

86
src/nvidia/mod.rs Normal file
View file

@ -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<f32>,
pub cpu_usage: f32,
pub token_ratio: f32,
}
/// Gets current system metrics
pub fn get_system_metrics(current_tokens: usize, max_tokens: usize) -> Result<SystemMetrics> {
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<HashMap<String, f32>> {
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::<f32>().unwrap_or(0.0),
);
util.insert(
"memory".to_string(),
parts[1].trim().parse::<f32>().unwrap_or(0.0),
);
}
}
Ok(util)
}