use std::collections::VecDeque;
use std::fs;
use std::io::{BufRead, BufReader};
use std::process::{Command, Stdio};
use std::sync::mpsc::{self, Receiver};
use std::thread;
use std::time::{Duration, Instant};
pub struct HwStats {
pub cpu_usage: f32,
pub cpu_history: VecDeque<f32>,
pub mem_usage: f32,
pub gpu_usage: f32,
pub gpu_history: VecDeque<f32>,
pub vram_usage: f32, pub gpu_name: String,
last_proc_time: u64,
last_cpu_total: u64,
last_update: Instant,
nvtop_rx: Receiver<String>,
}
impl Default for HwStats {
fn default() -> Self {
let mut history = VecDeque::with_capacity(60);
history.resize(60, 0.0);
let mut gpu_history = VecDeque::with_capacity(60);
gpu_history.resize(60, 0.0);
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let mut child = match Command::new("nvtop")
.args(["-l", "-d", "10"])
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
{
Ok(c) => c,
Err(_) => return,
};
if let Some(stdout) = child.stdout.take() {
let reader = BufReader::new(stdout);
let mut json_buffer = String::new();
let mut bracket_count = 0;
for line in reader.lines().map_while(Result::ok) {
for c in line.chars() {
if c == '[' {
bracket_count += 1;
}
if bracket_count > 0 {
json_buffer.push(c);
}
if c == ']' {
bracket_count -= 1;
if bracket_count == 0 {
let _ = tx.send(json_buffer.clone());
json_buffer.clear();
}
}
}
if bracket_count > 0 {
json_buffer.push('\n');
}
}
}
let _ = child.wait();
});
Self {
cpu_usage: 0.0,
cpu_history: history,
mem_usage: 0.0,
gpu_usage: 0.0,
gpu_history,
vram_usage: 0.0,
gpu_name: "DRM Primary Device".to_string(),
last_proc_time: 0,
last_cpu_total: 0,
last_update: Instant::now().checked_sub(Duration::from_secs(10)).unwrap(),
nvtop_rx: rx,
}
}
}
fn parse_numeric(val: &str) -> f32 {
val.chars()
.take_while(|c| c.is_numeric() || *c == '.')
.collect::<String>()
.parse::<f32>()
.unwrap_or(0.0)
}
impl HwStats {
pub fn update_throttled(&mut self) {
if self.last_update.elapsed().as_millis() < 1000 {
return;
}
self.last_update = Instant::now();
let my_pid = std::process::id().to_string();
let mut mem_total = 0.0;
if let Ok(meminfo) = fs::read_to_string("/proc/meminfo") {
for line in meminfo.lines() {
if line.starts_with("MemTotal:") {
mem_total = line
.split_whitespace()
.nth(1)
.unwrap_or("0")
.parse()
.unwrap_or(0.0);
break;
}
}
}
if mem_total > 0.0
&& let Ok(status) = fs::read_to_string("/proc/self/status") {
for line in status.lines() {
if line.starts_with("VmRSS:") {
let proc_rss: f32 = line
.split_whitespace()
.nth(1)
.unwrap_or("0")
.parse()
.unwrap_or(0.0);
self.mem_usage = (proc_rss / mem_total) * 100.0;
break;
}
}
}
let mut current_cpu_total = 0u64;
if let Ok(stat) = fs::read_to_string("/proc/stat")
&& let Some(cpu_line) = stat.lines().next() {
current_cpu_total = cpu_line
.split_whitespace()
.skip(1)
.filter_map(|s| s.parse::<u64>().ok())
.sum();
}
if let Ok(proc_stat) = fs::read_to_string("/proc/self/stat") {
let parts: Vec<&str> = proc_stat.split_whitespace().collect();
if parts.len() >= 15 {
let utime: u64 = parts[13].parse().unwrap_or(0);
let stime: u64 = parts[14].parse().unwrap_or(0);
let current_proc_time = utime + stime;
let diff_proc = current_proc_time.saturating_sub(self.last_proc_time);
let diff_total = current_cpu_total.saturating_sub(self.last_cpu_total);
if diff_total > 0 && self.last_cpu_total > 0 {
self.cpu_usage = (diff_proc as f32 / diff_total as f32) * 1000.0;
self.cpu_history.pop_front();
self.cpu_history.push_back(self.cpu_usage);
}
self.last_proc_time = current_proc_time;
self.last_cpu_total = current_cpu_total;
}
}
while let Ok(json_str) = self.nvtop_rx.try_recv() {
if let Ok(devices) = serde_json::from_str::<Vec<serde_json::Value>>(&json_str) {
for dev in devices {
let dev_name = dev["device_name"].as_str().unwrap_or("GPU");
let gpu_mem_total_str = dev["mem_total"].as_str().unwrap_or("0");
let gpu_mem_total = parse_numeric(gpu_mem_total_str);
if let Some(procs) = dev["processes"].as_array()
&& let Some(my_proc) = procs
.iter()
.find(|p| p["pid"].as_str().unwrap_or("") == my_pid)
{
self.gpu_name = dev_name.to_string();
self.gpu_usage = my_proc["gpu_usage"]
.as_str()
.unwrap_or("0")
.parse::<f32>()
.unwrap_or(0.0);
let alloc_str = my_proc["gpu_mem_bytes_alloc"].as_str().unwrap_or("0");
let alloc = parse_numeric(alloc_str);
if gpu_mem_total > 0.0 {
self.vram_usage = (alloc / gpu_mem_total) * 100.0;
}
self.gpu_history.pop_front();
self.gpu_history.push_back(self.gpu_usage);
break;
}
}
}
}
}
}