1 Commits
Author SHA1 Message Date
Andy Dixon 4b11d955cb Letter keys instead of function keys
h help, / search, s sort, t tree, z all, c command line, i swap in,
o swap out, F flush, k signal, q quit.
2026-10-07 12:32:27 +01:00
4 changed files with 34 additions and 45 deletions
+7
View File
@@ -1,5 +1,12 @@
# Changelog
## 1.0.1 — 2026-10-07
### Changed
- Plain letter keys instead of function keys: `h` help, `/` search, `s` sort,
`t` tree, `z` all, `c` command line, `i` swap in, `o` swap out, `F` flush,
`k` signal, `q` quit.
## 1.0.0 — 2026-10-07
First release.
+4 -4
View File
@@ -11,10 +11,10 @@ zram figures appear when present. Each process shows its SWAP, proportional
SWPPSS, DELTA since the last refresh, RSS and how much of it is in swap;
`Enter` breaks a process down by mapping (heap, stacks, libraries, files).
Actions, each confirmed first: swap a process back in (`F7`), page a process
out (`F8`, `process_madvise`, Linux 5.10+), flush every swap device with
swapoff+swapon (`F2`), send a signal (`F9`). Search (`/`), sort (`F6`), tree
view (`F5`), zero-swap toggle (`z`), full command line (`c`). Settings
Actions, each confirmed first: swap a process back in (`i`), page a process
out (`o`, `process_madvise`, Linux 5.10+), flush every swap device with
swapoff+swapon (`F`), send a signal (`k`). Search (`/`), sort (`s`), tree
view (`t`), zero-swap toggle (`z`), full command line (`c`), help (`h`). Settings
persist in `~/.config/swaptop/config`. See `man swaptop`.
Run it as root to see every process and use the actions:
+13 -13
View File
@@ -1,5 +1,5 @@
.\" Manual page for swaptop. View with: man -l docs/swaptop.1
.TH SWAPTOP 1 "October 2026" "swaptop 1.0.0" "User Commands"
.TH SWAPTOP 1 "October 2026" "swaptop 1.0.1" "User Commands"
.SH NAME
swaptop \- htop\-style view of which processes are using swap, and how
.SH SYNOPSIS
@@ -26,8 +26,8 @@ and
.IR /proc/PID/smaps .
Nothing is written anywhere except the configuration file (see
.BR FILES ).
Actions that change memory placement are available from the function keys
and always ask for confirmation first.
Actions that change memory placement are bound to single letter keys and
always ask for confirmation first.
.PP
Running as root (\fBsudo swaptop\fR) is recommended: without it the kernel
hides the detailed figures and memory of other users' processes (see
@@ -147,19 +147,19 @@ Move the selection.
.B Enter
Per\-mapping breakdown of the selected process. \fBEsc\fR or \fBq\fR returns.
.TP
.B F1, h, ?
.B h, ?
Help.
.TP
.B F3, /
.B /
Incremental search over command line, name, user and PID. \fBEnter\fR keeps
the filter, \fBEsc\fR clears it.
.TP
.B F5, t
.B t
Toggle tree view. In tree view a process is shown when it or any of its
descendants matches, so parents stay for context; processes without swap are
dimmed.
.TP
.B F6, s
.B s
Cycle the sort column.
.TP
.B z
@@ -168,12 +168,12 @@ Toggle between processes with swap only (the default) and all processes.
.B c
Toggle between the command name and the full command line.
.TP
.B F10, q
.B q
Quit, saving the current settings.
.SH ACTIONS
Each action asks for confirmation before doing anything.
.TP
.B F7, i \(em swap in
.B i \(em swap in
Faults every swapped page of the selected process back into RAM by reading
it through
.IR /proc/PID/mem .
@@ -183,7 +183,7 @@ so nothing else is touched. Afterwards the process runs at full speed again.
Its swap slots usually remain allocated as swap cache (yellow in the bar)
until the kernel needs them or the device is flushed.
.TP
.B F8, o \(em swap out
.B o \(em swap out
Asks the kernel to reclaim the selected process's resident memory now, with
.BR process_madvise (2)
.BR MADV_PAGEOUT
@@ -191,7 +191,7 @@ Asks the kernel to reclaim the selected process's resident memory now, with
pages are simply dropped. The process keeps running and faults its memory
back as it touches it. Useful for parking an idle but large process.
.TP
.B F2, F \(em flush
.B F \(em flush
Empties every swap device completely by calling
.BR swapoff (2)
and then
@@ -204,7 +204,7 @@ priority is kept; other
.BR swapon (8)
options such as discard are not reapplied. Needs root.
.TP
.B F9, k \(em signal
.B k \(em signal
Opens a picker of common signals (TERM, KILL, INT, HUP, QUIT, USR1, USR2,
STOP, CONT) to send to the selected process.
.SH PERMISSIONS
@@ -258,7 +258,7 @@ option.
.SH NOTES
Swap cache explains most apparent mismatches: a process can show 0 in
\fBSWAP\fR while the device's usage barely drops, because the slots are kept
until needed. Flushing (\fBF2\fR) is the only way to release them on demand.
until needed. Flushing (\fBF\fR) is the only way to release them on demand.
.PP
The \fBSWAP\fR column of processes sharing memory (database workers, for
example) double counts that memory, by design, so that each process shows
+10 -28
View File
@@ -97,7 +97,7 @@ func sortIndex(name string) int {
func main() {
cfg := loadConfig()
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: swaptop [-d SECONDS] [-s COLUMN] [-t] [-z] [--version]\n\nhtop-style view of swap usage per process. Press F1 inside for keys.\n\n")
fmt.Fprintf(os.Stderr, "Usage: swaptop [-d SECONDS] [-s COLUMN] [-t] [-z] [--version]\n\nhtop-style view of swap usage per process. Press h inside for keys.\n\n")
flag.PrintDefaults()
}
delay := flag.Float64("d", cfg.Refresh, "refresh interval in seconds")
@@ -399,24 +399,6 @@ func (a *app) key(ev *tcell.EventKey) bool {
}
case tcell.KeyEscape:
a.view = viewList
case tcell.KeyF1:
a.view = viewHelp
case tcell.KeyF2:
a.askFlush()
case tcell.KeyF3:
a.searching = true
case tcell.KeyF5:
a.toggleTree()
case tcell.KeyF6:
a.cycleSort()
case tcell.KeyF7:
a.askSwapIn()
case tcell.KeyF8:
a.askSwapOut()
case tcell.KeyF9:
a.askKill()
case tcell.KeyF10:
return true
case tcell.KeyRune:
switch ev.Rune() {
case 'q':
@@ -515,7 +497,7 @@ func (a *app) askSwapIn() {
}
})
if err == nil {
set(fmt.Sprintf("Swapped in %s (%d) in %s. Its slots stay 'cached' until the kernel drops them or you flush (F2).",
set(fmt.Sprintf("Swapped in %s (%d) in %s. Its slots stay 'cached' until the kernel drops them or you flush (F).",
p.Comm, p.PID, time.Since(start).Round(100*time.Millisecond)))
}
return err
@@ -861,14 +843,14 @@ func (a *app) drawHelp() {
" magenta not owned by any process: tmpfs/shm files, or slots whose owner we cannot read",
"",
"Actions (each asks for confirmation)",
" F7 i swap in: fault every swapped page of the process back into RAM (reads /proc/PID/mem)",
" F8 o swap out: ask the kernel to reclaim the process's pages now (process_madvise MADV_PAGEOUT, Linux 5.10+)",
" F2 F flush: swapoff + swapon every device, emptying swap completely (root, needs enough free RAM)",
" F9 k send a signal (picker)",
" i swap in: fault every swapped page of the process back into RAM (reads /proc/PID/mem)",
" o swap out: ask the kernel to reclaim the process's pages now (process_madvise MADV_PAGEOUT, Linux 5.10+)",
" F flush: swapoff + swapon every device, emptying swap completely (root, needs enough free RAM)",
" k send a signal (picker)",
"",
"Keys",
" Up/Down PgUp/PgDn Home/End move Enter per-mapping breakdown of the selected process",
" F3 / search F6 s sort F5 t tree view z show/hide processes with no swap c full command line F10 q quit",
" / search s sort t tree view z show/hide processes with no swap c full command line h help q quit / back",
"",
"Settings (sort, tree, z, c, refresh) are saved to " + configPath() + " on exit.",
"Non-root: SWPPSS and actions only work for your own processes (kernel.yama.ptrace_scope).",
@@ -891,13 +873,13 @@ func (a *app) drawBottom(w, h int) {
a.put(0, y, "Search: "+a.filter+"_", stWarn)
return
}
keys := [][2]string{{"F1", "Help"}, {"F2", "Flush"}, {"F3", "Search"}, {"F5", "Tree"}, {"F6", "SortBy"}, {"F7", "SwapIn"}, {"F8", "SwapOut"}, {"F9", "Kill"}, {"F10", "Quit"}}
keys := [][2]string{{"h", "Help"}, {"/", "Search"}, {"s", "Sort"}, {"t", "Tree"}, {"z", "All"}, {"c", "Cmd"}, {"i", "SwapIn"}, {"o", "SwapOut"}, {"F", "Flush"}, {"k", "Kill"}, {"q", "Quit"}}
if a.view != viewList {
keys = [][2]string{{"Esc", "Back"}, {"F7", "SwapIn"}, {"F8", "SwapOut"}, {"F9", "Kill"}, {"F10", "Quit"}}
keys = [][2]string{{"q", "Back"}, {"i", "SwapIn"}, {"o", "SwapOut"}, {"k", "Kill"}}
}
x := 0
for _, k := range keys {
x = a.put(x, y, fmt.Sprintf("%3s", k[0]), stKey)
x = a.put(x, y, " "+k[0], stKey)
x = a.put(x, y, fmt.Sprintf("%-7s", k[1]), stFn)
}
}