performance improvements and formatting cleanup

This commit is contained in:
jasonwitty 2025-08-11 22:37:46 -07:00
parent c3f81eef25
commit d69a4104fc
10 changed files with 141 additions and 75 deletions

View File

@ -24,7 +24,7 @@ use crate::types::Metrics;
use crate::ui::cpu::{ use crate::ui::cpu::{
draw_cpu_avg_graph, draw_per_core_bars, per_core_clamp, per_core_content_area, 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, per_core_handle_key, per_core_handle_mouse, per_core_handle_scrollbar_mouse,
PerCoreScrollDrag, PerCoreScrollDrag
}; };
use crate::ui::{ use crate::ui::{
disks::draw_disks, disks::draw_disks,
@ -32,7 +32,7 @@ use crate::ui::{
mem::draw_mem, mem::draw_mem,
net::draw_net_spark, net::draw_net_spark,
processes::draw_top_processes, processes::draw_top_processes,
swap::draw_swap, swap::draw_swap
}; };
use crate::ui::gpu::draw_gpu; use crate::ui::gpu::draw_gpu;
use crate::ws::{connect, request_metrics}; use crate::ws::{connect, request_metrics};
@ -74,7 +74,7 @@ impl App {
tx_peak: 0, tx_peak: 0,
should_quit: false, should_quit: false,
per_core_scroll: 0, per_core_scroll: 0,
per_core_drag: None, per_core_drag: None
} }
} }
@ -125,7 +125,7 @@ impl App {
Constraint::Ratio(1, 3), Constraint::Ratio(1, 3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Min(10), Constraint::Min(10)
]) ])
.split(area); .split(area);
let top = ratatui::layout::Layout::default() let top = ratatui::layout::Layout::default()
@ -154,7 +154,7 @@ impl App {
Constraint::Ratio(1, 3), Constraint::Ratio(1, 3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Min(10), Constraint::Min(10)
]) ])
.split(area); .split(area);
let top = ratatui::layout::Layout::default() let top = ratatui::layout::Layout::default()
@ -256,7 +256,7 @@ impl App {
Constraint::Ratio(1, 3), // top row Constraint::Ratio(1, 3), // top row
Constraint::Length(3), // memory (left) + GPU (right, part 1) Constraint::Length(3), // memory (left) + GPU (right, part 1)
Constraint::Length(3), // swap (left) + GPU (right, part 2) 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); .split(area);
@ -313,7 +313,7 @@ impl App {
.constraints([ .constraints([
Constraint::Min(7), // Disks grow Constraint::Min(7), // Disks grow
Constraint::Length(3), // Download Constraint::Length(3), // Download
Constraint::Length(3), // Upload Constraint::Length(3) // Upload
]) ])
.split(bottom_lr[0]); .split(bottom_lr[0]);
@ -359,7 +359,7 @@ impl Default for App {
tx_peak: 0, tx_peak: 0,
should_quit: false, should_quit: false,
per_core_scroll: 0, per_core_scroll: 0,
per_core_drag: None, per_core_drag: None
} }
} }
} }

View File

@ -100,7 +100,7 @@ pub fn per_core_handle_scrollbar_mouse(
drag: &mut Option<PerCoreScrollDrag>, drag: &mut Option<PerCoreScrollDrag>,
mouse: MouseEvent, mouse: MouseEvent,
per_core_area: Rect, per_core_area: Rect,
total_rows: usize, total_rows: usize
) { ) {
// Geometry // Geometry
let inner = Rect { let inner = Rect {
@ -265,7 +265,7 @@ pub fn draw_per_core_bars(
area: Rect, area: Rect,
m: Option<&Metrics>, m: Option<&Metrics>,
per_core_hist: &PerCoreHistory, per_core_hist: &PerCoreHistory,
scroll_offset: usize, scroll_offset: usize
) { ) {
f.render_widget( f.render_widget(
Block::default().borders(Borders::ALL).title("Per-core"), Block::default().borders(Borders::ALL).title("Per-core"),

View File

@ -2,7 +2,7 @@ use ratatui::{
layout::{Constraint, Direction, Layout, Rect}, layout::{Constraint, Direction, Layout, Rect},
style::{Color, Style}, style::{Color, Style},
text::Span, text::Span,
widgets::{Block, Borders, Gauge, Paragraph}, widgets::{Block, Borders, Gauge, Paragraph}
}; };
use crate::types::Metrics; use crate::types::Metrics;
@ -110,7 +110,7 @@ pub fn draw_gpu(f: &mut ratatui::Frame<'_>, area: Rect, m: Option<&Metrics>) {
f.render_widget( f.render_widget(
Paragraph::new(Span::raw(format!("vram: {used_s}/{total_s} ({mem_pct}%)"))) Paragraph::new(Span::raw(format!("vram: {used_s}/{total_s} ({mem_pct}%)")))
.style(Style::default().fg(Color::Gray)), .style(Style::default().fg(Color::Gray)),
mem_cols[1], mem_cols[1]
); );
} }
} }

View File

@ -3,7 +3,7 @@
use ratatui::{ use ratatui::{
layout::Rect, layout::Rect,
style::{Color, Style}, style::{Color, Style},
widgets::{Block, Borders, Sparkline}, widgets::{Block, Borders, Sparkline}
}; };
use std::collections::VecDeque; use std::collections::VecDeque;
@ -12,7 +12,7 @@ pub fn draw_net_spark(
area: Rect, area: Rect,
title: &str, title: &str,
hist: &VecDeque<u64>, hist: &VecDeque<u64>,
color: Color, color: Color
) { ) {
let max_points = area.width.saturating_sub(2) as usize; let max_points = area.width.saturating_sub(2) as usize;
let start = hist.len().saturating_sub(max_points); let start = hist.len().saturating_sub(max_points);

View File

@ -6,7 +6,7 @@ pub struct GpuMetrics {
pub name: String, pub name: String,
pub utilization_gpu_pct: u32, // 0..100 pub utilization_gpu_pct: u32, // 0..100
pub mem_used_bytes: u64, pub mem_used_bytes: u64,
pub mem_total_bytes: u64, pub mem_total_bytes: u64
} }
pub fn collect_all_gpus() -> Result<Vec<GpuMetrics>, Box<dyn std::error::Error>> { pub fn collect_all_gpus() -> Result<Vec<GpuMetrics>, Box<dyn std::error::Error>> {
@ -17,7 +17,7 @@ pub fn collect_all_gpus() -> Result<Vec<GpuMetrics>, Box<dyn std::error::Error>>
name: gpu.model().to_string(), name: gpu.model().to_string(),
utilization_gpu_pct: info.load_pct() as u32, utilization_gpu_pct: info.load_pct() as u32,
mem_used_bytes: info.used_vram(), mem_used_bytes: info.used_vram(),
mem_total_bytes: info.total_vram(), mem_total_bytes: info.total_vram()
}; };
Ok(vec![metrics]) Ok(vec![metrics])

View File

@ -54,12 +54,15 @@ async fn main() {
let state = AppState { let state = AppState {
sys: Arc::new(Mutex::new(sys)), sys: Arc::new(Mutex::new(sys)),
last_json: Arc::new(RwLock::new(String::new())), 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 // new: adaptive sampling controls
client_count: Arc::new(AtomicUsize::new(0)), client_count: Arc::new(AtomicUsize::new(0)),
wake_sampler: Arc::new(Notify::new()), wake_sampler: Arc::new(Notify::new()),
auth_token: std::env::var("SOCKTOP_TOKEN") auth_token: std::env::var("SOCKTOP_TOKEN")
.ok() .ok()
.filter(|s| !s.is_empty()), .filter(|s| !s.is_empty())
}; };
// Start background sampler (adjust cadence as needed) // Start background sampler (adjust cadence as needed)

View File

@ -4,19 +4,23 @@ use crate::gpu::collect_all_gpus;
use crate::state::AppState; use crate::state::AppState;
use crate::types::{DiskInfo, Metrics, NetworkInfo, ProcessInfo}; use crate::types::{DiskInfo, Metrics, NetworkInfo, ProcessInfo};
use sysinfo::{Components, Disks, Networks, System}; use std::cmp::Ordering;
use sysinfo::{ProcessesToUpdate, System};
use tracing::warn; use tracing::warn;
pub async fn collect_metrics(state: &AppState) -> Metrics { pub async fn collect_metrics(state: &AppState) -> Metrics {
let mut sys = state.sys.lock().await; let mut sys = state.sys.lock().await;
// Targeted refresh: CPU/mem/processes only
if let Err(e) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { 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()); let hostname = System::host_name().unwrap_or_else(|| "unknown".to_string());
// CPU usage // CPU usage
@ -29,52 +33,82 @@ pub async fn collect_metrics(state: &AppState) -> Metrics {
let swap_total = sys.total_swap(); let swap_total = sys.total_swap();
let swap_used = sys.used_swap(); let swap_used = sys.used_swap();
// Temperature (via Components container) drop(sys); // release quickly before touching other locks
let components = Components::new_with_refreshed_list();
let cpu_temp_c = components.iter().find_map(|c| { // Components (cached): just refresh temps
let l = c.label().to_ascii_lowercase(); let cpu_temp_c = {
if l.contains("cpu") || l.contains("package") || l.contains("tctl") || l.contains("tdie") { let mut components = state.components.lock().await;
c.temperature() components.refresh(true);
} else { components.iter().find_map(|c| {
None 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<DiskInfo> = {
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<NetworkInfo> = {
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<ProcessInfo> = {
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 process_count = {
let disks_list = Disks::new_with_refreshed_list(); let sys = state.sys.lock().await;
let disks: Vec<DiskInfo> = disks_list sys.processes().len()
.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<NetworkInfo> = 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<ProcessInfo> = 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);
// GPU(s) // GPU(s)
let gpus = match collect_all_gpus() { let gpus = match collect_all_gpus() {
@ -93,12 +127,12 @@ pub async fn collect_metrics(state: &AppState) -> Metrics {
mem_used, mem_used,
swap_total, swap_total,
swap_used, swap_used,
process_count: sys.processes().len(), process_count,
hostname, hostname,
cpu_temp_c, cpu_temp_c,
disks, disks,
networks, networks,
top_processes: procs, top_processes: procs,
gpus, gpus
} }
} }

View File

@ -13,13 +13,13 @@ pub struct Metrics {
pub swap_total_mb: u64, pub swap_total_mb: u64,
pub swap_used_mb: u64, pub swap_used_mb: u64,
pub net_aggregate: NetTotals, pub net_aggregate: NetTotals,
pub top_processes: Vec<Proc>, pub top_processes: Vec<Proc>
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetTotals { pub struct NetTotals {
pub rx_bytes: u64, pub rx_bytes: u64,
pub tx_bytes: u64, pub tx_bytes: u64
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -28,5 +28,5 @@ pub struct Proc {
pub name: String, pub name: String,
pub cpu: f32, pub cpu: f32,
pub mem_mb: u64, pub mem_mb: u64,
pub status: String, pub status: String
} }

View File

@ -2,10 +2,13 @@
use std::sync::atomic::AtomicUsize; use std::sync::atomic::AtomicUsize;
use std::sync::Arc; use std::sync::Arc;
use sysinfo::System; use sysinfo::{Components, Disks, Networks, System};
use tokio::sync::{Mutex, Notify, RwLock}; use tokio::sync::{Mutex, Notify, RwLock};
pub type SharedSystem = Arc<Mutex<System>>; pub type SharedSystem = Arc<Mutex<System>>;
pub type SharedComponents = Arc<Mutex<Components>>;
pub type SharedDisks = Arc<Mutex<Disks>>;
pub type SharedNetworks = Arc<Mutex<Networks>>;
#[derive(Clone)] #[derive(Clone)]
pub struct AppState { pub struct AppState {
@ -19,4 +22,30 @@ pub struct AppState {
pub client_count: Arc<AtomicUsize>, pub client_count: Arc<AtomicUsize>,
pub wake_sampler: Arc<Notify>, pub wake_sampler: Arc<Notify>,
pub auth_token: Option<String>, pub auth_token: Option<String>,
// 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())
}
}
} }

View File

@ -9,14 +9,14 @@ pub struct ProcessInfo {
pub pid: u32, pub pid: u32,
pub name: String, pub name: String,
pub cpu_usage: f32, pub cpu_usage: f32,
pub mem_bytes: u64, pub mem_bytes: u64
} }
#[derive(Debug, Serialize, Clone)] #[derive(Debug, Serialize, Clone)]
pub struct DiskInfo { pub struct DiskInfo {
pub name: String, pub name: String,
pub total: u64, pub total: u64,
pub available: u64, pub available: u64
} }
#[derive(Debug, Serialize, Clone)] #[derive(Debug, Serialize, Clone)]
@ -24,7 +24,7 @@ pub struct NetworkInfo {
pub name: String, pub name: String,
// cumulative totals since the agent started (client should diff to get rates) // cumulative totals since the agent started (client should diff to get rates)
pub received: u64, pub received: u64,
pub transmitted: u64, pub transmitted: u64
} }
#[derive(Serialize)] #[derive(Serialize)]
@ -41,5 +41,5 @@ pub struct Metrics {
pub disks: Vec<DiskInfo>, pub disks: Vec<DiskInfo>,
pub networks: Vec<NetworkInfo>, pub networks: Vec<NetworkInfo>,
pub top_processes: Vec<ProcessInfo>, pub top_processes: Vec<ProcessInfo>,
pub gpus: Option<Vec<GpuMetrics>>, // new pub gpus: Option<Vec<GpuMetrics>>
} }