memtaskman 0.1.0__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.
- memtaskman-0.1.0/.github/workflows/ci.yml +64 -0
- memtaskman-0.1.0/.github/workflows/release.yml +61 -0
- memtaskman-0.1.0/.gitignore +7 -0
- memtaskman-0.1.0/LICENSE +21 -0
- memtaskman-0.1.0/PKG-INFO +186 -0
- memtaskman-0.1.0/README.md +169 -0
- memtaskman-0.1.0/pyproject.toml +32 -0
- memtaskman-0.1.0/src/memtaskman/__init__.py +3 -0
- memtaskman-0.1.0/src/memtaskman/__main__.py +3 -0
- memtaskman-0.1.0/src/memtaskman/app.py +505 -0
- memtaskman-0.1.0/src/memtaskman/cli.py +135 -0
- memtaskman-0.1.0/src/memtaskman/collector.py +383 -0
- memtaskman-0.1.0/src/memtaskman/diff.py +58 -0
- memtaskman-0.1.0/src/memtaskman/fmt.py +73 -0
- memtaskman-0.1.0/src/memtaskman/linux.py +198 -0
- memtaskman-0.1.0/src/memtaskman/model.py +136 -0
- memtaskman-0.1.0/src/memtaskman/report.py +85 -0
- memtaskman-0.1.0/src/memtaskman/runtime.py +224 -0
- memtaskman-0.1.0/src/memtaskman/win32.py +390 -0
- memtaskman-0.1.0/tests/test_core.py +119 -0
- memtaskman-0.1.0/tests/test_linux_parsers.py +95 -0
- memtaskman-0.1.0/tests/test_runtime.py +90 -0
- memtaskman-0.1.0/tools/smoke.py +124 -0
- memtaskman-0.1.0/uv.lock +150 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master, main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ci-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
|
|
16
|
+
env:
|
|
17
|
+
PYTHONUTF8: "1"
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
test:
|
|
21
|
+
name: ${{ matrix.os }} / py${{ matrix.python }}
|
|
22
|
+
runs-on: ${{ matrix.os }}
|
|
23
|
+
strategy:
|
|
24
|
+
fail-fast: false
|
|
25
|
+
matrix:
|
|
26
|
+
os: [windows-latest, ubuntu-latest, macos-latest]
|
|
27
|
+
python: ["3.11", "3.14"]
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
|
|
31
|
+
- uses: astral-sh/setup-uv@v6
|
|
32
|
+
with:
|
|
33
|
+
python-version: ${{ matrix.python }}
|
|
34
|
+
enable-cache: true
|
|
35
|
+
|
|
36
|
+
- name: Install
|
|
37
|
+
run: uv sync --locked
|
|
38
|
+
|
|
39
|
+
- name: Unit tests
|
|
40
|
+
run: uv run --locked python -m unittest discover -s tests -v
|
|
41
|
+
|
|
42
|
+
- name: Smoke test (live counters, inspector, TUI, CLI)
|
|
43
|
+
run: uv run --locked python tools/smoke.py
|
|
44
|
+
|
|
45
|
+
- name: One-shot report
|
|
46
|
+
run: uv run --locked mtm --once --top 15
|
|
47
|
+
|
|
48
|
+
build:
|
|
49
|
+
name: Build sdist + wheel
|
|
50
|
+
runs-on: ubuntu-latest
|
|
51
|
+
steps:
|
|
52
|
+
- uses: actions/checkout@v4
|
|
53
|
+
- uses: astral-sh/setup-uv@v6
|
|
54
|
+
- run: uv build
|
|
55
|
+
- name: Install the built wheel and run it
|
|
56
|
+
run: |
|
|
57
|
+
uv venv /tmp/wheeltest
|
|
58
|
+
uv pip install --python /tmp/wheeltest dist/*.whl
|
|
59
|
+
/tmp/wheeltest/bin/mtm --version
|
|
60
|
+
/tmp/wheeltest/bin/mtm --once --top 5
|
|
61
|
+
- uses: actions/upload-artifact@v4
|
|
62
|
+
with:
|
|
63
|
+
name: dist
|
|
64
|
+
path: dist/
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Push a tag like v0.2.0 to build the package, attach it to a GitHub release
|
|
4
|
+
# and publish it to PyPI.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
tags: ["v*"]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
release:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: astral-sh/setup-uv@v6
|
|
19
|
+
|
|
20
|
+
- name: Check tag matches package version
|
|
21
|
+
run: |
|
|
22
|
+
version=$(grep -m1 '^version' pyproject.toml | cut -d'"' -f2)
|
|
23
|
+
if [ "v$version" != "${GITHUB_REF_NAME}" ]; then
|
|
24
|
+
echo "Tag ${GITHUB_REF_NAME} does not match pyproject version $version" >&2
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
- name: Test
|
|
29
|
+
run: |
|
|
30
|
+
uv sync --locked
|
|
31
|
+
uv run --locked python -m unittest discover -s tests
|
|
32
|
+
|
|
33
|
+
- name: Build
|
|
34
|
+
run: uv build
|
|
35
|
+
|
|
36
|
+
- name: Create GitHub release
|
|
37
|
+
env:
|
|
38
|
+
GH_TOKEN: ${{ github.token }}
|
|
39
|
+
run: gh release create "$GITHUB_REF_NAME" dist/* --title "$GITHUB_REF_NAME" --generate-notes
|
|
40
|
+
|
|
41
|
+
- uses: actions/upload-artifact@v4
|
|
42
|
+
with:
|
|
43
|
+
name: dist
|
|
44
|
+
path: dist/
|
|
45
|
+
|
|
46
|
+
pypi:
|
|
47
|
+
needs: release
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
environment:
|
|
50
|
+
name: pypi
|
|
51
|
+
url: https://pypi.org/project/memtaskman/
|
|
52
|
+
permissions:
|
|
53
|
+
id-token: write
|
|
54
|
+
steps:
|
|
55
|
+
- uses: actions/download-artifact@v4
|
|
56
|
+
with:
|
|
57
|
+
name: dist
|
|
58
|
+
path: dist/
|
|
59
|
+
- uses: astral-sh/setup-uv@v6
|
|
60
|
+
- name: Publish to PyPI
|
|
61
|
+
run: uv publish --trusted-publishing always dist/*
|
memtaskman-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yogpod
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: memtaskman
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An htop for what's inside your RAM, not just how full it is.
|
|
5
|
+
Author-email: Yogpod <yogpod332@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Environment :: Console
|
|
9
|
+
Classifier: Operating System :: MacOS
|
|
10
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Topic :: System :: Monitoring
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Requires-Dist: psutil>=5.9
|
|
15
|
+
Requires-Dist: textual>=1.0
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# memtaskman
|
|
19
|
+
|
|
20
|
+
[](https://github.com/Yogpod/memtaskman/actions/workflows/ci.yml)
|
|
21
|
+
|
|
22
|
+
An `htop` for what's **inside** your RAM, not just how full it is.
|
|
23
|
+
|
|
24
|
+
Task Manager tells you Chrome uses 5 GB. memtaskman tells you which of those bytes are
|
|
25
|
+
heap, which are DLL code shared with every other process, which are memory-mapped files
|
|
26
|
+
(fonts, caches, model weights), which are thread stacks, and how much is actually resident
|
|
27
|
+
versus merely committed. It also shows *where* the rest of your RAM is: standby cache,
|
|
28
|
+
modified pages, the compression store, kernel pools and commit charge.
|
|
29
|
+
|
|
30
|
+
Inspired by [gputaskman](https://github.com/shanselman/gputaskman), which does the same for VRAM.
|
|
31
|
+
|
|
32
|
+
> Early, experimental software. Windows is the primary target, Linux is supported (tested on Ubuntu under WSL2), macOS
|
|
33
|
+
> gets the basic psutil view without the address-space inspector.
|
|
34
|
+
|
|
35
|
+
## What you get
|
|
36
|
+
|
|
37
|
+
- **System bar**: RAM split into *In use / Modified / Standby / Free* (Windows) or
|
|
38
|
+
*In use / Cache / Free* (Linux), plus commit charge and swap/pagefile bars.
|
|
39
|
+
- **Eight history charts**: RAM in use, commit, standby/cache, compressed, swap,
|
|
40
|
+
kernel pools, page faults/s and hard faults/s. `c` cycles to a single large chart.
|
|
41
|
+
- **System panel**: available memory, modified/dirty, standby/page cache, kernel paged
|
|
42
|
+
and nonpaged pools (slab on Linux), compression store, driver-resident bytes, commit peak.
|
|
43
|
+
- **Process table**: resident (working set / RSS), private commit (Windows) or anonymous
|
|
44
|
+
resident (Linux), a smoothed **growth rate** (a steadily positive number is what a leak
|
|
45
|
+
looks like), page faults/s, kernel pool or swap, and the identified **runtime**.
|
|
46
|
+
On Linux there is also a **PSS** column (resident memory with shared pages split fairly
|
|
47
|
+
between the processes sharing them, so the column sums to real RAM use), read from
|
|
48
|
+
`smaps_rollup` for the visible rows only. Other users' processes show `-` unless you run as root.
|
|
49
|
+
- **Group by app** (`g`): every `chrome.exe`/`brave.exe`/`Discord.exe` summed into one row.
|
|
50
|
+
- **Inspector** for the selected process, built from a page-level walk of its address space:
|
|
51
|
+
|
|
52
|
+
| Category | Meaning |
|
|
53
|
+
|---------------------|-------------------------------------------------------------------|
|
|
54
|
+
| Private (heap/anon) | heaps, GC heaps, JIT code, anonymous mmap — memory only it owns |
|
|
55
|
+
| Stacks | thread stacks |
|
|
56
|
+
| Images | exe / DLL / .so pages, mostly shared with other processes |
|
|
57
|
+
| Mapped files | memory-mapped files: font caches, databases, mmap'd model weights |
|
|
58
|
+
| Shared memory | pagefile-backed sections, `/dev/shm`, memfd |
|
|
59
|
+
|
|
60
|
+
For each: bytes committed/mapped, bytes resident, and how many resident bytes are shared,
|
|
61
|
+
plus the largest file-backed mappings and the address space that is only reserved.
|
|
62
|
+
|
|
63
|
+
### Runtime identification
|
|
64
|
+
|
|
65
|
+
Runtimes are recognised from loaded modules, the executable and the command line, and every
|
|
66
|
+
claim is tagged with how strong the evidence is: **measured** (OS counter), **derived**
|
|
67
|
+
(computed by memtaskman), **identified** (from loaded modules), **hint** (a configured limit
|
|
68
|
+
such as `-Xmx` or `--max-old-space-size`, which is not usage).
|
|
69
|
+
|
|
70
|
+
Recognised: Chromium browsers and their process types (Chrome, Edge, Brave, Opera, Vivaldi,
|
|
71
|
+
WebView2, Electron apps: renderer / gpu / utility:network …), Firefox, Java, .NET and
|
|
72
|
+
.NET Framework (including chocolatey shims), Node.js / Bun / Deno, CPython (version from
|
|
73
|
+
the DLL, PyTorch detection), local LLM servers (Ollama, llama.cpp, LM Studio, vLLM,
|
|
74
|
+
Foundry Local), databases (SQL Server, PostgreSQL, MySQL, Redis, MongoDB …), hypervisor
|
|
75
|
+
hosts (`vmmem`/`vmmemWSL`, QEMU, VMware, VirtualBox) and the Windows compression store.
|
|
76
|
+
|
|
77
|
+
## Install & run
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
uv tool install . # from a clone; or: pipx install .
|
|
81
|
+
memtaskman # or: mtm
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Without installing:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
uv run mtm
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Run from an elevated terminal (or as root on Linux) to inspect processes owned by other users
|
|
91
|
+
and services; otherwise those show "access denied" in the inspector but still appear in the table.
|
|
92
|
+
|
|
93
|
+
## Keys
|
|
94
|
+
|
|
95
|
+
| Key | Action |
|
|
96
|
+
|------------|------------------------------------------|
|
|
97
|
+
| ↑ / ↓ | select a process and inspect it |
|
|
98
|
+
| `s` | cycle sort: resident, PSS (Linux), private, growth, faults, name, pid |
|
|
99
|
+
| `g` | group processes by executable |
|
|
100
|
+
| `a` | top 50 / all processes |
|
|
101
|
+
| `c` | cycle charts (all, or one metric) |
|
|
102
|
+
| `m` | slim mode (table only) |
|
|
103
|
+
| `p` | pause / resume |
|
|
104
|
+
| `?` | explanations of every counter |
|
|
105
|
+
| `q` | quit |
|
|
106
|
+
|
|
107
|
+
## Command line
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
mtm interactive TUI
|
|
111
|
+
mtm -i 2 sample every 2 seconds
|
|
112
|
+
mtm --once [--top N|--all] print a text report and exit
|
|
113
|
+
mtm --json [--top N|--all] print a JSON snapshot and exit
|
|
114
|
+
mtm --no-system skip system-wide counters
|
|
115
|
+
mtm inspect PID [--json] address-space breakdown of one process
|
|
116
|
+
mtm record out.jsonl [-i 5] [-n COUNT] [--all] append snapshots (JSON Lines)
|
|
117
|
+
mtm diff a.json b.json what grew, appeared or went away between snapshots
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Tip: take both snapshots with `--all` before using `diff`, otherwise processes outside the
|
|
121
|
+
top N look like they appeared or vanished.
|
|
122
|
+
|
|
123
|
+
## How it measures
|
|
124
|
+
|
|
125
|
+
| Source | Platform | Used for |
|
|
126
|
+
|--------|----------|----------|
|
|
127
|
+
| PDH `\Memory\*` counters | Windows | standby (3 priorities), modified, free+zero, cache, pools, fault rates |
|
|
128
|
+
| `GetPerformanceInfo` | Windows | commit charge / limit / peak |
|
|
129
|
+
| `GetProcessMemoryInfo` (via psutil) | Windows | working set, private commit, pools, fault counts |
|
|
130
|
+
| `VirtualQueryEx` | Windows | every region: committed vs reserved, image / mapped / private, stacks (guard pages) |
|
|
131
|
+
| `GetMappedFileName` | Windows | which file backs each image/mapped region |
|
|
132
|
+
| `QueryWorkingSet` | Windows | every resident page and its shared bit, attributed back to regions |
|
|
133
|
+
| `EnumProcessModulesEx` | Windows | loaded DLLs for runtime identification |
|
|
134
|
+
| `/proc/meminfo`, `/proc/vmstat` | Linux | cache, dirty, slab, zswap, commit, fault rates |
|
|
135
|
+
| `/proc/<pid>/status`, `stat` | Linux | RssAnon / RssFile / RssShmem, VmSwap, VmHWM, faults |
|
|
136
|
+
| `/proc/<pid>/smaps_rollup` | Linux | PSS and USS (private clean + dirty) for visible rows, refreshed every 2 s at most |
|
|
137
|
+
| `/proc/<pid>/smaps`, `maps` | Linux | per-mapping Rss / Shared / Swap, loaded libraries |
|
|
138
|
+
|
|
139
|
+
Nothing reads the *contents* of another process's memory; only metadata about its pages.
|
|
140
|
+
|
|
141
|
+
Caveats:
|
|
142
|
+
|
|
143
|
+
- Resident totals in group mode double count pages shared between members (DLLs, shared
|
|
144
|
+
memory). Private totals do not overlap.
|
|
145
|
+
- On Windows, *Private* is commit charge: it can exceed the working set when pages are
|
|
146
|
+
paged out or compressed.
|
|
147
|
+
- The compression store is reported as the working set of the `MemCompression` process.
|
|
148
|
+
- The inspector walk takes ~0.1-1 s for very large processes; it runs in the background and
|
|
149
|
+
refreshes every 5 seconds.
|
|
150
|
+
|
|
151
|
+
## Development
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
uv sync
|
|
155
|
+
uv run python -m unittest discover -s tests -v # hardware-independent unit tests
|
|
156
|
+
uv run python tools/smoke.py # live check: counters, inspector, TUI, every CLI command
|
|
157
|
+
uv build
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
CI (`.github/workflows/ci.yml`) runs the unit tests and the smoke test on Windows, Linux and
|
|
161
|
+
macOS with Python 3.11 and 3.14, then builds the wheel and runs it. To release, bump `version`
|
|
162
|
+
in `pyproject.toml`, then push a matching tag (`git tag v0.2.0 && git push main v0.2.0`). The release
|
|
163
|
+
workflow tests, builds, attaches the sdist and wheel to a GitHub release and publishes them
|
|
164
|
+
to PyPI.
|
|
165
|
+
|
|
166
|
+
Layout:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
src/memtaskman/
|
|
170
|
+
app.py Textual TUI
|
|
171
|
+
cli.py argument parsing, --once / --json / inspect / record / diff
|
|
172
|
+
collector.py sampling, growth & fault rates, runtime cache, group-by-app
|
|
173
|
+
win32.py ctypes: PDH, GetPerformanceInfo, VirtualQueryEx, QueryWorkingSet, modules
|
|
174
|
+
linux.py /proc parsers (pure) and readers
|
|
175
|
+
runtime.py runtime identification from modules + command line (pure)
|
|
176
|
+
model.py dataclasses shared by everything
|
|
177
|
+
report.py text / JSON output
|
|
178
|
+
diff.py snapshot comparison
|
|
179
|
+
fmt.py byte & rate formatting
|
|
180
|
+
tests/ hardware-independent unit tests
|
|
181
|
+
tools/smoke.py end-to-end check against the live machine (used by CI)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## License
|
|
185
|
+
|
|
186
|
+
MIT
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# memtaskman
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Yogpod/memtaskman/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
An `htop` for what's **inside** your RAM, not just how full it is.
|
|
6
|
+
|
|
7
|
+
Task Manager tells you Chrome uses 5 GB. memtaskman tells you which of those bytes are
|
|
8
|
+
heap, which are DLL code shared with every other process, which are memory-mapped files
|
|
9
|
+
(fonts, caches, model weights), which are thread stacks, and how much is actually resident
|
|
10
|
+
versus merely committed. It also shows *where* the rest of your RAM is: standby cache,
|
|
11
|
+
modified pages, the compression store, kernel pools and commit charge.
|
|
12
|
+
|
|
13
|
+
Inspired by [gputaskman](https://github.com/shanselman/gputaskman), which does the same for VRAM.
|
|
14
|
+
|
|
15
|
+
> Early, experimental software. Windows is the primary target, Linux is supported (tested on Ubuntu under WSL2), macOS
|
|
16
|
+
> gets the basic psutil view without the address-space inspector.
|
|
17
|
+
|
|
18
|
+
## What you get
|
|
19
|
+
|
|
20
|
+
- **System bar**: RAM split into *In use / Modified / Standby / Free* (Windows) or
|
|
21
|
+
*In use / Cache / Free* (Linux), plus commit charge and swap/pagefile bars.
|
|
22
|
+
- **Eight history charts**: RAM in use, commit, standby/cache, compressed, swap,
|
|
23
|
+
kernel pools, page faults/s and hard faults/s. `c` cycles to a single large chart.
|
|
24
|
+
- **System panel**: available memory, modified/dirty, standby/page cache, kernel paged
|
|
25
|
+
and nonpaged pools (slab on Linux), compression store, driver-resident bytes, commit peak.
|
|
26
|
+
- **Process table**: resident (working set / RSS), private commit (Windows) or anonymous
|
|
27
|
+
resident (Linux), a smoothed **growth rate** (a steadily positive number is what a leak
|
|
28
|
+
looks like), page faults/s, kernel pool or swap, and the identified **runtime**.
|
|
29
|
+
On Linux there is also a **PSS** column (resident memory with shared pages split fairly
|
|
30
|
+
between the processes sharing them, so the column sums to real RAM use), read from
|
|
31
|
+
`smaps_rollup` for the visible rows only. Other users' processes show `-` unless you run as root.
|
|
32
|
+
- **Group by app** (`g`): every `chrome.exe`/`brave.exe`/`Discord.exe` summed into one row.
|
|
33
|
+
- **Inspector** for the selected process, built from a page-level walk of its address space:
|
|
34
|
+
|
|
35
|
+
| Category | Meaning |
|
|
36
|
+
|---------------------|-------------------------------------------------------------------|
|
|
37
|
+
| Private (heap/anon) | heaps, GC heaps, JIT code, anonymous mmap — memory only it owns |
|
|
38
|
+
| Stacks | thread stacks |
|
|
39
|
+
| Images | exe / DLL / .so pages, mostly shared with other processes |
|
|
40
|
+
| Mapped files | memory-mapped files: font caches, databases, mmap'd model weights |
|
|
41
|
+
| Shared memory | pagefile-backed sections, `/dev/shm`, memfd |
|
|
42
|
+
|
|
43
|
+
For each: bytes committed/mapped, bytes resident, and how many resident bytes are shared,
|
|
44
|
+
plus the largest file-backed mappings and the address space that is only reserved.
|
|
45
|
+
|
|
46
|
+
### Runtime identification
|
|
47
|
+
|
|
48
|
+
Runtimes are recognised from loaded modules, the executable and the command line, and every
|
|
49
|
+
claim is tagged with how strong the evidence is: **measured** (OS counter), **derived**
|
|
50
|
+
(computed by memtaskman), **identified** (from loaded modules), **hint** (a configured limit
|
|
51
|
+
such as `-Xmx` or `--max-old-space-size`, which is not usage).
|
|
52
|
+
|
|
53
|
+
Recognised: Chromium browsers and their process types (Chrome, Edge, Brave, Opera, Vivaldi,
|
|
54
|
+
WebView2, Electron apps: renderer / gpu / utility:network …), Firefox, Java, .NET and
|
|
55
|
+
.NET Framework (including chocolatey shims), Node.js / Bun / Deno, CPython (version from
|
|
56
|
+
the DLL, PyTorch detection), local LLM servers (Ollama, llama.cpp, LM Studio, vLLM,
|
|
57
|
+
Foundry Local), databases (SQL Server, PostgreSQL, MySQL, Redis, MongoDB …), hypervisor
|
|
58
|
+
hosts (`vmmem`/`vmmemWSL`, QEMU, VMware, VirtualBox) and the Windows compression store.
|
|
59
|
+
|
|
60
|
+
## Install & run
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
uv tool install . # from a clone; or: pipx install .
|
|
64
|
+
memtaskman # or: mtm
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Without installing:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
uv run mtm
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Run from an elevated terminal (or as root on Linux) to inspect processes owned by other users
|
|
74
|
+
and services; otherwise those show "access denied" in the inspector but still appear in the table.
|
|
75
|
+
|
|
76
|
+
## Keys
|
|
77
|
+
|
|
78
|
+
| Key | Action |
|
|
79
|
+
|------------|------------------------------------------|
|
|
80
|
+
| ↑ / ↓ | select a process and inspect it |
|
|
81
|
+
| `s` | cycle sort: resident, PSS (Linux), private, growth, faults, name, pid |
|
|
82
|
+
| `g` | group processes by executable |
|
|
83
|
+
| `a` | top 50 / all processes |
|
|
84
|
+
| `c` | cycle charts (all, or one metric) |
|
|
85
|
+
| `m` | slim mode (table only) |
|
|
86
|
+
| `p` | pause / resume |
|
|
87
|
+
| `?` | explanations of every counter |
|
|
88
|
+
| `q` | quit |
|
|
89
|
+
|
|
90
|
+
## Command line
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
mtm interactive TUI
|
|
94
|
+
mtm -i 2 sample every 2 seconds
|
|
95
|
+
mtm --once [--top N|--all] print a text report and exit
|
|
96
|
+
mtm --json [--top N|--all] print a JSON snapshot and exit
|
|
97
|
+
mtm --no-system skip system-wide counters
|
|
98
|
+
mtm inspect PID [--json] address-space breakdown of one process
|
|
99
|
+
mtm record out.jsonl [-i 5] [-n COUNT] [--all] append snapshots (JSON Lines)
|
|
100
|
+
mtm diff a.json b.json what grew, appeared or went away between snapshots
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Tip: take both snapshots with `--all` before using `diff`, otherwise processes outside the
|
|
104
|
+
top N look like they appeared or vanished.
|
|
105
|
+
|
|
106
|
+
## How it measures
|
|
107
|
+
|
|
108
|
+
| Source | Platform | Used for |
|
|
109
|
+
|--------|----------|----------|
|
|
110
|
+
| PDH `\Memory\*` counters | Windows | standby (3 priorities), modified, free+zero, cache, pools, fault rates |
|
|
111
|
+
| `GetPerformanceInfo` | Windows | commit charge / limit / peak |
|
|
112
|
+
| `GetProcessMemoryInfo` (via psutil) | Windows | working set, private commit, pools, fault counts |
|
|
113
|
+
| `VirtualQueryEx` | Windows | every region: committed vs reserved, image / mapped / private, stacks (guard pages) |
|
|
114
|
+
| `GetMappedFileName` | Windows | which file backs each image/mapped region |
|
|
115
|
+
| `QueryWorkingSet` | Windows | every resident page and its shared bit, attributed back to regions |
|
|
116
|
+
| `EnumProcessModulesEx` | Windows | loaded DLLs for runtime identification |
|
|
117
|
+
| `/proc/meminfo`, `/proc/vmstat` | Linux | cache, dirty, slab, zswap, commit, fault rates |
|
|
118
|
+
| `/proc/<pid>/status`, `stat` | Linux | RssAnon / RssFile / RssShmem, VmSwap, VmHWM, faults |
|
|
119
|
+
| `/proc/<pid>/smaps_rollup` | Linux | PSS and USS (private clean + dirty) for visible rows, refreshed every 2 s at most |
|
|
120
|
+
| `/proc/<pid>/smaps`, `maps` | Linux | per-mapping Rss / Shared / Swap, loaded libraries |
|
|
121
|
+
|
|
122
|
+
Nothing reads the *contents* of another process's memory; only metadata about its pages.
|
|
123
|
+
|
|
124
|
+
Caveats:
|
|
125
|
+
|
|
126
|
+
- Resident totals in group mode double count pages shared between members (DLLs, shared
|
|
127
|
+
memory). Private totals do not overlap.
|
|
128
|
+
- On Windows, *Private* is commit charge: it can exceed the working set when pages are
|
|
129
|
+
paged out or compressed.
|
|
130
|
+
- The compression store is reported as the working set of the `MemCompression` process.
|
|
131
|
+
- The inspector walk takes ~0.1-1 s for very large processes; it runs in the background and
|
|
132
|
+
refreshes every 5 seconds.
|
|
133
|
+
|
|
134
|
+
## Development
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
uv sync
|
|
138
|
+
uv run python -m unittest discover -s tests -v # hardware-independent unit tests
|
|
139
|
+
uv run python tools/smoke.py # live check: counters, inspector, TUI, every CLI command
|
|
140
|
+
uv build
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
CI (`.github/workflows/ci.yml`) runs the unit tests and the smoke test on Windows, Linux and
|
|
144
|
+
macOS with Python 3.11 and 3.14, then builds the wheel and runs it. To release, bump `version`
|
|
145
|
+
in `pyproject.toml`, then push a matching tag (`git tag v0.2.0 && git push main v0.2.0`). The release
|
|
146
|
+
workflow tests, builds, attaches the sdist and wheel to a GitHub release and publishes them
|
|
147
|
+
to PyPI.
|
|
148
|
+
|
|
149
|
+
Layout:
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
src/memtaskman/
|
|
153
|
+
app.py Textual TUI
|
|
154
|
+
cli.py argument parsing, --once / --json / inspect / record / diff
|
|
155
|
+
collector.py sampling, growth & fault rates, runtime cache, group-by-app
|
|
156
|
+
win32.py ctypes: PDH, GetPerformanceInfo, VirtualQueryEx, QueryWorkingSet, modules
|
|
157
|
+
linux.py /proc parsers (pure) and readers
|
|
158
|
+
runtime.py runtime identification from modules + command line (pure)
|
|
159
|
+
model.py dataclasses shared by everything
|
|
160
|
+
report.py text / JSON output
|
|
161
|
+
diff.py snapshot comparison
|
|
162
|
+
fmt.py byte & rate formatting
|
|
163
|
+
tests/ hardware-independent unit tests
|
|
164
|
+
tools/smoke.py end-to-end check against the live machine (used by CI)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
MIT
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "memtaskman"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "An htop for what's inside your RAM, not just how full it is."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"textual>=1.0",
|
|
10
|
+
"psutil>=5.9",
|
|
11
|
+
]
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Yogpod", email = "yogpod332@gmail.com" },
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Operating System :: Microsoft :: Windows",
|
|
18
|
+
"Operating System :: POSIX :: Linux",
|
|
19
|
+
"Operating System :: MacOS",
|
|
20
|
+
"Topic :: System :: Monitoring",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.scripts]
|
|
24
|
+
memtaskman = "memtaskman.cli:main"
|
|
25
|
+
mtm = "memtaskman.cli:main"
|
|
26
|
+
|
|
27
|
+
[build-system]
|
|
28
|
+
requires = ["hatchling"]
|
|
29
|
+
build-backend = "hatchling.build"
|
|
30
|
+
|
|
31
|
+
[tool.hatch.build.targets.wheel]
|
|
32
|
+
packages = ["src/memtaskman"]
|