A fast, one-line-per-user “who-like” report that unifies session signals (TTY and non‑TTY) with per-user CPU%, Memory%, and Tasks, plus optional socket-to-host mapping. Designed for RHEL, SLES, and Ubuntu environments—especially HPC/login nodes and shared multi-user servers.
- Unified session view: combines
- TTY sessions (
who, SSH interactive) - Non‑TTY activity (SSH non‑tty, SFTP, VSCode Remote, Jupyter, RStudio Server, FTP, Samba, NoMachine)
- tmux (attached/detached), mosh
- TTY sessions (
- Resource metrics per user:
CPU%— sum of%CPUacross processes (multi-core aware, can exceed100%)MEM%— sum of RSS vsMemTotal(fast; note shared pages caveat)TASKS— process count
- Host mapping (optional) via
ss -tnpon common service ports (e.g.,22,8787,445, etc.) - Smart WHEN selection:
- Uses earliest/latest session time by default
- Falls back to earliest/latest “interesting” process start for users with processes but no session signals
- Adds
proc(<program>)hint toWHATon process-based fallback
- Robust & efficient:
- Single
pssnapshot (user pid ppid etimes %CPU RSS cmd) - Single
sspass - Hardened temp handling, numeric/input validation, and minimal process spawning
- Single
- Clean output:
- Columns:
USER,TTY,WHEN,HOST,CPU%,MEM%,TASKS,WHAT - Any missing field prints as
-
- Columns:
Stock who primarily reports interactive sessions (terminal lines). On shared servers and HPC login nodes, many users run workloads through non-interactive channels (SFTP, VSCode Remote, Jupyter, Slurm‑launched shells, etc.). This tool surfaces all active users and associates signals, giving operators a clear, sortable snapshot.
- Bash 4+ (associative arrays)
ps(procps‑ng)ss(iproute2) — optional, for host mappingwho,date,awk,stat,column(optional for pretty printing),tmux(optional)- Linux (tested on RHEL, SLES, Ubuntu)
git clone https://github.com/ncsa/whox.git
cd whox
sudo install -m 0755 scripts/whox.sh /usr/local/bin/whoxOr run in place:
chmod +x whox.sh
./whox.sh# Default: latest per-user time, columnized output
whox.sh
# Earliest per-user time
whox.sh --sort earliest
# Raw tab output for pipelines (no column formatting, no header)
whox.sh --no-column --no-header
# Treat UID < 500 as system accounts (default is 1000)
whox.sh --sys-uid-max 500- USER — login name
- TTY —
pts/NorttyNif interactive;mixedif both TTY/non‑TTY;nottyif only non‑TTY;-otherwise - WHEN — per-user earliest/latest time; if no session signals, uses earliest/latest process start
- HOST — single host if resolvable, or
multiple(N)if several;-if unknown - CPU% — sum of
%CPU(multi-core aware) - MEM% — sum of RSS /
MemTotal - TASKS — number of processes per user
- WHAT — aggregated signals, e.g.
tty(1),ssh(2),vscode(5),jupyter(1),proc(salloc)
USER TTY WHEN HOST CPU% MEM% TASKS WHAT
user1 pts/21 2025-12-05 10:24 192.0.2.2 0.00 0.04 4 tty(1),ssh(1)
user2 notty 2025-12-05 10:23 - 6.10 4.68 22 ssh-non-tty(1),vscode(13)
user3 - 2025-12-05 00:53 - 0.00 0.04 5 proc(salloc)
user4 - 2025-12-05 10:42 - 0.10 0.49 4 proc(python)
user5 pts/34 2025-12-05 10:44 192.0.2.6 2.40 1.15 562 tty(1),ssh(1)
-
--sort earliest|latest
Controls whetherWHEN(and sorting key) uses each user’s earliest or latest signal (or process fallback). -
--sys-uid-max N
Consider UID< Nas “system accounts.” If a user has no session signals and is a system account, their row is suppressed. Default1000. -
--no-header
Don’t print the header line. -
--no-column
Don’t format withcolumn -t; useful for machine parsing.
- Single pass
pssnapshot with PPID & ETIMES—no repeatedpsscans. - No per-PID
pscalls except rare fallback whenetimesis absent. - One
ss -tnppass to mapPID → remote hostfor common service ports. LC_ALL=Cfor faster text processing, andumask 077+ privateTMPDIRfor secure temporary files.
- Least privilege: runs fine as non‑root. Root improves tmux cross-user visibility and socket mapping; prefer constrained sudo rules if needed.
- Input validation: numeric checks for PIDs, whitelist for usernames,
--passed to commands to avoid option injection. - Temp isolation:
mktemp -dwithtrapcleanup, restricted permissions. - Systemd (optional): set
NoNewPrivileges=true,PrivateTmp=true, and run under a dedicated user for scheduled reports.
- CPU% is summed from per-process
%CPU; on multi-core hosts, totals can exceed100%. - MEM% uses RSS; shared pages may be double-counted (as with most quick summaries).
- Host mapping is limited to known service ports; customize as needed.
- Threads are not counted (
TASKS= processes). A future flag can add thread counts.
- Blank fields: the script prints
-for any unavailable field; if you see-forHOST,ssmay not have captured relevant sockets or ports. - Unbound variable errors: the script uses
set -u; ensure variable names are correct if you customize it. - Syntax check: run
bash -n whox.shafter edits. - ShellCheck:
shellcheck -x whox.shfor static analysis.
--jsonoutput mode for ingestion by monitoring/SEIM tools--threadsto report per-user thread count--cgroupmode (RHEL 9.x) to readmemory.current,pids.current, and cgroup CPU limits fromuser.slice- Configurable regexes:
--proc-hint-includeand--proc-hint-exclude
Issues and PRs welcome! Please:
- Keep changes portable across RHEL/SLES/Ubuntu.
- Add
bash -nandshellcheckto your PR checks. - Include a short before/after performance note for changes affecting runtime.
This project is licensed under the MIT License. See the LICENSE file for details.
This role was created by NCSA's ICI team. https://www.ncsa.illinois.edu/