Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b11d955cb |
@@ -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.
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user