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.
This commit is contained in:
Andy Dixon
2026-10-07 12:32:27 +01:00
parent ccbbd416b7
commit 4b11d955cb
4 changed files with 34 additions and 45 deletions
+7
View File
@@ -1,5 +1,12 @@
# Changelog # 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 ## 1.0.0 — 2026-10-07
First release. 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; 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). `Enter` breaks a process down by mapping (heap, stacks, libraries, files).
Actions, each confirmed first: swap a process back in (`F7`), page a process Actions, each confirmed first: swap a process back in (`i`), page a process
out (`F8`, `process_madvise`, Linux 5.10+), flush every swap device with out (`o`, `process_madvise`, Linux 5.10+), flush every swap device with
swapoff+swapon (`F2`), send a signal (`F9`). Search (`/`), sort (`F6`), tree swapoff+swapon (`F`), send a signal (`k`). Search (`/`), sort (`s`), tree
view (`F5`), zero-swap toggle (`z`), full command line (`c`). Settings view (`t`), zero-swap toggle (`z`), full command line (`c`), help (`h`). Settings
persist in `~/.config/swaptop/config`. See `man swaptop`. persist in `~/.config/swaptop/config`. See `man swaptop`.
Run it as root to see every process and use the actions: 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 .\" 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 .SH NAME
swaptop \- htop\-style view of which processes are using swap, and how swaptop \- htop\-style view of which processes are using swap, and how
.SH SYNOPSIS .SH SYNOPSIS
@@ -26,8 +26,8 @@ and
.IR /proc/PID/smaps . .IR /proc/PID/smaps .
Nothing is written anywhere except the configuration file (see Nothing is written anywhere except the configuration file (see
.BR FILES ). .BR FILES ).
Actions that change memory placement are available from the function keys Actions that change memory placement are bound to single letter keys and
and always ask for confirmation first. always ask for confirmation first.
.PP .PP
Running as root (\fBsudo swaptop\fR) is recommended: without it the kernel Running as root (\fBsudo swaptop\fR) is recommended: without it the kernel
hides the detailed figures and memory of other users' processes (see hides the detailed figures and memory of other users' processes (see
@@ -147,19 +147,19 @@ Move the selection.
.B Enter .B Enter
Per\-mapping breakdown of the selected process. \fBEsc\fR or \fBq\fR returns. Per\-mapping breakdown of the selected process. \fBEsc\fR or \fBq\fR returns.
.TP .TP
.B F1, h, ? .B h, ?
Help. Help.
.TP .TP
.B F3, / .B /
Incremental search over command line, name, user and PID. \fBEnter\fR keeps Incremental search over command line, name, user and PID. \fBEnter\fR keeps
the filter, \fBEsc\fR clears it. the filter, \fBEsc\fR clears it.
.TP .TP
.B F5, t .B t
Toggle tree view. In tree view a process is shown when it or any of its 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 descendants matches, so parents stay for context; processes without swap are
dimmed. dimmed.
.TP .TP
.B F6, s .B s
Cycle the sort column. Cycle the sort column.
.TP .TP
.B z .B z
@@ -168,12 +168,12 @@ Toggle between processes with swap only (the default) and all processes.
.B c .B c
Toggle between the command name and the full command line. Toggle between the command name and the full command line.
.TP .TP
.B F10, q .B q
Quit, saving the current settings. Quit, saving the current settings.
.SH ACTIONS .SH ACTIONS
Each action asks for confirmation before doing anything. Each action asks for confirmation before doing anything.
.TP .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 Faults every swapped page of the selected process back into RAM by reading
it through it through
.IR /proc/PID/mem . .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) Its swap slots usually remain allocated as swap cache (yellow in the bar)
until the kernel needs them or the device is flushed. until the kernel needs them or the device is flushed.
.TP .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 Asks the kernel to reclaim the selected process's resident memory now, with
.BR process_madvise (2) .BR process_madvise (2)
.BR MADV_PAGEOUT .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 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. back as it touches it. Useful for parking an idle but large process.
.TP .TP
.B F2, F \(em flush .B F \(em flush
Empties every swap device completely by calling Empties every swap device completely by calling
.BR swapoff (2) .BR swapoff (2)
and then and then
@@ -204,7 +204,7 @@ priority is kept; other
.BR swapon (8) .BR swapon (8)
options such as discard are not reapplied. Needs root. options such as discard are not reapplied. Needs root.
.TP .TP
.B F9, k \(em signal .B k \(em signal
Opens a picker of common signals (TERM, KILL, INT, HUP, QUIT, USR1, USR2, Opens a picker of common signals (TERM, KILL, INT, HUP, QUIT, USR1, USR2,
STOP, CONT) to send to the selected process. STOP, CONT) to send to the selected process.
.SH PERMISSIONS .SH PERMISSIONS
@@ -258,7 +258,7 @@ option.
.SH NOTES .SH NOTES
Swap cache explains most apparent mismatches: a process can show 0 in 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 \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 .PP
The \fBSWAP\fR column of processes sharing memory (database workers, for The \fBSWAP\fR column of processes sharing memory (database workers, for
example) double counts that memory, by design, so that each process shows 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() { func main() {
cfg := loadConfig() cfg := loadConfig()
flag.Usage = func() { 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() flag.PrintDefaults()
} }
delay := flag.Float64("d", cfg.Refresh, "refresh interval in seconds") 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: case tcell.KeyEscape:
a.view = viewList 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: case tcell.KeyRune:
switch ev.Rune() { switch ev.Rune() {
case 'q': case 'q':
@@ -515,7 +497,7 @@ func (a *app) askSwapIn() {
} }
}) })
if err == nil { 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))) p.Comm, p.PID, time.Since(start).Round(100*time.Millisecond)))
} }
return err 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", " magenta not owned by any process: tmpfs/shm files, or slots whose owner we cannot read",
"", "",
"Actions (each asks for confirmation)", "Actions (each asks for confirmation)",
" F7 i swap in: fault every swapped page of the process back into RAM (reads /proc/PID/mem)", " 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+)", " 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)", " F flush: swapoff + swapon every device, emptying swap completely (root, needs enough free RAM)",
" F9 k send a signal (picker)", " k send a signal (picker)",
"", "",
"Keys", "Keys",
" Up/Down PgUp/PgDn Home/End move Enter per-mapping breakdown of the selected process", " 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.", "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).", "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) a.put(0, y, "Search: "+a.filter+"_", stWarn)
return 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 { 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 x := 0
for _, k := range keys { 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) x = a.put(x, y, fmt.Sprintf("%-7s", k[1]), stFn)
} }
} }