pmemstat 3.0.7__tar.gz → 4.0.4__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 (36) hide show
  1. pmemstat-4.0.4/.github/workflows/ci.yml +61 -0
  2. {pmemstat-3.0.7 → pmemstat-4.0.4}/.gitignore +4 -1
  3. pmemstat-4.0.4/PKG-INFO +374 -0
  4. pmemstat-4.0.4/README.md +348 -0
  5. pmemstat-4.0.4/memtest.c +415 -0
  6. pmemstat-4.0.4/pmemstat/CGroup.py +208 -0
  7. pmemstat-4.0.4/pmemstat/History.py +486 -0
  8. pmemstat-4.0.4/pmemstat/Pressure.py +158 -0
  9. pmemstat-4.0.4/pmemstat/UiPrefs.py +199 -0
  10. pmemstat-4.0.4/pmemstat/main.py +3634 -0
  11. pmemstat-4.0.4/pylintrc +24 -0
  12. pmemstat-4.0.4/pyproject.toml +47 -0
  13. pmemstat-4.0.4/runner +127 -0
  14. pmemstat-4.0.4/tests/__init__.py +65 -0
  15. pmemstat-4.0.4/tests/fixtures/smaps_rollup_sample.txt +23 -0
  16. pmemstat-4.0.4/tests/fixtures/smaps_sample.txt +1150 -0
  17. pmemstat-4.0.4/tests/test_cgroup.py +192 -0
  18. pmemstat-4.0.4/tests/test_cpusmooth.py +62 -0
  19. pmemstat-4.0.4/tests/test_history.py +303 -0
  20. pmemstat-4.0.4/tests/test_main.py +1847 -0
  21. pmemstat-4.0.4/tests/test_pressure.py +126 -0
  22. pmemstat-4.0.4/tests/test_uiprefs.py +156 -0
  23. pmemstat-3.0.7/PKG-INFO +0 -235
  24. pmemstat-3.0.7/README.md +0 -218
  25. pmemstat-3.0.7/TODO.md +0 -88
  26. pmemstat-3.0.7/images/help-screen.png +0 -0
  27. pmemstat-3.0.7/memtest.c +0 -215
  28. pmemstat-3.0.7/pmemstat/main.py +0 -1584
  29. pmemstat-3.0.7/pyproject.toml +0 -31
  30. pmemstat-3.0.7/runner +0 -17
  31. {pmemstat-3.0.7 → pmemstat-4.0.4}/LICENSE.txt +0 -0
  32. {pmemstat-3.0.7 → pmemstat-4.0.4}/images/pmemstat.png +0 -0
  33. {pmemstat-3.0.7 → pmemstat-4.0.4}/images/scroll-pos.png +0 -0
  34. {pmemstat-3.0.7 → pmemstat-4.0.4}/pmemstat/CpuSmooth.py +0 -0
  35. {pmemstat-3.0.7 → pmemstat-4.0.4}/pmemstat/KillThem.py +0 -0
  36. {pmemstat-3.0.7 → pmemstat-4.0.4}/pmemstat/__init__.py +0 -0
@@ -0,0 +1,61 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ tests:
11
+ name: tests (py${{ matrix.python-version }})
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: actions/setup-python@v5
20
+ with:
21
+ python-version: ${{ matrix.python-version }}
22
+ - name: Install package
23
+ run: |
24
+ python -m pip install --upgrade pip
25
+ python -m pip install .
26
+ - name: Unit tests
27
+ run: python -m unittest discover -s tests -t . -v
28
+
29
+ lint:
30
+ name: pylint
31
+ runs-on: ubuntu-latest
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+ - uses: actions/setup-python@v5
35
+ with:
36
+ python-version: "3.13"
37
+ - name: Install package and pylint
38
+ run: |
39
+ python -m pip install --upgrade pip
40
+ # Pin pylint so a new release cannot unexpectedly break the required gate.
41
+ python -m pip install . pylint==3.3.4
42
+ - name: Lint
43
+ run: python -m pylint pmemstat
44
+
45
+ build:
46
+ name: build wheel
47
+ runs-on: ubuntu-latest
48
+ steps:
49
+ - uses: actions/checkout@v4
50
+ - uses: actions/setup-python@v5
51
+ with:
52
+ python-version: "3.13"
53
+ - name: Build
54
+ run: |
55
+ python -m pip install --upgrade pip build
56
+ python -m build
57
+ - name: Upload wheel
58
+ uses: actions/upload-artifact@v4
59
+ with:
60
+ name: dist
61
+ path: dist/*
@@ -1,6 +1,9 @@
1
1
  pmemstat.bundle
2
2
  .venv/
3
3
  dist/
4
+ build/
4
5
  __pycache__/
6
+ .pytest_cache/
5
7
  .vscode/
6
- *.egg-info/
8
+ *.egg-info/
9
+ memtest
@@ -0,0 +1,374 @@
1
+ Metadata-Version: 2.4
2
+ Name: pmemstat
3
+ Version: 4.0.4
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` — runs as root via `sudo` to show *all* processes (your password may be required)
30
+ > * **No root capability?** `pmemstat -U` (or `--user`) — an account without root privilege opts out and sees only *its own* processes
31
+ > * **Upgrade:** `pipx upgrade pmemstat || pipx install pmemstat`
32
+ > * Type `?` within `pmemstat` for the help screen.
33
+
34
+ > **`pmemstat` runs as root by default; an unprivileged (`-U`) run is a degraded, partial view, not a smaller-but-equivalent one:**
35
+ > 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.
36
+ > 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.
37
+ > 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.
38
+ > 4. **Kill mode reaches only your own processes.**
39
+ >
40
+ > Use `-U` only when you deliberately want a user-only view (or cannot elevate); otherwise the default root run works as designed.
41
+
42
+
43
+ # pmemstat - Proportional Memory Status
44
+
45
+ `pmemstat` shows detailed **proportional** memory use of **Linux** processes by digesting `/proc/{PID}/smaps` and `/proc/{PID}/smaps_rollup`.
46
+
47
+ 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.
48
+
49
+ 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. In window mode, `o` toggles the detail.
50
+
51
+ 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.
52
+
53
+ ## How `pmemstat` Compares to Other Tools
54
+ 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).
55
+
56
+ | Feature | `pmemstat` | `smem` | `systemd-cgtop` | `htop` |
57
+ |---|---|---|---|---|
58
+ | **Proportional memory (PSS)** accounting | ✓ core design | ✓ core design | ✗ (`memory.current`) | ✗ (RES, double-counts) |
59
+ | **Memory-type breakdown** (shared/swap/stack/text/data) | ✓ | ✗ (USS/PSS/RSS/Swap only) | ✗ | ✗ |
60
+ | **Proportional swap** per grouping | ✓ | ✗ (flat Swap column) | ✗ | ✗ (total swap only) |
61
+ | **Group processes** by executable/command | ✓ aggregates PIDs into one row | ✓ by command by default | ✗ | ✗ (tree view only) |
62
+ | **Group by cgroup v2** (services, scopes, containers) | ✓ (`-g cgroup`) | ✗ | ✓ (its core design) | ✗ |
63
+ | **Memory-leak / growth monitoring** (annotates top-N growers; only re-shows significant growth) | ✓ (`G`, `--growth-style`, growth sort) | ✗ | ✗ | ✗ |
64
+ | **zRAM-aware effective RAM** (eTot/eUsed/eAvail) | ✓ | ✗ | ✗ | ✗ |
65
+ | **System pressure (PSI)** in header (memory/cpu/io, some/full) | ✓ (`-P`) | ✗ | ✗ | ✗ |
66
+ | **Data source** | `/proc/{PID}/smaps*` | `/proc/{PID}/smaps` | `/sys/fs/cgroup` | `/proc` + libs |
67
+ | **Interactive full-screen window** | ✓ (curses) | ✗ (one-shot) | ✓ | ✓ |
68
+ | **CPU usage reporting** | ✓ | ✗ (memory only) | ✓ | ✓ |
69
+ | **Kill processes** | ✓ (inline confirm) | ✗ | ✗ | ✓ |
70
+ | **Inline search-as-you-type** | ✓ (`/`) | ✗ | ✗ | ✓ (F3) |
71
+ | **Requires root for all-process detail** | ✓ (else only your own) | ✓ (else only your own) | ✗ | ✗ |
72
+
73
+ 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.
74
+
75
+ ## Installation Options
76
+ * `pmemstat` requires **Python 3.10 or later**.
77
+ * Root is needed to read `smaps`/`smaps_rollup` for **all** processes, so `pmemstat` runs as root via `sudo` by default. Persistent default flags go in `PMEMSTAT_ARGS` (e.g. `export PMEMSTAT_ARGS='--psi --loop 3 -s name'`); anything typed on the command line still takes precedence.
78
+ * If elevation fails (for example, no terminal for a password prompt and sudo not pre-authorized), the run exits with a clear message rather than silently downgrading.
79
+ * To opt out of elevation and run user-only, use `--user`/`-U`; the header marks such a run with `--user` so the degraded (partial) view is obvious.
80
+
81
+ See the **Quick Start** above for the basic install; **Alternative Installation Options** below covers system-wide and non-`pipx` installs.
82
+
83
+
84
+ ## Usage
85
+ ```
86
+ usage: pmemstat [-h] [-D] [-C] [-a CPU_AVG_SECS] [-P]
87
+ [-g {exe,cmd,pid,cgroup,cgroupCharge}] [-f] [-k MIN_DELTA_KB]
88
+ [-l LOOP_SECS] [-L CMDLEN] [-t TOP_PCT] [-U]
89
+ [--save-history-now] [--reset-history-now] [-o]
90
+ [-u {MB,KB,human}] [-s {mem,cpu,name,growth}]
91
+ [--growth-style {off,both,growth,rate}] [--growth-top {all,10}]
92
+ [--no-growth-history] [--dont-save-growth-history] [-/ SEARCH]
93
+ [-W] [pids ...]
94
+
95
+ positional arguments:
96
+ pids list of pids/groups (none means every accessible pid)
97
+
98
+ options:
99
+ -h, --help show this help message and exit
100
+ -D, --debug debug mode (the more Ds, the higher the debug level)
101
+ -C, --no-cpu do NOT report percent CPU (only in window mode)
102
+ -a, --cpu-avg-secs CPU_AVG_SECS
103
+ CPU moving-average smoothing window in seconds [5-90,
104
+ dflt=20]
105
+ -P, --psi show PSI (system pressure in header + memPSI% column)
106
+ [dflt=off]
107
+ -g, --groupby {exe,cmd,pid,cgroup,cgroupCharge}
108
+ grouping method for presenting rows; the cgroup
109
+ variants differ only in the memory view: "cgroup"
110
+ (footprt) or "cgroupCharge" (memory.current)
111
+ -f, --fit-to-window do not overflow window [if -w]
112
+ -k, --min-delta-kb MIN_DELTA_KB
113
+ minimum delta KB to show again [dflt=100 if DB else
114
+ 1000]
115
+ -l, --loop LOOP_SECS loop interval in secs [dflt=5 if -w else 0]
116
+ -L, --cmdlen CMDLEN max shown command length [dflt=36 if not -w]
117
+ -t, --top-pct TOP_PCT
118
+ report group contributing to top pct of ptotal
119
+ [dflt=100]
120
+ -U, --user opt out of the default root elevation and run as user;
121
+ marks the header with a dimmed "--user" so the
122
+ degraded view is obvious
123
+ --save-history-now save the current stats for growth tracking and exit
124
+ (runs as root; ignores other options and the
125
+ environment)
126
+ --reset-history-now reset the saved growth history so all growth is
127
+ measured from the current values, then exit (runs as
128
+ root; ignores other options and the environment)
129
+ -o, --others expand "other" into shSYSV, shOth, stack, text
130
+ -u, --units {MB,KB,human}
131
+ units of memory [dflt=MB]
132
+ -s, --sortby {mem,cpu,name,growth}
133
+ sort method for presenting rows
134
+ --growth-style {off,both,growth,rate}
135
+ leak/growth style: off|both|growth|rate [dflt=off]
136
+ --growth-top {all,10}
137
+ annotate only the top-N growers: all|10 [dflt=all]
138
+ --no-growth-history do NOT seed growth from the saved history (fresh
139
+ baseline)
140
+ --dont-save-growth-history
141
+ do NOT update the saved growth history
142
+ -/, --search SEARCH show items with search string in name
143
+ -W, --no-window show in "curses" window [disables: -D,-t,-L]
144
+
145
+ ```
146
+ Explanation of some options and arguments:
147
+ * `-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.
148
+ * `-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.
149
+ * `-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.
150
+ * `-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`).
151
+ * `--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.
152
+ * `--growth-top {all,10}` - annotate only the top-N growers by the displayed metric (default `all` = no cap).
153
+ * `--no-growth-history` / `--dont-save-growth-history` - opt out of (respectively) seeding growth from the saved history and updating it; `PMEMSTAT_NO_HISTORY` disables both. A non-root run (`-U`) always disables both. See "Memory growth (leak) detection" below.
154
+ * `-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).
155
+ * `pids` - the positional arguments may be pids (i.e., numbers) or the names of executables (as shown by `-gexe`)
156
+
157
+
158
+ ## Grouping by cgroup v2
159
+ 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 `/`.
160
+
161
+ There are two cgroup memory views:
162
+ * `kcharge` - what the kernel charges, and what `memory.max` / the OOM killer act on; the number `systemd-cgtop`/`docker stats` show.
163
+ * `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.
164
+
165
+ 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`).
166
+
167
+ 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:
168
+ * `anon` - anonymous memory charged to the cgroup
169
+ * `cache` - page cache (reclaimable inactive cache removed in the `footprt` view)
170
+ * `kmem` - kernel memory (reclaimable slab removed in the `footprt` view)
171
+ * `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)
172
+ * `oK` - the remainder, normally just `sock`, so the row adds up
173
+ * `footprt` / `kcharge` - the row **total** for the active grouping (`cgroup` -> `footprt`; `cgroupCharge` -> `kcharge`):
174
+ * `footprt` (default) - the derived **footprint**: what the cgroup really needs to keep resident; also the growth metric
175
+ * `kcharge` - `memory.current`, the kernel's own charge
176
+ * `memPSI%` - memory-pressure stall time as a percentage; shown only with `-P` (PSI), beside `cpu%`
177
+
178
+ 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).
179
+
180
+ 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 ----`).
181
+
182
+ 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).
183
+
184
+ ## System pressure (PSI)
185
+ 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.
186
+
187
+ ```
188
+ SOME 10s 60s 300s FULL 10s 60s 300s
189
+ memPSI% 0.42 0.10 0.02 0.00 0.00 0.00
190
+ cpuPSI% 0.00 0.03 0.00 - - -
191
+ ioPSI% 0.15 0.60 1.04 0.10 0.49 0.96
192
+ ```
193
+ 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 this kernel does not provide is omitted; if no resource is readable, the table is not shown.
194
+
195
+ This is the **system-wide** view, distinct from the per-cgroup `memPSI%` column described under "Grouping by cgroup v2".
196
+
197
+ ## Example Output
198
+ ```
199
+ 20:49:12 Tot=7.6G Used=6.2G Avail=1.4G OthK=512M OthU=3.6G Sh+Tmp=477M PIDs=174
200
+ 2.4%/ker MajF/s=2 zRAM=813M CR=4.3 eTot:16.8G eUsed:8.8G eAvail:8.0G
201
+ cpu% pswap other data ptotal key/info (exe by mem)
202
+ 60.8 2,535 593 3,988 7,116 T 174x --TOTALS in MB --
203
+ ───────────────────────────────────────────────────────────────────────────────
204
+ 5.9 1,366 90 2,110 3,567 24x browser
205
+ 16.6 89 117 822 1,028 9x code
206
+ 5.9 270 32 291 593 3.5G 2h3m 1x firefox
207
+ 1.6 95 68 335 497 6x exe
208
+ 2.4 150 66 94 310 6x brave
209
+ 16.0 52 73 152 277 3x vivaldi
210
+ 0.1 39 19 13 70 2x konsole
211
+ ```
212
+
213
+ This is the default **root** run; an unprivileged (`-U`) run shows a single `Oth` (see below).
214
+
215
+ In the default window loop we see
216
+ * a **leader line** with the current time, and `Tot`, `Avail`, `Used` (= Tot-Avail) and `Sh+Tmp` from `/proc/meminfo`, plus:
217
+ * 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.
218
+ * As **root** every process is accounted for, so `Oth` splits into **OthK** = `SUnreclaim + KernelStack + PageTables` (kernel-owned) and **OthU** = `Oth - OthK` (the userland remainder).
219
+ * 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.
220
+ * 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`).
221
+ * contributing PIDs vs the total excluding kernel threads
222
+ * an optional **PSI block** (with `-P`), as described in "System pressure (PSI)" above
223
+ * a **second leader line for zRAM** (only if zRAM is active):
224
+ * percent CPU consumed by kernel processes (not normalized: 4 CPUs can total 400%) — with zRAM mostly the swap process
225
+ * **MajF/s** - major page faults per second; 100+/s means heavy swapping and memory pressure, 1000+/s is extreme
226
+ * **zRAM** - "Used" RAM storing zRAM data
227
+ * **eTot** / **eUsed** / **eAvail** - effective Total / Used / Available RAM (more accurate as **zRAM** grows)
228
+ * a **header line with the reported fields**:
229
+ * **cpu** - percent CPU (again not normalized to 100%)
230
+ * **pswap** - proportional swap use (per smaps_rollup)
231
+ * **other** - partly sums these categories (shown by the key, `o`):
232
+ * **shSYSV** - proportional System V shared memory use (per smaps)
233
+ * **shOth** - proportional use of other shared memory (per smaps)
234
+ * **stack** - exclusive stack use per smaps
235
+ * **text** - proportional use of text memory (the read-only binary code, per smaps)
236
+ * **data** - exclusive data ("heap") use per smaps
237
+ * **ptotal** - proportional memory of all categories (sum of the columns to the left); with zRAM it includes **pswap**
238
+ * *empty* - entry type: **T** (grand total), **A** (newly added grouping), **O** (overflow groupings below `--top-pct`) or **x** (a grouping whose processes have all ended; its last summary is kept briefly)
239
+ * **growth** - inline growth annotation for top-N growers (e.g. `3.5G 2h3m`); only when growth mode is not `off`
240
+ * **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.
241
+
242
+ ## Colour and Themes
243
+ In window mode colour highlights titles and values that are changing; it is
244
+ decoration only, so a terminal without colour (or `TERM=dumb`) still shows the
245
+ full report in plain text.
246
+
247
+ `t` opens the theme picker (`ESC` returns) and cycles the appearance. **`mono`**
248
+ turns colour off entirely, using bold and reverse-video attributes for emphasis.
249
+
250
+ The choice is remembered per user. `--theme` selects a theme, `--no-color` (or
251
+ the standard `NO_COLOR` convention) disables colour, `--theme mono` is the same
252
+ as `--no-color`, and `--list-themes` prints the available names.
253
+
254
+ ## Memory growth (leak) detection
255
+
256
+ With `--growth-style` (or the `G` key in window mode) `pmemstat` annotates the
257
+ groups that are growing, making slow leaks visible without scrolling history:
258
+
259
+ ```
260
+ 5.9 270 32 291 593 3.5G 2h3m 1x firefox
261
+ ```
262
+
263
+ * The annotation is `{human growth} {interval}` (e.g. `3.5G 2h3m`); `rate` adds
264
+ a per-day projection (`3.5G/d`) once the baseline spans at least a minute, and
265
+ `growth` shows just the size.
266
+ * The column's `growth` label sits on the **TOTALS** row, directly above the
267
+ annotations it titles; the TOTALS row's `T` marker is dropped there (its info
268
+ already reads TOTALS) and returns when growth is `off`.
269
+ * Changing the grouping (`g`/`o`) rebuilds every row, which would mark them all
270
+ new for one refresh. With growth `off` that `A` flash is kept (it explains the
271
+ reshuffle), but in a growth mode the annotations are shown instead, from the
272
+ per-view history that survives the change; a key that genuinely appears later
273
+ still gets its `A`.
274
+ * Growth is measured from the group's **first observation this boot** (possibly
275
+ an earlier run), not merely since this invocation; a reboot resets it. This
276
+ history is system-wide and **root-only**: a non-root run (`-U`) measures only
277
+ from this run's start. Opt out with `--no-growth-history`,
278
+ `--dont-save-growth-history`, or `PMEMSTAT_NO_HISTORY` (both).
279
+ * Startup bursts age out, and growth is clamped at zero, so shrinking is not
280
+ reported.
281
+ * A row is annotated only if its growth (KB) is at least `-k` **and** it is
282
+ among the top-N growers (`--growth-top`, default `all`; `p` cycles `all`/`10`).
283
+ `-s growth` sorts by the displayed metric.
284
+ * With a cgroup grouping the metric is the grouping's total (`footprt` for
285
+ `-g cgroup`, `kcharge` for `-g cgroupCharge`) rather than proportional PSS.
286
+ * A system-wide line beneath the leader closes the identity
287
+ `ΔUsed = ΔTOTALS(ptotal) + Δ(Sh+Tmp) + ΔOth`; as **root** the residual splits
288
+ into `ΔOthK`/`ΔOthU`, so kernel/driver growth is separable from userspace and
289
+ tmpfs, while a non-root run shows a single `ΔOth`. The shared baseline interval
290
+ trails as `[1h58m]` in every growth mode.
291
+
292
+ ## Updating History from Cron / Startup
293
+ `pmemstat --save-history-now` performs one silent scan, saves the current
294
+ history and exits (`0` on success, `1` on failure with a message on stderr). It
295
+ runs as root (elevating via `sudo` if needed) and ignores all other options and
296
+ `PMEMSTAT_ARGS`, so it is safe from cron or a startup unit. Run it as the user
297
+ whose history you want to update.
298
+
299
+ `pmemstat --reset-history-now` scans the same way but **replaces** the saved
300
+ history rather than merging: current values become the new baseline, as if the
301
+ tool had first run just after boot. It likewise runs as root and ignores other
302
+ options. Inside the window, `z` performs the same reset (only as root while the
303
+ growth annotation is on), asking for `y`/`Y` confirmation first; `ESC` or 10
304
+ seconds declines.
305
+
306
+ ## Key Legend (Window Mode)
307
+ The top line of the header is an always-visible key legend with the live search
308
+ field appended, so the available keys are in evidence without opening the help
309
+ screen:
310
+
311
+ ?:help group units sort cpu Kill PSI Growth /{regex}
312
+
313
+ `?` opens the complete list of keys plus the navigation keys (press it again, or
314
+ `ESC`, to return). `zap` is appended for a root run while the growth annotation
315
+ is showing, since resetting the baseline has no visible effect otherwise.
316
+
317
+ ## Help Screen (in Window Mode, Press '?')
318
+ In window mode, `?` opens the help screen (and closes it again); **ESC** also
319
+ closes it. Navigation keys are at the top and, below the line, the option keys
320
+ with their possible values (the current choice is highlighted). Typing an option
321
+ key (e.g. `c`) advances to the next value, applied on the next loop; the option
322
+ keys also work directly on the main screen without opening the help screen. `t`
323
+ opens the theme picker (where `t` cycles the appearance), and `K` on this screen
324
+ toggles **demo mode**, which shows the last key pressed — handy for screencasts.
325
+ **ENTER** closes nothing here — it stays reserved for kill mode's row selection.
326
+
327
+ ## Inline Search (Window Mode)
328
+ Press `/` to activate inline search mode; the live search field is the trailing
329
+ `/{regex}` on the first header line. The display filters in real-time,
330
+ case-insensitively, matching any part of the process info. **Enter** commits the
331
+ search; **ESC** cancels and clears it.
332
+
333
+ ## Kill Mode (Window Mode)
334
+ Press `K` (on the report; on the Help screen `K` is demo mode instead) to enter
335
+ "Kill Mode" where you can select and kill processes:
336
+ 1. Use the navigation keys to highlight a row and press **Enter**
337
+ 2. An inline confirmation prompt names the process and its PID count; press **`y`** to kill the process(es) or **ESC** to cancel
338
+ 3. A brief flash message reports the result
339
+ 4. Press `K` again to exit Kill Mode
340
+
341
+ ## Scroll Position (Window Mode)
342
+
343
+ Sometimes, the horizontal line between the header and scrollable region has a reverse video block (under the "351" in this case):
344
+ ![scroll-pos example](https://github.com/joedefen/pmemstat/blob/main/images/scroll-pos.png?raw=true)
345
+
346
+ Its position and length indicate how far through the document you are and how
347
+ much of it is visible; with no block, the document does not overflow. Press `f`
348
+ to fit the document to the screen with a "rollup" line summarizing the lines
349
+ that would not fit.
350
+
351
+ ## Quirks and Details
352
+ * **pswap** comes only from `smaps_rollup`, so it may be slightly out of sync with the `smaps` data.
353
+ * **ptotal** (from `smaps`) and **pss** (from `smaps_rollup`, usually hidden) differ more than expected, though still closely.
354
+ * After the first loop, refreshes are faster but slightly less detailed.
355
+ * 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`).
356
+
357
+ ## Alternative Installation Options
358
+ The Quick Start's per-user `pipx` install is preferred. Variants:
359
+ * **System-wide with `pipx` (root)** — makes `pmemstat` available to every account and to plain `sudo pmemstat` (the launcher lands in `/usr/local/bin`):
360
+ ```
361
+ sudo pipx install --global pmemstat
362
+ # to upgrade: sudo pipx upgrade --global pmemstat || sudo pipx install --global pmemstat
363
+ # to uninstall: sudo pipx uninstall --global pmemstat
364
+ ```
365
+ * **From PyPi as non-root (`pip`)** — you need `~/.local/bin` on your `$PATH`:
366
+ ```
367
+ python -m pip install --user pmemstat
368
+ # to uninstall: python -m pip uninstall pmemstat
369
+ ```
370
+ * **From PyPi as root (`pip`)** — installs into `/usr/local/bin` for all users; `PIP_BREAK_SYSTEM_PACKAGES=1` may be required on some distros:
371
+ ```
372
+ sudo PIP_BREAK_SYSTEM_PACKAGES=1 python3 -m pip install pmemstat
373
+ # to uninstall: sudo python -m pip uninstall pmemstat
374
+ ```