Files
socktop/socktop_agent/src/ws.rs
jason 0859f50897 multiple feature and performance improvements (see description)
Here are concise release notes you can paste into your GitHub release.

Release notes — 2025-08-12

Highlights

Agent back to near-zero CPU when idle (request-driven, no background samplers).
Accurate per-process CPU% via /proc deltas; only top-level processes (threads hidden).
TUI: processes pane gets scrollbar, click-to-sort (CPU% or Mem) with indicator, stable total count.
Network panes made taller; disks slightly reduced.
README revamped: rustup prereqs, crates.io install, update/systemd instructions.
Clippy cleanups across agent and client.
Agent

Reverted precompressed caches and background samplers; WebSocket path is request-driven again.
Ensured on-demand gzip for larger replies; no per-request overhead when small.
Processes: switched to refresh_processes_specifics with ProcessRefreshKind::everything().without_tasks() to exclude threads.
Per-process CPU% now computed from /proc jiffies deltas using a small ProcCpuTracker (fixes “always 0%”/scaling issues).
Optional metrics and light caching:
CPU temp and GPU metrics gated by env (SOCKTOP_AGENT_TEMP=0, SOCKTOP_AGENT_GPU=0).
Tiny TTL caches via once_cell to avoid rescanning sensors every tick.
Dependencies: added once_cell = "1.19".
No API changes to WS endpoints.
Client (TUI)

Processes pane:
Scrollbar (mouse wheel, drag; keyboard arrows/PageUp/PageDown/Home/End).
Click header to sort by CPU% or Mem; dot indicator on active column.
Preserves process_count across fast metrics updates to avoid flicker.
UI/theme:
Shared scrollbar colors moved to ui/theme.rs; both CPU and Processes reuse them.
Cached pane rect to fix input handling; removed unused vars.
Layout: network download/upload get more vertical space; disks shrink slightly.
Clippy fixes: derive Default for ProcSortBy; style/import cleanups.
Docs

README: added rustup install steps (with proper shell reload), install via cargo install socktop and cargo install socktop_agent, and a clear Updating section (systemd service steps included).
Features list updated; roadmap marks independent cadences as done.
Upgrade notes

Agent: cargo install socktop_agent --force, then restart your systemd service; if unit changed, systemctl daemon-reload.
TUI: cargo install socktop --force.
Optional envs to trim overhead: SOCKTOP_AGENT_GPU=0, SOCKTOP_AGENT_TEMP=0.
No config or API breaking changes.
2025-08-12 15:52:46 -07:00

70 lines
2.4 KiB
Rust

//! WebSocket upgrade and per-connection handler (request-driven).
use axum::{
extract::ws::{Message, WebSocket},
extract::{Query, State, WebSocketUpgrade},
response::Response,
};
use flate2::{write::GzEncoder, Compression};
use futures_util::StreamExt;
use std::collections::HashMap;
use std::io::Write;
use crate::metrics::{collect_disks, collect_fast_metrics, collect_processes_top_k};
use crate::state::AppState;
pub async fn ws_handler(
ws: WebSocketUpgrade,
State(state): State<AppState>,
Query(q): Query<HashMap<String, String>>,
) -> Response {
// optional auth
if let Some(expected) = state.auth_token.as_ref() {
if q.get("token") != Some(expected) {
return ws.on_upgrade(|socket| async move {
let _ = socket.close().await;
});
}
}
ws.on_upgrade(move |socket| handle_socket(socket, state))
}
async fn handle_socket(mut socket: WebSocket, state: AppState) {
state
.client_count
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
while let Some(Ok(msg)) = socket.next().await {
match msg {
Message::Text(ref text) if text == "get_metrics" => {
let m = collect_fast_metrics(&state).await;
let _ = send_json(&mut socket, &m).await;
}
Message::Text(ref text) if text == "get_disks" => {
let d = collect_disks(&state).await;
let _ = send_json(&mut socket, &d).await;
}
Message::Text(ref text) if text == "get_processes" => {
let p = collect_processes_top_k(&state, 50).await;
let _ = send_json(&mut socket, &p).await;
}
Message::Close(_) => break,
_ => {}
}
}
state
.client_count
.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
}
// Small, cheap gzip for larger payloads; send text for small.
async fn send_json<T: serde::Serialize>(ws: &mut WebSocket, value: &T) -> Result<(), axum::Error> {
let json = serde_json::to_string(value).expect("serialize");
if json.len() <= 768 {
return ws.send(Message::Text(json)).await;
}
let mut enc = GzEncoder::new(Vec::new(), Compression::fast());
enc.write_all(json.as_bytes()).ok();
let bin = enc.finish().unwrap_or_else(|_| json.into_bytes());
ws.send(Message::Binary(bin)).await
}