From 20278d67f1a7f56972710ebafb56a6851091d9c2 Mon Sep 17 00:00:00 2001 From: jasonwitty Date: Mon, 11 Aug 2025 12:04:55 -0700 Subject: [PATCH] new feature: gpu support --- .vscode/launch.json | 83 ++++++++++++ Cargo.lock | 236 +++++++++++++++++++++++++++++++++-- socktop/src/app.rs | 64 +++++++--- socktop/src/types.rs | 10 ++ socktop/src/ui/gpu.rs | 115 +++++++++++++++++ socktop/src/ui/mod.rs | 1 + socktop_agent/Cargo.toml | 4 +- socktop_agent/src/gpu.rs | 26 ++++ socktop_agent/src/main.rs | 1 + socktop_agent/src/metrics.rs | 14 +++ socktop_agent/src/types.rs | 4 +- 11 files changed, 525 insertions(+), 33 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 socktop/src/ui/gpu.rs create mode 100644 socktop_agent/src/gpu.rs diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..5f839d8 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,83 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'socktop'", + "cargo": { + "args": [ + "build", + "--bin=socktop", + "--package=socktop" + ], + "filter": { + "name": "socktop", + "kind": "bin" + } + }, + "args": ["ws://127.0.0.1:3000/ws"], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'socktop'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=socktop", + "--package=socktop" + ], + "filter": { + "name": "socktop", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'socktop_agent'", + "cargo": { + "args": [ + "build", + "--bin=socktop_agent", + "--package=socktop_agent" + ], + "filter": { + "name": "socktop_agent", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'socktop_agent'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=socktop_agent", + "--package=socktop_agent" + ], + "filter": { + "name": "socktop_agent", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 98aaf20..3f5de8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -253,6 +253,16 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -534,6 +544,21 @@ dependencies = [ "wasi", ] +[[package]] +name = "gfxinfo" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f33b12fba19d158bebe0262fd2b6c49fee6f0e0e089f4f3fdc11c017548473" +dependencies = [ + "core-foundation", + "io-kit-sys", + "libdrm_amdgpu_sys", + "nvml-wrapper", + "serde", + "windows 0.61.3", + "wmi", +] + [[package]] name = "gimli" version = "0.31.1" @@ -650,7 +675,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core", + "windows-core 0.61.2", ] [[package]] @@ -794,6 +819,16 @@ dependencies = [ "syn", ] +[[package]] +name = "io-kit-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" +dependencies = [ + "core-foundation-sys", + "mach2", +] + [[package]] name = "io-uring" version = "0.7.9" @@ -842,6 +877,25 @@ version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +[[package]] +name = "libdrm_amdgpu_sys" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7bf95f3c5d8a8ae4a5c0c46fe2271c16fdf8fd4b26978f44b082a0f8ebaaae" +dependencies = [ + "libc", +] + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.3", +] + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -879,6 +933,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + [[package]] name = "matchers" version = "0.1.0" @@ -967,6 +1030,29 @@ dependencies = [ "autocfg", ] +[[package]] +name = "nvml-wrapper" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9bff0aa1d48904a1385ea2a8b97576fbdcbc9a3cfccd0d31fe978e1c4038c5" +dependencies = [ + "bitflags", + "libloading", + "nvml-wrapper-sys", + "static_assertions", + "thiserror 1.0.69", + "wrapcenum-derive", +] + +[[package]] +name = "nvml-wrapper-sys" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "698d45156f28781a4e79652b6ebe2eaa0589057d588d3aec1333f6466f13fcb5" +dependencies = [ + "libloading", +] + [[package]] name = "objc2-core-foundation" version = "0.3.1" @@ -1389,6 +1475,8 @@ dependencies = [ "axum", "futures", "futures-util", + "gfxinfo", + "nvml-wrapper", "serde", "serde_json", "sysinfo", @@ -1476,7 +1564,7 @@ dependencies = [ "ntapi", "objc2-core-foundation", "objc2-io-kit", - "windows", + "windows 0.61.3", ] [[package]] @@ -1485,7 +1573,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", ] [[package]] @@ -1499,6 +1596,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.9" @@ -1665,7 +1773,7 @@ dependencies = [ "log", "rand", "sha1", - "thiserror", + "thiserror 1.0.69", "utf-8", ] @@ -1825,17 +1933,39 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf874e74c7a99773e62b1c671427abf01a425e77c3d3fb9fb1e4883ea934529" +dependencies = [ + "windows-collections 0.1.1", + "windows-core 0.60.1", + "windows-future 0.1.1", + "windows-link", + "windows-numerics 0.1.1", +] + [[package]] name = "windows" version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-collections", - "windows-core", - "windows-future", + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", "windows-link", - "windows-numerics", + "windows-numerics 0.2.0", +] + +[[package]] +name = "windows-collections" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5467f79cc1ba3f52ebb2ed41dbb459b8e7db636cc3429458d9a852e15bc24dec" +dependencies = [ + "windows-core 0.60.1", ] [[package]] @@ -1844,7 +1974,20 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core", + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.60.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca21a92a9cae9bf4ccae5cf8368dce0837100ddf6e6d57936749e85f152f6247" +dependencies = [ + "windows-implement 0.59.0", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings 0.3.1", ] [[package]] @@ -1853,11 +1996,21 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement", + "windows-implement 0.60.0", "windows-interface", "windows-link", "windows-result", - "windows-strings", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-future" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a787db4595e7eb80239b74ce8babfb1363d8e343ab072f2ffe901400c03349f0" +dependencies = [ + "windows-core 0.60.1", + "windows-link", ] [[package]] @@ -1866,11 +2019,22 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link", "windows-threading", ] +[[package]] +name = "windows-implement" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-implement" version = "0.60.0" @@ -1899,13 +2063,23 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-numerics" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "005dea54e2f6499f2cee279b8f703b3cf3b5734a2d8d21867c8f44003182eeed" +dependencies = [ + "windows-core 0.60.1", + "windows-link", +] + [[package]] name = "windows-numerics" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link", ] @@ -1918,6 +2092,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-strings" version = "0.4.2" @@ -2149,6 +2332,33 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +[[package]] +name = "wmi" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f902b4592b911109e7352bcfec7b754b07ec71e514d7dfa280eaef924c1cb08" +dependencies = [ + "chrono", + "futures", + "log", + "serde", + "thiserror 2.0.12", + "windows 0.60.0", + "windows-core 0.60.1", +] + +[[package]] +name = "wrapcenum-derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76ff259533532054cfbaefb115c613203c73707017459206380f03b3b3f266e" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "writeable" version = "0.6.1" diff --git a/socktop/src/app.rs b/socktop/src/app.rs index 89f6e56..65cc5a7 100644 --- a/socktop/src/app.rs +++ b/socktop/src/app.rs @@ -13,7 +13,8 @@ use crossterm::{ }; use ratatui::{ backend::CrosstermBackend, - layout::{Constraint, Direction, Rect}, // add Rect + layout::{Constraint, Direction, Rect}, + //style::Color, // + add Color Terminal, }; use tokio::time::sleep; @@ -33,6 +34,7 @@ use crate::ui::{ processes::draw_top_processes, swap::draw_swap, }; +use crate::ui::gpu::draw_gpu; use crate::ws::{connect, request_metrics}; pub struct App { @@ -243,52 +245,77 @@ impl App { self.last_metrics = Some(m); } - fn draw(&mut self, f: &mut ratatui::Frame<'_>) { + pub fn draw(&mut self, f: &mut ratatui::Frame<'_>) { let area = f.area(); + // Root rows: header, top (cpu avg + per-core), memory, swap, bottom let rows = ratatui::layout::Layout::default() .direction(Direction::Vertical) .constraints([ - Constraint::Length(1), - Constraint::Ratio(1, 3), - Constraint::Length(3), - Constraint::Length(3), - Constraint::Min(10), + Constraint::Length(1), // header + Constraint::Ratio(1, 3), // top row + Constraint::Length(3), // memory (left) + GPU (right, part 1) + Constraint::Length(3), // swap (left) + GPU (right, part 2) + Constraint::Min(10), // bottom: disks + net (left), top procs (right) ]) .split(area); + // Header draw_header(f, rows[0], self.last_metrics.as_ref()); - let top = ratatui::layout::Layout::default() + // Top row: left CPU avg, right Per-core (full top-right) + let top_lr = ratatui::layout::Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Percentage(66), Constraint::Percentage(34)]) .split(rows[1]); - draw_cpu_avg_graph(f, top[0], &self.cpu_hist, self.last_metrics.as_ref()); + draw_cpu_avg_graph(f, top_lr[0], &self.cpu_hist, self.last_metrics.as_ref()); draw_per_core_bars( f, - top[1], + top_lr[1], self.last_metrics.as_ref(), &self.per_core_hist, self.per_core_scroll, ); - draw_mem(f, rows[2], self.last_metrics.as_ref()); - draw_swap(f, rows[3], self.last_metrics.as_ref()); + // Memory + Swap rows split into left/right columns + let mem_lr = ratatui::layout::Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(66), Constraint::Percentage(34)]) + .split(rows[2]); + let swap_lr = ratatui::layout::Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(66), Constraint::Percentage(34)]) + .split(rows[3]); - let bottom = ratatui::layout::Layout::default() + // Left: Memory + Swap + draw_mem(f, mem_lr[0], self.last_metrics.as_ref()); + draw_swap(f, swap_lr[0], self.last_metrics.as_ref()); + + // Right: GPU spans the same vertical space as Memory + Swap + let gpu_area = ratatui::layout::Rect { + x: mem_lr[1].x, + y: mem_lr[1].y, + width: mem_lr[1].width, + height: mem_lr[1].height + swap_lr[1].height, + }; + draw_gpu(f, gpu_area, self.last_metrics.as_ref()); + + // Bottom area: left = Disks + Network, right = Top Processes + let bottom_lr = ratatui::layout::Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Percentage(66), Constraint::Percentage(34)]) .split(rows[4]); + // Left bottom: Disks + Net stacked (network "back up") let left_stack = ratatui::layout::Layout::default() .direction(Direction::Vertical) .constraints([ - Constraint::Min(6), - Constraint::Length(4), - Constraint::Length(4), + Constraint::Min(7), // Disks grow + Constraint::Length(3), // Download + Constraint::Length(3), // Upload ]) - .split(bottom[0]); + .split(bottom_lr[0]); draw_disks(f, left_stack[0], self.last_metrics.as_ref()); draw_net_spark( @@ -314,7 +341,8 @@ impl App { ratatui::style::Color::Blue, ); - draw_top_processes(f, bottom[1], self.last_metrics.as_ref()); + // Right bottom: Top Processes fills the column + draw_top_processes(f, bottom_lr[1], self.last_metrics.as_ref()); } } diff --git a/socktop/src/types.rs b/socktop/src/types.rs index d5386ec..4e94f13 100644 --- a/socktop/src/types.rs +++ b/socktop/src/types.rs @@ -24,6 +24,15 @@ pub struct ProcessInfo { pub mem_bytes: u64, } +#[derive(Debug, Clone, serde::Deserialize)] +pub struct GpuMetrics { + pub name: String, + pub utilization_gpu_pct: u32, + pub mem_used_bytes: u64, + pub mem_total_bytes: u64, + // pub vendor: Option, +} + #[derive(Debug, Deserialize, Clone)] pub struct Metrics { pub cpu_total: f32, @@ -38,4 +47,5 @@ pub struct Metrics { pub disks: Vec, pub networks: Vec, pub top_processes: Vec, + pub gpus: Option>, } diff --git a/socktop/src/ui/gpu.rs b/socktop/src/ui/gpu.rs new file mode 100644 index 0000000..544c6c4 --- /dev/null +++ b/socktop/src/ui/gpu.rs @@ -0,0 +1,115 @@ +use ratatui::{ + layout::{Constraint, Direction, Layout, Rect}, + style::{Color, Style}, + text::Span, + widgets::{Block, Borders, Gauge, Paragraph}, +}; + +use crate::types::Metrics; + +fn fmt_bytes(b: u64) -> String { + const KB: f64 = 1024.0; + const MB: f64 = KB * 1024.0; + const GB: f64 = MB * 1024.0; + let fb = b as f64; + if fb >= GB { format!("{:.1}G", fb / GB) } + else if fb >= MB { format!("{:.1}M", fb / MB) } + else if fb >= KB { format!("{:.1}K", fb / KB) } + else { format!("{}B", b) } +} + +pub fn draw_gpu(f: &mut ratatui::Frame<'_>, area: Rect, m: Option<&Metrics>) { + let mut area = area; + let block = Block::default().borders(Borders::ALL).title("GPU"); + f.render_widget(block, area); + + // Guard: need some space inside the block + if area.height <= 2 || area.width <= 2 { + return; + } + + // Inner padding consistent with the rest of the app + area.y += 1; + area.height = area.height.saturating_sub(2); + area.x += 1; + area.width = area.width.saturating_sub(2); + + let Some(metrics) = m else { return; }; + let Some(gpus) = metrics.gpus.as_ref() else { + f.render_widget(Paragraph::new("No GPUs"), area); + return; + }; + if gpus.is_empty() { + f.render_widget(Paragraph::new("No GPUs"), area); + return; + } + + // Show 3 rows per GPU: name, util bar, vram bar. + if area.height < 3 { + return; + } + let per_gpu_rows: u16 = 3; + let max_gpus = (area.height / per_gpu_rows) as usize; + let count = gpus.len().min(max_gpus); + + let constraints = std::iter::repeat(Constraint::Length(1)) + .take(count * per_gpu_rows as usize) + .collect::>(); + let rows = Layout::default() + .direction(Direction::Vertical) + .constraints(constraints) + .split(area); + + // Per bar horizontal layout: [gauge] [value] + let split_bar = |r: Rect| { + Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Min(8), // gauge column + Constraint::Length(24), // value column + ]) + .split(r) + }; + + for i in 0..count { + let g = &gpus[i]; + + // Row 1: GPU name (and temp can be appended later) + let name_text = format!("{}", g.name); + f.render_widget( + Paragraph::new(Span::raw(name_text)).style(Style::default().fg(Color::Gray)), + rows[i * 3], + ); + + // Row 2: Utilization bar + right label + let util_cols = split_bar(rows[i * 3 + 1]); + let util = g.utilization_gpu_pct.min(100) as u16; + let util_gauge = Gauge::default() + .gauge_style(Style::default().fg(Color::Green)) + .label(Span::raw("")) + .ratio(util as f64 / 100.0); + f.render_widget(util_gauge, util_cols[0]); + f.render_widget( + Paragraph::new(Span::raw(format!("util: {}%", util))).style(Style::default().fg(Color::Gray)), + util_cols[1], + ); + + // Row 3: VRAM bar + right label + let mem_cols = split_bar(rows[i * 3 + 2]); + let used = g.mem_used_bytes; + let total = g.mem_total_bytes.max(1); + let mem_ratio = used as f64 / total as f64; + let mem_pct = (mem_ratio * 100.0).round() as u16; + + let mem_gauge = Gauge::default() + .gauge_style(Style::default().fg(Color::LightMagenta)) + .label(Span::raw("")) + .ratio(mem_ratio); + f.render_widget(mem_gauge, mem_cols[0]); + f.render_widget( + Paragraph::new(Span::raw(format!("vram: {}/{} ({}%)", fmt_bytes(used), fmt_bytes(total), mem_pct))) + .style(Style::default().fg(Color::Gray)), + mem_cols[1], + ); + } +} \ No newline at end of file diff --git a/socktop/src/ui/mod.rs b/socktop/src/ui/mod.rs index b72a25a..3965c66 100644 --- a/socktop/src/ui/mod.rs +++ b/socktop/src/ui/mod.rs @@ -2,6 +2,7 @@ pub mod cpu; pub mod disks; +pub mod gpu; pub mod header; pub mod mem; pub mod net; diff --git a/socktop_agent/Cargo.toml b/socktop_agent/Cargo.toml index f8222a8..fbf8a69 100644 --- a/socktop_agent/Cargo.toml +++ b/socktop_agent/Cargo.toml @@ -14,4 +14,6 @@ serde_json = "1" futures = "0.3" futures-util = "0.3.31" tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } \ No newline at end of file +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +nvml-wrapper = "0.10" +gfxinfo = "0.1.2" \ No newline at end of file diff --git a/socktop_agent/src/gpu.rs b/socktop_agent/src/gpu.rs new file mode 100644 index 0000000..80c8613 --- /dev/null +++ b/socktop_agent/src/gpu.rs @@ -0,0 +1,26 @@ +// gpu.rs +use gfxinfo::active_gpu; + +#[derive(Debug, Clone, serde::Serialize)] +pub struct GpuMetrics { + pub name: String, + pub utilization_gpu_pct: u32, // 0..100 + pub mem_used_bytes: u64, + pub mem_total_bytes: u64, + // pub vendor: String, +} + +pub fn collect_all_gpus() -> Result, Box> { + let gpu = active_gpu()?; // Use ? to unwrap Result + let info = gpu.info(); + + let metrics = GpuMetrics { + name: gpu.model().to_string(), + utilization_gpu_pct: info.load_pct() as u32, + mem_used_bytes: info.used_vram(), + mem_total_bytes: info.total_vram(), + // vendor: gpu.vendor().to_string(), + }; + + Ok(vec![metrics]) +} \ No newline at end of file diff --git a/socktop_agent/src/main.rs b/socktop_agent/src/main.rs index efef2ca..7c01ceb 100644 --- a/socktop_agent/src/main.rs +++ b/socktop_agent/src/main.rs @@ -6,6 +6,7 @@ mod sampler; mod state; mod types; mod ws; +mod gpu; use axum::{routing::get, Router}; use std::{ diff --git a/socktop_agent/src/metrics.rs b/socktop_agent/src/metrics.rs index e55f25c..4f39d90 100644 --- a/socktop_agent/src/metrics.rs +++ b/socktop_agent/src/metrics.rs @@ -1,10 +1,18 @@ //! Metrics collection using sysinfo. Keeps sysinfo handles in AppState to //! avoid repeated allocations and allow efficient refreshes. + +use crate::gpu::collect_all_gpus; + + + use crate::state::AppState; use crate::types::{DiskInfo, Metrics, NetworkInfo, ProcessInfo}; + + use sysinfo::{Components, System}; + pub async fn collect_metrics(state: &AppState) -> Metrics { // System (CPU/mem/proc) let mut sys = state.sys.lock().await; @@ -99,6 +107,11 @@ pub async fn collect_metrics(state: &AppState) -> Metrics { }); } + let gpus = match collect_all_gpus() { + Ok(v) if !v.is_empty() => Some(v), + _ => None, + }; + Metrics { cpu_total: sys.global_cpu_usage(), cpu_per_core: sys.cpus().iter().map(|c| c.cpu_usage()).collect(), @@ -112,6 +125,7 @@ pub async fn collect_metrics(state: &AppState) -> Metrics { disks, networks, top_processes: procs, + gpus, } } diff --git a/socktop_agent/src/types.rs b/socktop_agent/src/types.rs index 5b0410c..c27c147 100644 --- a/socktop_agent/src/types.rs +++ b/socktop_agent/src/types.rs @@ -1,6 +1,7 @@ //! Data types sent to the client over WebSocket. //! Keep this module minimal and stable — it defines the wire format. +use crate::gpu::GpuMetrics; use serde::Serialize; #[derive(Debug, Serialize, Clone)] @@ -26,7 +27,7 @@ pub struct NetworkInfo { pub transmitted: u64, } -#[derive(Debug, Serialize, Clone)] +#[derive(Serialize)] pub struct Metrics { pub cpu_total: f32, pub cpu_per_core: Vec, @@ -40,4 +41,5 @@ pub struct Metrics { pub disks: Vec, pub networks: Vec, pub top_processes: Vec, + pub gpus: Option>, // new }