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.
100 lines
3.1 KiB
Rust
100 lines
3.1 KiB
Rust
//! socktop agent entrypoint: sets up sysinfo handles, launches a sampler,
|
|
//! and serves a WebSocket endpoint at /ws.
|
|
|
|
mod gpu;
|
|
mod metrics;
|
|
mod sampler;
|
|
mod state;
|
|
mod types;
|
|
mod ws;
|
|
|
|
use axum::{routing::get, Router};
|
|
use std::net::SocketAddr;
|
|
|
|
use crate::sampler::{spawn_disks_sampler, spawn_process_sampler, spawn_sampler};
|
|
use state::AppState;
|
|
use ws::ws_handler;
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
tracing_subscriber::fmt::init();
|
|
|
|
let state = AppState::new();
|
|
|
|
// Start background sampler (adjust cadence as needed)
|
|
// 500ms fast metrics
|
|
let _h_fast = spawn_sampler(state.clone(), std::time::Duration::from_millis(500));
|
|
// 2s processes (top 50)
|
|
let _h_procs = spawn_process_sampler(state.clone(), std::time::Duration::from_secs(2), 50);
|
|
// 5s disks
|
|
let _h_disks = spawn_disks_sampler(state.clone(), std::time::Duration::from_secs(5));
|
|
|
|
// Web app
|
|
let port = resolve_port();
|
|
let app = Router::new()
|
|
.route("/ws", get(ws_handler))
|
|
.with_state(state);
|
|
|
|
let addr = SocketAddr::from(([0, 0, 0, 0], port));
|
|
|
|
//output to console
|
|
println!("Remote agent running at http://{addr}");
|
|
println!("WebSocket endpoint: ws://{addr}/ws");
|
|
|
|
//trace logging
|
|
tracing::info!("Remote agent running at http://{} (ws at /ws)", addr);
|
|
tracing::info!("WebSocket endpoint: ws://{}/ws", addr);
|
|
|
|
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
|
axum::serve(listener, app).await.unwrap();
|
|
}
|
|
|
|
// Resolve the listening port from CLI args/env with a 3000 default.
|
|
// Supports: --port <PORT>, -p <PORT>, a bare numeric positional arg, or SOCKTOP_PORT.
|
|
fn resolve_port() -> u16 {
|
|
const DEFAULT: u16 = 3000;
|
|
|
|
// Env takes precedence over positional, but is overridden by explicit flags if present.
|
|
if let Ok(s) = std::env::var("SOCKTOP_PORT") {
|
|
if let Ok(p) = s.parse::<u16>() {
|
|
if p != 0 {
|
|
return p;
|
|
}
|
|
}
|
|
eprintln!("Warning: invalid SOCKTOP_PORT='{s}'; using default {DEFAULT}");
|
|
}
|
|
|
|
let mut args = std::env::args().skip(1);
|
|
while let Some(arg) = args.next() {
|
|
match arg.as_str() {
|
|
"--port" | "-p" => {
|
|
if let Some(v) = args.next() {
|
|
match v.parse::<u16>() {
|
|
Ok(p) if p != 0 => return p,
|
|
_ => {
|
|
eprintln!("Invalid port '{v}'; using default {DEFAULT}");
|
|
return DEFAULT;
|
|
}
|
|
}
|
|
} else {
|
|
eprintln!("Missing value for {arg} ; using default {DEFAULT}");
|
|
return DEFAULT;
|
|
}
|
|
}
|
|
"--help" | "-h" => {
|
|
println!("Usage: socktop_agent [--port <PORT>] [PORT]\n SOCKTOP_PORT=<PORT> socktop_agent");
|
|
std::process::exit(0);
|
|
}
|
|
s => {
|
|
if let Ok(p) = s.parse::<u16>() {
|
|
if p != 0 {
|
|
return p;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DEFAULT
|
|
}
|