diff --git a/socktop/src/app.rs b/socktop/src/app.rs index 65cc5a7..e239c3f 100644 --- a/socktop/src/app.rs +++ b/socktop/src/app.rs @@ -24,7 +24,7 @@ use crate::types::Metrics; use crate::ui::cpu::{ draw_cpu_avg_graph, draw_per_core_bars, per_core_clamp, per_core_content_area, per_core_handle_key, per_core_handle_mouse, per_core_handle_scrollbar_mouse, - PerCoreScrollDrag, + PerCoreScrollDrag }; use crate::ui::{ disks::draw_disks, @@ -32,7 +32,7 @@ use crate::ui::{ mem::draw_mem, net::draw_net_spark, processes::draw_top_processes, - swap::draw_swap, + swap::draw_swap }; use crate::ui::gpu::draw_gpu; use crate::ws::{connect, request_metrics}; @@ -74,7 +74,7 @@ impl App { tx_peak: 0, should_quit: false, per_core_scroll: 0, - per_core_drag: None, + per_core_drag: None } } @@ -125,7 +125,7 @@ impl App { Constraint::Ratio(1, 3), Constraint::Length(3), Constraint::Length(3), - Constraint::Min(10), + Constraint::Min(10) ]) .split(area); let top = ratatui::layout::Layout::default() @@ -154,7 +154,7 @@ impl App { Constraint::Ratio(1, 3), Constraint::Length(3), Constraint::Length(3), - Constraint::Min(10), + Constraint::Min(10) ]) .split(area); let top = ratatui::layout::Layout::default() @@ -256,7 +256,7 @@ impl App { 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) + Constraint::Min(10) // bottom: disks + net (left), top procs (right) ]) .split(area); @@ -313,7 +313,7 @@ impl App { .constraints([ Constraint::Min(7), // Disks grow Constraint::Length(3), // Download - Constraint::Length(3), // Upload + Constraint::Length(3) // Upload ]) .split(bottom_lr[0]); @@ -359,7 +359,7 @@ impl Default for App { tx_peak: 0, should_quit: false, per_core_scroll: 0, - per_core_drag: None, + per_core_drag: None } } } diff --git a/socktop/src/ui/cpu.rs b/socktop/src/ui/cpu.rs index 7ffbbba..9b5e200 100644 --- a/socktop/src/ui/cpu.rs +++ b/socktop/src/ui/cpu.rs @@ -100,7 +100,7 @@ pub fn per_core_handle_scrollbar_mouse( drag: &mut Option, mouse: MouseEvent, per_core_area: Rect, - total_rows: usize, + total_rows: usize ) { // Geometry let inner = Rect { @@ -265,7 +265,7 @@ pub fn draw_per_core_bars( area: Rect, m: Option<&Metrics>, per_core_hist: &PerCoreHistory, - scroll_offset: usize, + scroll_offset: usize ) { f.render_widget( Block::default().borders(Borders::ALL).title("Per-core"), diff --git a/socktop/src/ui/gpu.rs b/socktop/src/ui/gpu.rs index 2a696c6..acaa98d 100644 --- a/socktop/src/ui/gpu.rs +++ b/socktop/src/ui/gpu.rs @@ -2,7 +2,7 @@ use ratatui::{ layout::{Constraint, Direction, Layout, Rect}, style::{Color, Style}, text::Span, - widgets::{Block, Borders, Gauge, Paragraph}, + widgets::{Block, Borders, Gauge, Paragraph} }; use crate::types::Metrics; @@ -110,7 +110,7 @@ pub fn draw_gpu(f: &mut ratatui::Frame<'_>, area: Rect, m: Option<&Metrics>) { f.render_widget( Paragraph::new(Span::raw(format!("vram: {used_s}/{total_s} ({mem_pct}%)"))) .style(Style::default().fg(Color::Gray)), - mem_cols[1], + mem_cols[1] ); } } \ No newline at end of file diff --git a/socktop/src/ui/net.rs b/socktop/src/ui/net.rs index 1e6665a..5e4e6ab 100644 --- a/socktop/src/ui/net.rs +++ b/socktop/src/ui/net.rs @@ -3,7 +3,7 @@ use ratatui::{ layout::Rect, style::{Color, Style}, - widgets::{Block, Borders, Sparkline}, + widgets::{Block, Borders, Sparkline} }; use std::collections::VecDeque; @@ -12,7 +12,7 @@ pub fn draw_net_spark( area: Rect, title: &str, hist: &VecDeque, - color: Color, + color: Color ) { let max_points = area.width.saturating_sub(2) as usize; let start = hist.len().saturating_sub(max_points); diff --git a/socktop_agent/src/gpu.rs b/socktop_agent/src/gpu.rs index 6e1e06d..1916993 100644 --- a/socktop_agent/src/gpu.rs +++ b/socktop_agent/src/gpu.rs @@ -6,7 +6,7 @@ pub struct GpuMetrics { pub name: String, pub utilization_gpu_pct: u32, // 0..100 pub mem_used_bytes: u64, - pub mem_total_bytes: u64, + pub mem_total_bytes: u64 } pub fn collect_all_gpus() -> Result, Box> { @@ -17,7 +17,7 @@ pub fn collect_all_gpus() -> Result, Box> 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(), + mem_total_bytes: info.total_vram() }; Ok(vec![metrics]) diff --git a/socktop_agent/src/main.rs b/socktop_agent/src/main.rs index 9d61d89..5fa46cc 100644 --- a/socktop_agent/src/main.rs +++ b/socktop_agent/src/main.rs @@ -54,12 +54,15 @@ async fn main() { let state = AppState { sys: Arc::new(Mutex::new(sys)), last_json: Arc::new(RwLock::new(String::new())), + components: Arc::new(Mutex::new(components)), + disks: Arc::new(Mutex::new(disks)), + networks: Arc::new(Mutex::new(nets)), // new: adaptive sampling controls client_count: Arc::new(AtomicUsize::new(0)), wake_sampler: Arc::new(Notify::new()), auth_token: std::env::var("SOCKTOP_TOKEN") .ok() - .filter(|s| !s.is_empty()), + .filter(|s| !s.is_empty()) }; // Start background sampler (adjust cadence as needed) diff --git a/socktop_agent/src/metrics.rs b/socktop_agent/src/metrics.rs index 0e1787a..7ed4870 100644 --- a/socktop_agent/src/metrics.rs +++ b/socktop_agent/src/metrics.rs @@ -4,19 +4,23 @@ use crate::gpu::collect_all_gpus; use crate::state::AppState; use crate::types::{DiskInfo, Metrics, NetworkInfo, ProcessInfo}; -use sysinfo::{Components, Disks, Networks, System}; +use std::cmp::Ordering; +use sysinfo::{ProcessesToUpdate, System}; use tracing::warn; pub async fn collect_metrics(state: &AppState) -> Metrics { let mut sys = state.sys.lock().await; + // Targeted refresh: CPU/mem/processes only if let Err(e) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - sys.refresh_all(); + sys.refresh_cpu_usage(); + sys.refresh_memory(); + sys.refresh_processes(ProcessesToUpdate::All, true); })) { - warn!("sysinfo refresh panicked: {e:?}"); + warn!("sysinfo selective refresh panicked: {e:?}"); } - // Hostname (associated fn on System in 0.37) + // Hostname let hostname = System::host_name().unwrap_or_else(|| "unknown".to_string()); // CPU usage @@ -29,52 +33,82 @@ pub async fn collect_metrics(state: &AppState) -> Metrics { let swap_total = sys.total_swap(); let swap_used = sys.used_swap(); - // Temperature (via Components container) - let components = Components::new_with_refreshed_list(); - let cpu_temp_c = components.iter().find_map(|c| { - let l = c.label().to_ascii_lowercase(); - if l.contains("cpu") || l.contains("package") || l.contains("tctl") || l.contains("tdie") { - c.temperature() - } else { - None - } + drop(sys); // release quickly before touching other locks + + // Components (cached): just refresh temps + let cpu_temp_c = { + let mut components = state.components.lock().await; + components.refresh(true); + components.iter().find_map(|c| { + let l = c.label().to_ascii_lowercase(); + if l.contains("cpu") || l.contains("package") || l.contains("tctl") || l.contains("tdie") { + c.temperature() + } else { + None + } + }) + }; + + // Disks (cached): refresh sizes/usage, reuse enumeration + let disks: Vec = { + let mut disks_list = state.disks.lock().await; + disks_list.refresh(true); + disks_list + .iter() + .map(|d| DiskInfo { + name: d.name().to_string_lossy().into_owned(), + total: d.total_space(), + available: d.available_space(), + }) + .collect() + }; + + // Networks (cached): refresh counters + let networks: Vec = { + let mut nets = state.networks.lock().await; + nets.refresh(true); + nets.iter() + .map(|(name, data)| NetworkInfo { + name: name.to_string(), + received: data.total_received(), + transmitted: data.total_transmitted() + }) + .collect() + }; + + // Processes: only collect fields we use (pid, name, cpu, mem), keep top K efficiently + const TOP_K: usize = 30; + let mut procs: Vec = { + let sys = state.sys.lock().await; // re-lock briefly to read processes + sys.processes() + .values() + .map(|p| ProcessInfo { + pid: p.pid().as_u32(), + name: p.name().to_string_lossy().into_owned(), + cpu_usage: p.cpu_usage(), + mem_bytes: p.memory() + }) + .collect() + }; + + if procs.len() > TOP_K { + procs.select_nth_unstable_by(TOP_K, |a, b| { + b.cpu_usage + .partial_cmp(&a.cpu_usage) + .unwrap_or(Ordering::Equal) + }); + procs.truncate(TOP_K); + } + procs.sort_by(|a, b| { + b.cpu_usage + .partial_cmp(&a.cpu_usage) + .unwrap_or(Ordering::Equal) }); - // Disks (via Disks container) - let disks_list = Disks::new_with_refreshed_list(); - let disks: Vec = disks_list - .iter() - .map(|d| DiskInfo { - name: d.name().to_string_lossy().into_owned(), - total: d.total_space(), - available: d.available_space(), - }) - .collect(); - - // Networks (via Networks container) – include interface name - let nets = Networks::new_with_refreshed_list(); - let networks: Vec = nets - .iter() - .map(|(name, data)| NetworkInfo { - name: name.to_string(), - received: data.total_received(), - transmitted: data.total_transmitted(), - }) - .collect(); - - // Processes (top N by CPU) - let mut procs: Vec = sys - .processes() - .values() - .map(|p| ProcessInfo { - pid: p.pid().as_u32(), - name: p.name().to_string_lossy().into_owned(), - cpu_usage: p.cpu_usage(), - mem_bytes: p.memory(), - }) - .collect(); - procs.sort_by(|a, b| b.cpu_usage.partial_cmp(&a.cpu_usage).unwrap_or(std::cmp::Ordering::Equal)); - procs.truncate(30); + let process_count = { + let sys = state.sys.lock().await; + sys.processes().len() + }; // GPU(s) let gpus = match collect_all_gpus() { @@ -93,12 +127,12 @@ pub async fn collect_metrics(state: &AppState) -> Metrics { mem_used, swap_total, swap_used, - process_count: sys.processes().len(), + process_count, hostname, cpu_temp_c, disks, networks, top_processes: procs, - gpus, + gpus } } \ No newline at end of file diff --git a/socktop_agent/src/proto.rs b/socktop_agent/src/proto.rs index 3ff466f..459cbfb 100644 --- a/socktop_agent/src/proto.rs +++ b/socktop_agent/src/proto.rs @@ -13,13 +13,13 @@ pub struct Metrics { pub swap_total_mb: u64, pub swap_used_mb: u64, pub net_aggregate: NetTotals, - pub top_processes: Vec, + pub top_processes: Vec } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NetTotals { pub rx_bytes: u64, - pub tx_bytes: u64, + pub tx_bytes: u64 } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -28,5 +28,5 @@ pub struct Proc { pub name: String, pub cpu: f32, pub mem_mb: u64, - pub status: String, + pub status: String } \ No newline at end of file diff --git a/socktop_agent/src/state.rs b/socktop_agent/src/state.rs index d8c2a04..5a1efde 100644 --- a/socktop_agent/src/state.rs +++ b/socktop_agent/src/state.rs @@ -2,10 +2,13 @@ use std::sync::atomic::AtomicUsize; use std::sync::Arc; -use sysinfo::System; +use sysinfo::{Components, Disks, Networks, System}; use tokio::sync::{Mutex, Notify, RwLock}; pub type SharedSystem = Arc>; +pub type SharedComponents = Arc>; +pub type SharedDisks = Arc>; +pub type SharedNetworks = Arc>; #[derive(Clone)] pub struct AppState { @@ -19,4 +22,30 @@ pub struct AppState { pub client_count: Arc, pub wake_sampler: Arc, pub auth_token: Option, + + // Cached containers (enumerated once; refreshed per tick) + pub components: SharedComponents, + pub disks: SharedDisks, + pub networks: SharedNetworks +} + +impl AppState { + + #[allow(dead_code)] + pub fn new() -> Self { + let sys = System::new(); // targeted refreshes per tick + let components = Components::new_with_refreshed_list(); // enumerate once + let disks = Disks::new_with_refreshed_list(); + let networks = Networks::new_with_refreshed_list(); + Self { + sys: Arc::new(Mutex::new(sys)), + components: Arc::new(Mutex::new(components)), + disks: Arc::new(Mutex::new(disks)), + networks: Arc::new(Mutex::new(networks)), + last_json: Arc::new(RwLock::new(String::new())), + client_count: Arc::new(AtomicUsize::new(0)), + wake_sampler: Arc::new(Notify::new()), + auth_token: std::env::var("SOCKTOP_TOKEN").ok().filter(|s| !s.is_empty()) + } + } } diff --git a/socktop_agent/src/types.rs b/socktop_agent/src/types.rs index c27c147..5d4fca3 100644 --- a/socktop_agent/src/types.rs +++ b/socktop_agent/src/types.rs @@ -9,14 +9,14 @@ pub struct ProcessInfo { pub pid: u32, pub name: String, pub cpu_usage: f32, - pub mem_bytes: u64, + pub mem_bytes: u64 } #[derive(Debug, Serialize, Clone)] pub struct DiskInfo { pub name: String, pub total: u64, - pub available: u64, + pub available: u64 } #[derive(Debug, Serialize, Clone)] @@ -24,7 +24,7 @@ pub struct NetworkInfo { pub name: String, // cumulative totals since the agent started (client should diff to get rates) pub received: u64, - pub transmitted: u64, + pub transmitted: u64 } #[derive(Serialize)] @@ -41,5 +41,5 @@ pub struct Metrics { pub disks: Vec, pub networks: Vec, pub top_processes: Vec, - pub gpus: Option>, // new + pub gpus: Option> }