pmemstat 4.0.0__tar.gz → 4.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pmemstat might be problematic. Click here for more details.

Files changed (29) hide show
  1. pmemstat-4.0.2/PKG-INFO +375 -0
  2. pmemstat-4.0.2/README.md +349 -0
  3. {pmemstat-4.0.0 → pmemstat-4.0.2}/pmemstat/main.py +349 -85
  4. {pmemstat-4.0.0 → pmemstat-4.0.2}/pyproject.toml +12 -6
  5. pmemstat-4.0.2/runner +123 -0
  6. {pmemstat-4.0.0 → pmemstat-4.0.2}/tests/__init__.py +11 -1
  7. {pmemstat-4.0.0 → pmemstat-4.0.2}/tests/test_main.py +285 -19
  8. pmemstat-4.0.0/PKG-INFO +0 -452
  9. pmemstat-4.0.0/README.md +0 -429
  10. pmemstat-4.0.0/runner +0 -55
  11. {pmemstat-4.0.0 → pmemstat-4.0.2}/.github/workflows/ci.yml +0 -0
  12. {pmemstat-4.0.0 → pmemstat-4.0.2}/.gitignore +0 -0
  13. {pmemstat-4.0.0 → pmemstat-4.0.2}/LICENSE.txt +0 -0
  14. {pmemstat-4.0.0 → pmemstat-4.0.2}/images/pmemstat.png +0 -0
  15. {pmemstat-4.0.0 → pmemstat-4.0.2}/images/scroll-pos.png +0 -0
  16. {pmemstat-4.0.0 → pmemstat-4.0.2}/memtest.c +0 -0
  17. {pmemstat-4.0.0 → pmemstat-4.0.2}/pmemstat/CGroup.py +0 -0
  18. {pmemstat-4.0.0 → pmemstat-4.0.2}/pmemstat/CpuSmooth.py +0 -0
  19. {pmemstat-4.0.0 → pmemstat-4.0.2}/pmemstat/History.py +0 -0
  20. {pmemstat-4.0.0 → pmemstat-4.0.2}/pmemstat/KillThem.py +0 -0
  21. {pmemstat-4.0.0 → pmemstat-4.0.2}/pmemstat/Pressure.py +0 -0
  22. {pmemstat-4.0.0 → pmemstat-4.0.2}/pmemstat/__init__.py +0 -0
  23. {pmemstat-4.0.0 → pmemstat-4.0.2}/pylintrc +0 -0
  24. {pmemstat-4.0.0 → pmemstat-4.0.2}/tests/fixtures/smaps_rollup_sample.txt +0 -0
  25. {pmemstat-4.0.0 → pmemstat-4.0.2}/tests/fixtures/smaps_sample.txt +0 -0
  26. {pmemstat-4.0.0 → pmemstat-4.0.2}/tests/test_cgroup.py +0 -0
  27. {pmemstat-4.0.0 → pmemstat-4.0.2}/tests/test_cpusmooth.py +0 -0
  28. {pmemstat-4.0.0 → pmemstat-4.0.2}/tests/test_history.py +0 -0
  29. {pmemstat-4.0.0 → pmemstat-4.0.2}/tests/test_pressure.py +0 -0
@@ -0,0 +1,375 @@
1
+ Metadata-Version: 2.4
2
+ Name: pmemstat
3
+ Version: 4.0.2
4
+ Summary: Report Proportional Memory on Linux
5
+ Keywords: proportional memory,RAM,htop,top,monitor
6
+ Author-email: Joe Defen <joedef@duck.com>
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: POSIX :: Linux
19
+ License-File: LICENSE.txt
20
+ Requires-Dist: console-window == 2.1.3
21
+ Requires-Dist: pylint == 3.3.4 ; extra == "dev"
22
+ Requires-Dist: build ; extra == "dev"
23
+ Project-URL: Bug Tracker, https://github.com/joedefen/pmemstat/issues
24
+ Project-URL: Homepage, https://github.com/joedefen/pmemstat
25
+ Provides-Extra: dev
26
+
27
+ > **Quick Start**: from the CLI (requires **Python 3.10 or later**)
28
+ > * **Install (preferred, per-user):** `pipx install pmemstat`
29
+ > * **Run:** `pmemstat` — shows only *your* processes
30
+ > * **Full view (all processes):** `pmemstat --sudo`. To avoid retyping it, add `export PMEMSTAT_ARGS=--sudo` to your shell init, then just run `pmemstat`.
31
+ > * **Upgrade:** `pipx upgrade pmemstat || pipx install pmemstat`
32
+ > * Type `?` within `pmemstat` for the help screen.
33
+
34
+ > **Note (v4.0.0 — breaking change):** `pmemstat` no longer elevates itself automatically (it did in 3.x); use `--sudo` for the full view.
35
+
36
+ > **Run `pmemstat` as root if you can. `sudo pmemstat` (or `--sudo`) is strongly recommended; an unprivileged (`-U`) run is a degraded, partial view, not a smaller-but-equivalent one:**
37
+ > 1. **You see only your own processes.** Every other user's (and most of root's) memory and CPU is missing, so totals understate real usage; the `PIDs=shown/total` count makes the gap explicit.
38
+ > 2. **System-wide header numbers degrade.** The unaccounted residual `Oth` cannot be split into its kernel (`OthK`) and user (`OthU`) parts — it silently absorbs every process you cannot read, so it is inflated and misleading.
39
+ > 3. **Growth / leak monitoring is crippled.** The cross-run, per-boot history is root-only, so an unprivileged run measures growth only from *this run's start* (no "since boot" baseline), and the `z` zap reset is unavailable.
40
+ > 4. **Kill mode reaches only your own processes.**
41
+ >
42
+ > Use `-U` only when you deliberately want a user-only view (or cannot elevate); otherwise elevate — the tool then works as designed.
43
+
44
+
45
+ # pmemstat - Proportional Memory Status
46
+
47
+ `pmemstat` shows detailed **proportional** memory use of **Linux** processes by digesting `/proc/{PID}/smaps` and `/proc/{PID}/smaps_rollup`.
48
+
49
+ Proportional memory splits the cost of shared memory among the processes sharing it (instead of counting it repeatedly, as `top` does) and excludes uninstantiated virtual memory.
50
+
51
+ Without `-o`, `pmemstat` is much faster but less detailed (it omits classes such as SysV shared memory, usually absent anyway). With `-o` it may take a few seconds to start, though loop-mode refreshes stay fast because unchanged numbers are not recomputed. In window mode, `o` toggles the detail.
52
+
53
+ Grouping rolls up the resources of multiple processes of one feature (e.g. a browser) so its total memory/CPU impact is apparent. Looping reveals growth that may be a leak, and segregating memory by type makes leaks easier to identify.
54
+
55
+ ## How `pmemstat` Compares to Other Tools
56
+ Reach for `top`/`ps` when you need a tool that is always installed, and `htop` for a friendly full-screen overview. `pmemstat` differs in its focus on **accurate, proportional memory**: rather than RES/RSS — which double-counts shared pages and ignores uninstantiated virtual memory — it digests `smaps`/`smaps_rollup` to attribute shared memory fairly and break memory down by type. That makes it slower to start (especially with `-o`) but far more truthful about who is really using RAM. The table compares it with its closest peers: `smem` (the other tool that derives **PSS** from `smaps`) and `systemd-cgtop` (whose per-cgroup view `pmemstat` mirrors, with proportional numbers).
57
+
58
+ Three capabilities are **new or substantially overhauled** recently and marked **🆕** below: **cgroup v2 grouping**, **memory-leak / growth monitoring**, and **system pressure (PSI)**. All three are ✗ for every peer, and each has its own section.
59
+
60
+ | Feature | `pmemstat` | `smem` | `systemd-cgtop` | `htop` |
61
+ |---|---|---|---|---|
62
+ | **Proportional memory (PSS)** accounting | ✓ core design | ✓ core design | ✗ (`memory.current`) | ✗ (RES, double-counts) |
63
+ | **Memory-type breakdown** (shared/swap/stack/text/data) | ✓ | ✗ (USS/PSS/RSS/Swap only) | ✗ | ✗ |
64
+ | **Proportional swap** per grouping | ✓ | ✗ (flat Swap column) | ✗ | ✗ (total swap only) |
65
+ | **Group processes** by executable/command | ✓ aggregates PIDs into one row | ✓ by command by default | ✗ | ✗ (tree view only) |
66
+ | **Group by cgroup v2** 🆕 (services, scopes, containers) | ✓ (`-g cgroup`) | ✗ | ✓ (its core design) | ✗ |
67
+ | **Memory-leak / growth monitoring** 🆕 (annotates top-N growers; only re-shows significant growth) | ✓ (`G`, `--growth-style`, growth sort) | ✗ | ✗ | ✗ |
68
+ | **zRAM-aware effective RAM** (eTot/eUsed/eAvail) | ✓ | ✗ | ✗ | ✗ |
69
+ | **System pressure (PSI)** 🆕 in header (memory/cpu/io, some/full) | ✓ (`-P`) | ✗ | ✗ | ✗ |
70
+ | **Data source** | `/proc/{PID}/smaps*` | `/proc/{PID}/smaps` | `/sys/fs/cgroup` | `/proc` + libs |
71
+ | **Interactive full-screen window** | ✓ (`console-window`/curses) | ✗ (one-shot) | ✓ | ✓ |
72
+ | **CPU usage reporting** | ✓ | ✗ (memory only) | ✓ | ✓ |
73
+ | **Kill processes** | ✓ (inline confirm) | ✗ | ✗ | ✓ |
74
+ | **Inline search-as-you-type** | ✓ (`/`) | ✗ | ✗ | ✓ (F3) |
75
+ | **Requires root for all-process detail** | ✓ (else only your own) | ✓ (else only your own) | ✗ | ✗ |
76
+
77
+ For **per-mapped-file attribution** (which shared library or mmap'd file costs the most RAM), use `smem -m` or `pmap`; `pmemstat` deliberately stays focused on per-process and per-cgroup responsibility rather than rolling PSS up by mapped file.
78
+
79
+ ## Installation Options
80
+ * `pmemstat` requires **Python 3.10 or later** and depends on `console-window` (the curses UI), pinned to an **exact version** on purpose; installing `pmemstat` pulls it automatically, so do not "upgrade" it independently.
81
+ * Root is needed to read `smaps`/`smaps_rollup` for **all** processes; without it only your own are reported. Persistent default flags go in `PMEMSTAT_ARGS` (e.g. `export PMEMSTAT_ARGS='--sudo --psi --loop 3 -s name'`), which is tokenized like a command line and prepended to the real arguments, so anything typed still takes precedence.
82
+ * `pmemstat` runs as the invoking user by default and never silently escalates. If `--sudo` cannot elevate (no terminal for a password prompt and sudo not pre-authorized, or the isolated interpreter cannot import the install), it fails with a clear message and non-zero exit rather than downgrading or hanging.
83
+ * To force user-only operation, use `--user`/`-U`; the header marks such a run with `--user` so the degraded (partial) view is obvious.
84
+
85
+ See the **Quick Start** above for the basic install; **Alternative Installation Options** below covers system-wide and non-`pipx` installs.
86
+
87
+
88
+ ## Usage
89
+ ```
90
+ usage: pmemstat [-h] [-D] [-C] [-a CPU_AVG_SECS] [-P]
91
+ [-g {exe,cmd,pid,cgroup,cgroupCharge}] [-f] [-k MIN_DELTA_KB]
92
+ [-l LOOP_SECS] [-L CMDLEN] [-t TOP_PCT] [-U] [--sudo]
93
+ [--save-history-now] [--reset-history-now] [-o]
94
+ [-u {MB,KB,human}] [-s {mem,cpu,name,growth}]
95
+ [--growth-style {off,both,growth,rate}] [--growth-top {all,10}]
96
+ [--no-growth-history] [--dont-save-growth-history] [-/ SEARCH]
97
+ [-W] [pids ...]
98
+
99
+ positional arguments:
100
+ pids list of pids/groups (none means every accessible pid)
101
+
102
+ options:
103
+ -h, --help show this help message and exit
104
+ -D, --debug debug mode (the more Ds, the higher the debug level)
105
+ -C, --no-cpu do NOT report percent CPU (only in window mode)
106
+ -a, --cpu-avg-secs CPU_AVG_SECS
107
+ CPU moving-average smoothing window in seconds [5-90,
108
+ dflt=20]
109
+ -P, --psi show PSI (system pressure in header + memPSI% column)
110
+ [dflt=off]
111
+ -g, --groupby {exe,cmd,pid,cgroup,cgroupCharge}
112
+ grouping method for presenting rows; the cgroup
113
+ variants differ only in the memory view: "cgroup"
114
+ (footprt) or "cgroupCharge" (memory.current)
115
+ -f, --fit-to-window do not overflow window [if -w]
116
+ -k, --min-delta-kb MIN_DELTA_KB
117
+ minimum delta KB to show again [dflt=100 if DB else
118
+ 1000]
119
+ -l, --loop LOOP_SECS loop interval in secs [dflt=5 if -w else 0]
120
+ -L, --cmdlen CMDLEN max shown command length [dflt=36 if not -w]
121
+ -t, --top-pct TOP_PCT
122
+ report group contributing to top pct of ptotal
123
+ [dflt=100]
124
+ -U, --user run as user (NOT as root); marks the header with a
125
+ dimmed "--user" so the degraded view is obvious
126
+ --sudo re-run self as root via sudo (or set
127
+ PMEMSTAT_ARGS=--sudo)
128
+ --save-history-now save the current stats to the history ledger and exit
129
+ (implies --sudo; ignores other options and the
130
+ environment)
131
+ --reset-history-now reset the history ledger so all growth is measured
132
+ from the current values, then exit (implies --sudo;
133
+ ignores other options and the environment)
134
+ -o, --others expand "other" into shSYSV, shOth, stack, text
135
+ -u, --units {MB,KB,human}
136
+ units of memory [dflt=MB]
137
+ -s, --sortby {mem,cpu,name,growth}
138
+ sort method for presenting rows
139
+ --growth-style {off,both,growth,rate}
140
+ leak/growth style: off|both|growth|rate [dflt=off]
141
+ --growth-top {all,10}
142
+ annotate only the top-N growers: all|10 [dflt=all]
143
+ --no-growth-history do NOT seed growth from the cross-run ledger (fresh
144
+ baseline)
145
+ --dont-save-growth-history
146
+ do NOT write the cross-run growth ledger
147
+ -/, --search SEARCH show items with search string in name
148
+ -W, --no-window show in "curses" window [disables: -D,-t,-L]
149
+
150
+ ```
151
+ Explanation of some options and arguments:
152
+ * `-g {exe,cmd,pid,cgroup,cgroupCharge}, --groupby {...}` - select the grouping of memory stats for reporting: the executable basename (the default `exe`), the truncated command line (`cmd`, length via `-L`), one process (`pid`), or the cgroup v2 path (`cgroup`, showing the derived **footprint** total, or `cgroupCharge`, showing the kernel's `memory.current`); see "Grouping by cgroup v2" below.
153
+ * `-a CPU_AVG_SECS, --cpu-avg-secs CPU_AVG_SECS` - the CPU smoothing window in seconds, an integer from 5 to 90 (default 20), which damps brief spikes without hiding sustained load.
154
+ * `-P, --psi` - show PSI (off by default): adds a **system pressure (PSI)** table to the header *and* the per-cgroup `memPSI%` column; see "System pressure (PSI)" and "Grouping by cgroup v2" below.
155
+ * `-k MIN_DELTA_KB, --min-delta-kb MIN_DELTA_KB` - when looping, how much change in memory use is required to show the grouping in subsequent loops: a positive value means the group must **grow** by that amount (KB), a non-positive value means it need only **change** by that amount. It also gates the growth annotation (a row is annotated only if its absolute growth is at least `-k`).
156
+ * `--growth-style {off,both,growth,rate}` - the inline growth ("leak") annotation for the groups that are growing (default `off`); the `G` key cycles it in window mode. See "Memory growth (leak) detection" below.
157
+ * `--growth-top {all,10}` - annotate only the top-N growers by the displayed metric (default `all` = no cap).
158
+ * `--no-growth-history` / `--dont-save-growth-history` - opt out of (respectively) seeding growth from the cross-run ledger and writing it; `PMEMSTAT_NO_HISTORY` disables both. A non-root run (`-U`) asserts both regardless. See "Memory growth (leak) detection" below.
159
+ * `-W, --no-window` - report once to the terminal instead of the interactive curses window (with `-l` it loops there too). Window mode (the default) forces `-t 100`, `-L 100` and off `-D`; `-W` forces `-C` (no CPU).
160
+ * `pids` - the positional arguments may be pids (i.e., numbers) or the names of executables (as shown by `-gexe`)
161
+
162
+
163
+ ## Grouping by cgroup v2
164
+ With `-g cgroup` (or cycling `g` in window mode) `pmemstat` groups processes by their **cgroup v2** path (read from `/proc/<pid>/cgroup`), which corresponds to systemd services/scopes and to container sandboxes. Each row is labelled with the cgroup's leaf unit name (e.g. `foo.service`), and a trailing `+` marks a cgroup that also contains descendant cgroups listed on their own rows. If the leaf name matches none of the row's member executables — common with launcher-created app scopes (e.g. niri's `app-niri-fuzzel-*.scope`, which actually host the launched app) — the row is labelled with the dominant member executable instead; the full cgroup path stays searchable with `/`.
165
+
166
+ There are two cgroup memory views:
167
+ * `kcharge` - what the kernel charges, and what `memory.max` / the OOM killer act on; the number `systemd-cgtop`/`docker stats` show.
168
+ * `footprt` - what the cgroup really needs to keep resident: a stable working-set proxy that does not balloon with page cache and still accounts for swapped-out pages.
169
+
170
+ Because `pmemstat` computes **proportional** memory (PSS), `ptotal` is *not* the number `systemd-cgtop`, `docker stats` or `podman stats` report (they use the non-proportional `memory.current`, which over-counts shared pages). The cgroup groupings therefore keep `ptotal` as a left-hand **reference** column but use the kernel charge as the row total: `-g cgroup` shows `footprt`, `-g cgroupCharge` shows `memory.current` (`kcharge`).
171
+
172
+ The kernel columns, read from `/sys/fs/cgroup/<path>/` in the `-u` units, decompose that charge, and the slices to the left of the total sum to it:
173
+ * `anon` - `memory.stat`'s `anon`: anonymous memory charged to the cgroup
174
+ * `cache` - page cache: `file` (in the `kcharge` view) or `file - inactive_file` (in the `footprt` view, where reclaimable inactive cache is removed)
175
+ * `kmem` - kernel memory: `kernel` (`kcharge`) or `kernel - slab_reclaimable` (`footprt`)
176
+ * `swap` - the kernel swap charge (`footprt` view only; swap is not part of `memory.current`). Distinct from `pswap` (the smaps-proportional swap shown in other modes)
177
+ * `oK` - the remainder, `total - (anon + cache + kmem [+ swap])`; normally just `sock`, it guarantees the row adds up
178
+ * `footprt` / `kcharge` - the row **total** for the active grouping (`cgroup` -> `footprt`; `cgroupCharge` -> `kcharge`):
179
+ * `footprt` (default) - the derived **footprint**: `memory.current - inactive_file - slab_reclaimable + swap`; also the growth metric
180
+ * `kcharge` - `memory.current`, the kernel's own charge
181
+ * `memPSI%` - `memory.pressure`'s `some avg10`: memory-pressure stall time as a percentage; shown only with `-P` (PSI), beside `cpu%`
182
+
183
+ These columns appear only in the cgroup groupings (`-g cgroup` / `-g cgroupCharge`), and are omitted entirely when the kernel exposes no cgroup v2 data (e.g. a cgroup v1 host).
184
+
185
+ cgroup v2 accounting is **hierarchical**: each cgroup's figures include its descendants, so a row with separately listed descendants (marked `+`) has their figures **subtracted** and shows only its own share; rows therefore add up to `TOTALS`. The unified root (`/`, labelled `(root)`) is everyone's ancestor, so a process living directly in it — as inside a container, where it may be the only cgroup visible — makes the `(root)+` row count every other row as a descendant. `memPSI%` is not totalled (`n/a` in `TOTALS` and `---- OTHERS ----`).
186
+
187
+ Reading `/proc/<pid>/cgroup` and `/sys/fs/cgroup` needs no root, so the grouping works unprivileged too (the PSS columns still reflect only the processes you can read).
188
+
189
+ ## System pressure (PSI)
190
+ With `-P` (or the `p` key in window mode) pmemstat adds a small header table reporting Linux **Pressure Stall Information**, read from `/proc/pressure/{memory,cpu,io}`: the fraction of time work stalled waiting for a resource, so higher means more of a bottleneck.
191
+
192
+ ```
193
+ SOME 10s 60s 300s FULL 10s 60s 300s
194
+ memPSI% 0.42 0.10 0.02 0.00 0.00 0.00
195
+ cpuPSI% 0.00 0.03 0.00 - - -
196
+ ioPSI% 0.15 0.60 1.04 0.10 0.49 0.96
197
+ ```
198
+ Each cell is the kernel's `avg10`/`avg60`/`avg300` stall-time percentage for its window. `SOME` means at least one task was stalled; `FULL` means **all** non-idle tasks were stalled, and is normally available only for `memory` and `io` (cells a resource does not provide show `-`). A resource whose file is absent (a kernel built with `CONFIG_PSI=n`) is omitted; if no resource is readable, the table is not shown.
199
+
200
+ This is the **system-wide** view, distinct from the per-cgroup `memPSI%` column described under "Grouping by cgroup v2".
201
+
202
+ ## Example Output
203
+ ```
204
+ 20:49:12 Tot=7.6G Used=6.2G Avail=1.4G OthK=512M OthU=3.6G Sh+Tmp=477M PIDs=174
205
+ 2.4%/ker MajF/s=2 zRAM=813M CR=4.3 eTot:16.8G eUsed:8.8G eAvail:8.0G
206
+ cpu% pswap other data ptotal key/info (exe by mem)
207
+ 60.8 2,535 593 3,988 7,116 T 174x --TOTALS in MB --
208
+ ───────────────────────────────────────────────────────────────────────────────
209
+ 5.9 1,366 90 2,110 3,567 24x browser
210
+ 16.6 89 117 822 1,028 9x code
211
+ 5.9 270 32 291 593 3.5G 2h3m 1x firefox
212
+ 1.6 95 68 335 497 6x exe
213
+ 2.4 150 66 94 310 6x brave
214
+ 16.0 52 73 152 277 3x vivaldi
215
+ 0.1 39 19 13 70 2x konsole
216
+ ```
217
+
218
+ This is a **root** (`--sudo`) run; a non-root run shows a single `Oth` (see below).
219
+
220
+ In the default window loop we see
221
+ * a **leader line** with the current time, and `Tot`, `Avail`, `Used` (= Tot-Avail) and `Sh+Tmp` from `/proc/meminfo`, plus:
222
+ * the unaccounted-for memory `Oth = Tot - Avail - Tmp - ptotal` — whatever is in neither `ptotal` (the sum of proportional process totals) nor `Sh+Tmp`: kernel, reserve, drivers, imprecision, etc., and when **not** root every process whose `smaps` you cannot read.
223
+ * As **root** every process is accounted for, so `Oth` splits into **OthK** = `SUnreclaim + KernelStack + PageTables` (kernel-owned) and **OthU** = `Oth - OthK` (the userland remainder).
224
+ * When **not root** other users' memory is folded into the residual, so a single **Oth** is shown; `PIDs=shown/total` on the same line is the clue that `ptotal` is incomplete.
225
+ * BTRFS, ZFS, zRAM or unattached SysV shared memory can make `Oth` significant (and MemAvailable understated); start with `sudo slabtop -sc` and feature tools (e.g. `zpool list`).
226
+ * contributing PIDs vs the total excluding kernel threads
227
+ * an optional **PSI block** (with `-P`), as described in "System pressure (PSI)" above
228
+ * a **second leader line for zRAM** (only if zRAM is active):
229
+ * percent CPU consumed by kernel processes (not normalized: 4 CPUs can total 400%) — with zRAM mostly the swap process
230
+ * **MajF/s** - major page faults per second; 100+/s means heavy swapping and memory pressure, 1000+/s is extreme
231
+ * **zRAM** - "Used" RAM storing zRAM data
232
+ * **eTot** / **eUsed** / **eAvail** - effective Total / Used / Available RAM (more accurate as **zRAM** grows)
233
+ * a **header line with the reported fields**:
234
+ * **cpu** - percent CPU (again not normalized to 100%)
235
+ * **pswap** - proportional swap use (per smaps_rollup)
236
+ * **other** - partly sums these categories (shown by the key, `o`):
237
+ * **shSYSV** - proportional System V shared memory use (per smaps)
238
+ * **shOth** - proportional use of other shared memory (per smaps)
239
+ * **stack** - exclusive stack use per smaps
240
+ * **text** - proportional use of text memory (the read-only binary code, per smaps)
241
+ * **data** - exclusive data ("heap") use per smaps
242
+ * **ptotal** - proportional memory of all categories (sum of the columns to the left); with zRAM it includes **pswap**
243
+ * *empty* - entry type: **T** (grand total), **A** (newly added grouping) or **O** (overflow groupings below `--top-pct`)
244
+ * **growth** - inline growth annotation for top-N growers (e.g. `3.5G 2h3m`); only when growth mode is not `off`
245
+ * **key/info** - **{num}x**, the number of processes in the grouping (e.g. `24x browser`), plus the grouping key; `pid` grouping rows show `1x`, the pid and the command line.
246
+
247
+ ## Memory growth (leak) detection
248
+
249
+ With `--growth-style` (or the `G` key in window mode) `pmemstat` annotates the
250
+ groups that are growing, making slow leaks visible without scrolling history:
251
+
252
+ ```
253
+ 5.9 270 32 291 593 3.5G 2h3m 1x firefox
254
+ ```
255
+
256
+ * The annotation is `{human growth} {interval}` (e.g. `3.5G 2h3m`); `rate` adds
257
+ a per-day projection (`3.5G/d`) once the baseline spans at least a minute, and
258
+ `growth` shows just the size.
259
+ * The baseline comes from a **per-boot history ledger** (see "Updating History
260
+ from Cron / Startup") that root runs write as they run and on exit, so growth
261
+ is measured from the group's **first observation this boot** (possibly an
262
+ earlier run), not merely since this invocation; a reboot resets it. The ledger
263
+ is system-wide and hence **root-only**: a non-root run (`-U`) neither reads nor
264
+ writes it and measures from this run's start, since a root baseline compared
265
+ against a user-only view is meaningless and a user-only write would corrupt it.
266
+ Opt out with `--no-growth-history` (seed), `--dont-save-growth-history`
267
+ (write), or `PMEMSTAT_NO_HISTORY` (both).
268
+ * Within a run the baseline is **geometric and self-forgetting** (a group's
269
+ first 16 seconds never count), so startup bursts age out; growth is clamped at
270
+ zero, so shrinking is not reported.
271
+ * A row is annotated only if its growth (KB) is at least `-k` **and** it is
272
+ among the top-N growers (`--growth-top`, default `all`; `t` cycles `all`/`10`).
273
+ `-s growth` sorts by the displayed metric.
274
+ * With a cgroup grouping the metric is the grouping's total (`footprt` for
275
+ `-g cgroup`, `kcharge` for `-g cgroupCharge`) rather than proportional PSS.
276
+ * A system-wide line beneath the leader closes the identity
277
+ `ΔUsed = ΔTOTALS(ptotal) + Δ(Sh+Tmp) + ΔOth`; as **root** the residual splits
278
+ into `ΔOthK`/`ΔOthU`, so kernel/driver growth is separable from userspace and
279
+ tmpfs, while a non-root run shows a single `ΔOth`. The shared baseline interval
280
+ trails as `[1h58m]` in every growth mode.
281
+
282
+ ## Updating History from Cron / Startup
283
+ `pmemstat --save-history-now` performs one **silent** scan, writes the per-boot
284
+ history ledger and exits (`0` on success, `1` on failure with a message on
285
+ stderr). It implies `--sudo` and ignores all other options and `PMEMSTAT_ARGS`,
286
+ so it is safe from cron or a startup unit. The ledger is per invoking user
287
+ (`/tmp/pmemstat-<uid>`, or `PMEMSTAT_STATE_DIR`), so run it as the user whose
288
+ history you want; a root cron job without `SUDO_UID` updates the root ledger.
289
+ Only root runs use the ledger (see "Memory growth (leak) detection" above).
290
+
291
+ `pmemstat --reset-history-now` scans the same way but **replaces** the ledger
292
+ rather than merging: current values become the new baseline, as if the tool had
293
+ first run just after boot. It likewise implies `--sudo` and ignores other
294
+ options. Inside the window, `z` performs the same reset (only as root while the
295
+ growth annotation is on), asking for `y`/`Y` confirmation first; `ESC` or 10
296
+ seconds declines.
297
+
298
+ ## Key Legend (Window Mode)
299
+ The top line of the header is an always-visible key legend with the live search
300
+ field appended, so the available keys are in evidence without opening the help
301
+ screen:
302
+
303
+ ?:help group units sort cpu Kill pSI Growth /{regex}
304
+
305
+ `?` opens the complete list of keys plus the navigation keys. `zap` is appended
306
+ for a root run while the growth annotation is showing, since resetting the
307
+ baseline has no visible effect otherwise.
308
+
309
+ ## Help Screen (in Window Mode, Press '?')
310
+ In window mode, `?` opens the help screen: navigation keys at the top and, below
311
+ the line, the option keys with their possible values (the current choice is
312
+ highlighted). Typing an option key (e.g. `c`) advances to the next value, applied
313
+ on the next loop; the option keys also work directly on the main screen without
314
+ opening the help screen.
315
+
316
+ ## Inline Search (Window Mode)
317
+ Press `/` to activate inline search mode; the live search field is the trailing
318
+ `/{regex}` on the first header line. The display filters in real-time,
319
+ case-insensitively, matching any part of the process info. **Enter** commits the
320
+ search; **ESC** cancels and clears it.
321
+
322
+ ## Kill Mode (Window Mode)
323
+ Press `K` to enter "Kill Mode" where you can select and kill processes:
324
+ 1. Use the navigation keys to highlight a row and press **Enter**
325
+ 2. An inline confirmation prompt names the process and its PID count; press **`y`** to kill the process(es) or **ESC** to cancel
326
+ 3. A brief flash message reports the result
327
+ 4. Press `K` again to exit Kill Mode
328
+
329
+ ## Scroll Position (Window Mode)
330
+
331
+ Sometimes, the horizontal line between the header and scrollable region has a reverse video block (under the "351" in this case):
332
+ ![scroll-pos example](https://github.com/joedefen/pmemstat/blob/main/images/scroll-pos.png?raw=true)
333
+
334
+ Its position and length indicate how far through the document you are and how
335
+ much of it is visible; with no block, the document does not overflow. Press `f`
336
+ to fit the document to the screen with a "rollup" line summarizing the lines
337
+ that would not fit.
338
+
339
+ ## Quirks and Details
340
+ * **pswap** comes only from `smaps_rollup`, so it may be slightly out of sync with the `smaps` data.
341
+ * **ptotal** (from `smaps`) and **pss** (from `smaps_rollup`, usually hidden) differ more than expected, though still closely.
342
+ * After the first loop only **pss** is read, and groups with sufficient aggregate change are probed for detail; later loops are therefore faster, at some cost in accuracy.
343
+ * the "exe" value comes from `/proc/{PID}/cmdline`: the leading path is stripped, and if the result is a script interpreter (python, perl, bash, ...) whose first argument is a full path, it is shown as `{interpreter}->{basename(script)}` (e.g. `python3->pmemstat.py`).
344
+
345
+
346
+ ## Test Program and Test Suggestions
347
+ The included C program, `memtest.c`, builds with `cc memtest.c -o memtest`. It allocates about **3 MiB per loop** by default across all memory types (SysV shared memory, a memory-mapped file, the heap and a little stack), can run several times concurrently (sharing the SysV and mapped memory), and caps itself near `min(256 MiB, RAM/16)` so it will not trigger the OOM killer.
348
+
349
+ Because ~3 MiB per loop exceeds `pmemstat`'s default `-k` gate (1000 KB), the program stays visible loop to loop and — with `--growth-style` and `--growth-top all` — is annotated as a grower without relaxing `-k`. Tuning options:
350
+ * `-q` - quick: sleep 1s between loops instead of 10s
351
+ * `-s` - short: only 16 loops
352
+ * `-n LOOPS` - number of loops
353
+ * `-c KiB` - bytes added to each pool per loop (default 1024 KiB)
354
+ * `-b MiB` - total memory budget (default `min(256 MiB, RAM/16)`)
355
+
356
+ For a robustness test with processes coming and going, run several background `sleep`s of varying durations in a loop plus one in the foreground; this exercises races such as a PID vanishing from `/proc` while its `smaps` is read.
357
+
358
+ ## Alternative Installation Options
359
+ The Quick Start's per-user `pipx` install is preferred. Variants:
360
+ * **System-wide with `pipx` (root)** — makes `pmemstat` available to every account and to plain `sudo pmemstat` (the launcher lands in `/usr/local/bin`):
361
+ ```
362
+ sudo pipx install --global pmemstat
363
+ # to upgrade: sudo pipx upgrade --global pmemstat || sudo pipx install --global pmemstat
364
+ # to uninstall: sudo pipx uninstall --global pmemstat
365
+ ```
366
+ * **From PyPi as non-root (`pip`)** — you need `~/.local/bin` on your `$PATH`:
367
+ ```
368
+ python -m pip install --user pmemstat
369
+ # to uninstall: python -m pip uninstall pmemstat
370
+ ```
371
+ * **From PyPi as root (`pip`)** — installs into `/usr/local/bin` for all users; `PIP_BREAK_SYSTEM_PACKAGES=1` may be required on some distros:
372
+ ```
373
+ sudo PIP_BREAK_SYSTEM_PACKAGES=1 python3 -m pip install pmemstat
374
+ # to uninstall: sudo python -m pip uninstall pmemstat
375
+ ```