wsltop
by adachi6k
README.md
# wsltop
`wsltop` is a unified top-like resource monitor for Windows, WSL2, Docker, and WSL Containers (WSLC). It runs natively on both Windows and WSL, combining host and guest observations on one host-wide CPU scale.
> Windows Task Manager says `VmmemWSL` is busy. `wsltop` shows which Windows, WSL, WSLC, or Docker workload is responsible.
`wsltop` provides a one-shot CLI and interactive terminal UI on both Windows and WSL. A graphical UI is outside the current scope.
AI agents can inspect the same workloads through the [read-only MCP server](#use-wsltop-from-ai-agents).
The default TUI has a compact two-line host CPU/RAM summary, history graphs and
colorized Windows/WSL/WSLC/Docker observations while keeping top-like simplicity.
Use `--header classic` for the traditional one-line header and `--color never`
for monochrome output. See [release notes](https://github.com/adachi6k/wsltop/releases/latest).

Live v0.5.0 session with workloads in another WSL distribution and running WSLC
and Docker containers, including their process details. The two demo containers
are each limited to 0.25 CPU cores. [Capture details](docs/assets/README.md).
## Quick start
Requires Windows 11 with a usable WSL2 distribution; the Linux binary runs
inside WSL2. Docker and WSLC are optional.
With [cargo-binstall](https://github.com/cargo-bins/cargo-binstall) installed,
you can fetch the x64 GitHub Releases binaries without a source build,
which is faster than `cargo install`.
### Windows
Download the **Windows x86_64 MSVC ZIP** from the
[latest GitHub Release](https://github.com/adachi6k/wsltop/releases/latest).
Extract it and open PowerShell in the versioned directory:
```powershell
.\wsltop.exe --interactive
```
You can instead install with `cargo binstall wsltop`, then run
`wsltop --interactive`.
### WSL / Linux
Fast prebuilt install:
```console
cargo binstall wsltop
wsltop --interactive
```
Or build from [crates.io](https://crates.io/crates/wsltop):
```console
cargo install --locked wsltop
```
Without Cargo, download the **Linux x86_64 tar.gz** from the
[latest GitHub Release](https://github.com/adachi6k/wsltop/releases/latest),
extract it and run `./wsltop --interactive` from the versioned directory.
See [checksum verification](#verify-downloads) for downloaded archives.
## Why wsltop?
Task Manager may show only a VM aggregate such as `VmmemWSL 35%`. That identifies the host process, but not the guest workload. `wsltop --tree` makes the hierarchy visible:
```text
WSL VM
|- process verilator
`- infra plan9
Docker
`- container build
|- process compiler
`- unattributed
```
Parent and child CPU values are attribution views, not values to add together.
## Features
- Windows native process monitoring
- Current WSL distribution process monitoring through `/proc`
- Multiple running WSL distribution monitoring
- WSL Containers monitoring
- Docker and WSLC container monitoring with in-container process attribution
- WSL and WSLC host attribution trees with an `unattributed` remainder
- Host-wide CPU normalization: all environments use Windows host logical CPUs = 100%
- Interactive terminal UI with flat/tree views, scrolling, and display toggles
- Shared CPU/memory/name sorting with ascending/descending order in TUI, CLI, and JSON
- Flat and tree JSON output
- Resource classification as `process`, `container`, `infra`, or internal `host`
- Best-effort degradation when optional WSLC, Docker, or additional-distro collectors are unavailable
## What the TUI shows
The flat view is a host-wide activity ranking. Windows and WSL processes appear alongside Docker and WSLC containers. A container is ranked once by its total CPU; optional process rows are an indented explanation of that total, not extra CPU to add to it.
This example uses 16 host logical CPUs: summary CPU percentages are host-wide, while table rows use the core scale (Docker's `11.99%` becomes `0.7%` in the summary).
```text
CPU 12.0% ▁▁▂▁▁▁▂▂▁▁▁▁▂▁▁▁▂▂▁▁▁▁▁ | Win 5.7% WSL 0.3% WSLC 0.4% Docker 0.7%
RAM 15.7/31.9G ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ | Win 16.79G WSL 1.50G WSLC 348M Docker 520M
─────────────────────────────────────────────────────────────────────────────────────────────────
ENV TYPE ID/PID CPU% MEM TIME+ COMMAND
-------------------------------------------------------------------------------------------------
Docker container 68dae66282ff 11.99% 520M - act-CI-simulate...
process 34692 11.75% 157M 62:03.46 |- simx
residual - 0.24% - - `- unattributed
WSLC container 5e0c144e6a3c 5.94% 348M - mighty_flinders
process 806 5.71% 31M 4:12.08 |- cc1plus
process 470 0.19% 6M 0:03.20 |- ninja
residual - 0.04% - - `- unattributed
Windows application 3 PIDs 4.82% 1.24G 103:27.51 Teams
Windows application 12 PIDs 2.37% 1.68G 248:10.03 Chrome
Windows application 31460 1.20% 198M 178:27.95 Taskmgr
WSL infra 5 0.05% 4M 0:14.82 plan9
[flat cpu↓ core 3.0s] q quit ? help t tree i infra:on h hosts:off 0 zero:on
```
In this example, `simx 11.75%` is included in its Docker container's `11.99%`; the values must not be added. Containers keep their position according to total container CPU, while their processes are sorted within the container. Windows rows are ranked by application, so multi-process applications such as Teams and Chrome appear once. By default, at most five processes are shown per container and additional processes are summarized.
For a Windows application row backed by one observed process, `ID/PID` shows that process's real PID. Multi-process applications show `N PIDs` without extra punctuation. If a single member's PID is unavailable, wsltop falls back to `1 PID`. Tree view exposes individual process IDs, while JSON retains `pid: null` for application totals.
Press `t` to switch to the attribution tree and answer a different question: how much of each VM or container total can wsltop explain?
```text
Host logical CPUs: 16
WSL VM 3.20%
|- infra plan9 0.05%
|- process codex 0.18%
`- unattributed 2.97%
Docker
`- container act-CI-simulate... 11.99%
|- process simx 11.75%
`- unattributed 0.24%
```
The default text/TUI scale treats one fully occupied logical CPU as `100%`; use `--cpu-scale host` for the Windows host-wide scale.
## Installation
Choose a prebuilt or Cargo install in [Quick start](#quick-start).
WSL execution requires Windows interoperability and `powershell.exe` on PATH.
Docker collection needs a reachable Docker daemon; WSLC collection needs
`wslc.exe`.
### Build from source
A Rust toolchain is required; Windows builds also need the Visual Studio C++
build tools. From PowerShell or a WSL shell:
```console
git clone https://github.com/adachi6k/wsltop.git
cd wsltop
cargo build --release --locked
```
Run `.\target\release\wsltop.exe --interactive` on Windows or
`./target/release/wsltop --interactive` in WSL.
### Verify downloads
Each archive in the
[latest GitHub Release](https://github.com/adachi6k/wsltop/releases/latest)
has a `.sha256` sidecar. Download both files into the same directory. Names
follow `wsltop-v<version>-<target>`; replace `v0.5.1` below with your downloaded
version.
Windows PowerShell:
```powershell
$archive = 'wsltop-v0.5.1-x86_64-pc-windows-msvc.zip'
$expected = ((Get-Content "$archive.sha256") -split '\s+')[0]
if ((Get-FileHash $archive -Algorithm SHA256).Hash -ne $expected) { throw 'Checksum mismatch' }
```
WSL:
```console
sha256sum --check wsltop-v0.5.1-x86_64-unknown-linux-gnu.tar.gz.sha256
```
## Usage
The default and `--once` modes take two cumulative CPU snapshots separated by `--interval-ms` and print one table.
```console
wsltop [OPTIONS]
--once Take one sampled measurement (default)
-i, --interactive Run the continuously updating terminal UI
--json Emit JSON; incompatible with --interactive
--tree Show CPU attribution; selects the initial TUI view
--limit N Limit flat resources (default: 30)
--interval-ms N Sampling/refresh interval (default: 3000, minimum: 100)
--show-wsl-host Show raw vmmem/vmmemWSL/vmmemwslc-* rows in flat views
--wsl-only Skip Windows, additional-distro, and WSLC collectors
--distro NAME Select the primary WSL distro (Windows executable only)
--no-wslc Disable WSLC collection
--no-docker Disable Docker collection
--show-container-processes Include Docker/WSLC processes (default for text/TUI)
--hide-container-processes Hide Docker/WSLC processes from flat output
--container-process-limit N Show at most N processes per container (default: 5)
--cpu-scale core|host CPU display scale for text/TUI output (default: core)
--sort cpu|memory|name Sort resources and their children (default: cpu)
--sort-order asc|desc Sort direction (default: desc)
--hide-infra Hide infrastructure rows
```
Options that affect collection or the initial view also apply to interactive mode. `--once` remains an explicit alias for the default one-shot behavior.
For a memory ranking, use `wsltop --sort memory`; for alphabetical JSON, use
`wsltop --json --sort name --sort-order asc`. TUI keys `c`, `m`, and `n` select
CPU, memory, and name; `r` reverses the current direction. Changing the key keeps
the current direction. The header displays both, and a key change immediately
requeries the latest observation, including candidates outside the previous limit.
Flat sorting ranks parents before applying `--limit`; their process children stay
attached and use the same sort before `--container-process-limit` is applied.
Tree sections keep their hierarchy and sort peers within each section, including
Windows applications and their member processes. Tree output keeps all collected
children; the flat limits do not truncate the attribution tree or its residuals.
Memory/name tree views include idle Windows applications and processes; the
default CPU tree retains its existing activity-focused Windows view.
CPU ties prefer memory; memory ties prefer CPU. Names use case-sensitive string
ordering. Remaining ties use name and resource identity in ascending order,
independently of direction. CPU and memory sorting use numeric observations, not
formatted strings or the selected CPU display scale. Windows working set, Linux
RSS, and container memory have different meanings: sorting does not make their
memory values additive or enable parent-minus-child memory accounting.
## Interactive TUI
Start the terminal UI with `wsltop --interactive` in WSL or `wsltop.exe --interactive` in Windows. It draws immediately and accepts partial collector updates instead of waiting for every source. The primary WSL collector reads local `/proc` in WSL or samples remotely through `wsl.exe` on Windows. After its first successful baseline it waits a fixed 150 ms warmup, then uses the configured interval. While Windows host discovery is pending, non-Windows collectors use the executing platform's visible CPU count and are marked provisional; WSL category CPU remains unavailable until the host count is confirmed. If the Windows-reported count differs, provisional rows are discarded and repopulated on the host-wide scale; delayed results carrying the old normalization count are ignored. `--wsl-only` uses WSL-visible logical CPUs in WSL-native execution and the Windows host logical CPU count in Windows-native execution. Windows collection runs independently; additional WSL distributions, WSLC, and Docker refresh on a slower cadence (at least two seconds), so a slow optional collector cannot serialize primary sampling. Windows primary selection may still require synchronous default/fallback discovery.
Additional distro discovery runs in its own worker and discovers newly started distributions during the session. Its initial baseline remains `loading` until a CPU delta is available, no additional distro is running, or an error is reported. Transient failures retain last-good rows; confirmed stopped distributions lose their rows and baseline. Distribution-name matching ignores ASCII casing.
The TUI retains each collector's last successful result. While collectors start, the footer reports `loading`; after a collector error its previous rows remain visible and the footer reports the error. Docker/WSLC aggregate rows are collected separately from internal process details. Details are enabled by default for text/TUI output and use a separate bounded queue and five-second command timeout, so slow process inspection does not block aggregate refreshes. Use `--hide-container-processes` to skip process rows in flat output.
Windows processes are ranked as application totals in human-readable flat/TUI output. Multi-process applications such as Teams, Chrome, and ChatGPT therefore occupy one top-level row. Tree output expands each application into its contributing PIDs. WebView2 ownership uses current parent-PID evidence plus CIM command-line/package metadata; ambiguous helpers remain under a conservative `WebView2` application instead of being assigned by guess. Metadata discovery runs independently in interactive mode and retains its last successful result across transient failures.
Controls:
| Key | Action |
| --- | --- |
| `q`, `Esc` | Quit |
| Up/Down, Page Up/Page Down | Scroll |
| `t` | Toggle flat/tree view |
| `c`, `m`, `n` | Sort by CPU, memory, or name |
| `r` | Reverse sort direction |
| `i` | Toggle infrastructure rows |
| `h` | Toggle raw WSL host rows in flat view |
| `0` | Toggle zero-CPU rows |
| `?` | Open/close summary help (arrows/Pg scroll; Esc closes help) |
Terminal raw mode, alternate-screen state, and cursor visibility are restored on normal exit and propagated errors.
### Compact resource summary
The TUI defaults to a two-line CPU/RAM summary. Windows, WSL, WSLC and Docker
labels use the same colors in the summary and resource list: blue, green, magenta
and cyan respectively. A neutral separator divides the summary from the resource
table; its length follows the wider of the summary and table headings/rule,
capped by terminal width. Long commands do not stretch it. The existing rule
below column headings remains. A single footer groups
view, sort, CPU scale and interval as `[flat cpu↓ core 3.0s]`, followed by option states and
key hints. Press `?` for metric definitions, detailed controls and collector status.
Host CPU and RAM have short history graphs alongside their totals when all four
environment labels fit. Left is older, right is now. Both use a fixed 0–100%
scale (RAM is physical memory in use / total), with 23 time slots at 120 columns
or wider and 15 slots at 80–119 columns. Each slot spans the configured refresh
interval: at the default 3 seconds these cover 69 and 45 seconds respectively.
Both graphs share a fixed clock and
shift left together once per interval. Until a new result arrives, the previous
value is held; these held values are not new measurements. Before the first result,
slots are blank. An explicitly failed or unavailable reading shows `!` until a
successful reading arrives; `▁` represents low/zero usage. Redraws and other
collectors do not advance the clock or append observations. The graphs are not
rescaled to exaggerate small changes. `TERM=dumb` uses ASCII levels (`_` through `#`)
with the same `!` failure marker.
The Windows collector includes query time in its refresh interval: a 0.8-second
query with a 3-second interval waits another 2.2 seconds. If collection takes longer
than the interval, the next query starts when it finishes, without overlapping
queries. The history holds the last value during that wait.
Below 80 columns, only CPU/RAM totals remain in the summary; history continues
to be recorded while hidden. Classic and WSL-only views do not show host graphs.
Totals are left-aligned in fixed-width fields, so both values and graphs start
at matching columns. Environment observations also have fixed columns, with three
spaces between blocks at 120+ columns and one on medium terminals, including while
values are unavailable or change digit count. Large memory
values use fewer decimals or larger binary units to keep columns stable. Very short
terminals reduce the summary to one line. The footer progressively omits hints,
preserving flat/tree, sort key/order and `q quit` whenever physically possible.
```text
CPU 23.4% ▄▃▂▂▂▂▄▃▂▂▂▂▄▃▂ | Win 5.0% WSL 10.0% WSLC 2.0% Docker 4.0%
RAM 12.3/32.0G ▃▃▃▄▄▄▃▃▃▄▄▄▃▃▃ | Win 3.20G WSL 2.10G WSLC 300M Docker 800M
```
These example values are **independent observations, not an additive breakdown**:
- `Win` sums observed Windows processes, excluding WSL/WSLC VM host rows.
- `WSL` CPU uses the shared WSL kernel's `/proc/stat` counters, sampled once through
the primary distribution. It includes short-lived processes and kernel work,
including other distributions even with `--wsl-only`. Container workloads in
that same kernel may also appear under `Docker` or `WSLC`.
- `WSLC` and `Docker` sum container statistics, excluding child process detail rows.
- Even without container overlap, Win process time and WSL guest-kernel time are
not an additive physical-CPU breakdown; see [CPU accounting](docs/cpu-accounting.md#wsl-category-cpu).
- Environment RAM values are Windows working sets, WSL RSS, and container CLI memory
statistics respectively. Shared pages and overlapping observations mean these
values must not be summed or subtracted from host physical RAM.
`RAM` is physical total minus available memory, collected using
[GlobalMemoryStatusEx](https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-globalmemorystatusex).
`K`, `M`, `G`, `T`, `P`, and `E` use powers of 1024. Missing, disabled, warming-up or incomplete
collectors display `N/A`; a successful empty collection displays zero. Host RAM can
appear before the first CPU interval. With `--wsl-only`, host totals are unavailable
and CPU observations use the WSL CPU scale, as explained in help.
Summary observations are independent of row filters, limits, sorting and `--cpu-scale`.
Use `--header classic` for the existing one-line header, or `--header compact`
for the new default. `--color auto|always|never` controls TUI colors; `auto` honors
nonempty `NO_COLOR` and disables colors with `TERM=dumb`, while `always` explicitly
overrides them. `--color never` retains all labels and numbers. These display options
do not change text or JSON output.
## CPU display and accounting
The TUI header shows `CPU` for the entire Windows host (all logical CPUs together = 100%), including WSL/container activity. It uses Windows system counter deltas, independently of row limits, filters, sorting, and `--cpu-scale`. It displays `N/A` during warmup, with `--wsl-only`, or when the counter is unavailable. This is busy CPU time, which can differ from Task Manager's frequency-adjusted utilization.
Text and TUI output default to the familiar Linux `top` convention where one fully busy logical CPU is 100%; multi-threaded workloads can exceed 100%. Use `--cpu-scale host` for the Task Manager-style whole-host display where all Windows host logical CPUs together equal 100%.
Internally, every CPU percentage remains on the common host-wide denominator. Display scaling is applied only while rendering, so sorting, attribution, residual accounting, and JSON values do not change.
Windows and WSL process percentages come from deltas of cumulative processor time. WSLC and Docker percentages are normalized from their collector-specific values onto the same host scale. See [CPU accounting](docs/cpu-accounting.md) for formulas and caveats.
`TIME+` is cumulative CPU time consumed, formatted as unbounded minutes, seconds, and hundredths (`MM:SS.hh`), as in Linux `top`; it is not wall-clock process age. Windows application TIME+ is the sum of its currently observed member processes. Process rows report TIME+ where their collector exposes it. Container totals and residual rows show `-` because wsltop does not infer cumulative time from point-in-time container percentages.
## Attribution tree
Use `--tree` to treat `vmmem`, `vmmemWSL`, and `vmmemwslc-*` as parent resources:
```text
Host logical CPUs: 16 | CPU scale: 1 core = 100%
WSL VM 131.20%
|- infra plan9 43.20%
|- process codex 3.20%
`- unattributed 84.80%
```
The remainder is clamped at zero:
```text
unattributed = max(host CPU - known child CPU, 0)
```
Children are never proportionally scaled to fit a parent. If independently timed samples make children exceed the host value, the internal tree records sampling skew. Memory is displayed as resource data only; host-minus-child memory attribution is not performed.
Raw WSL host processes stay hidden in default flat output to avoid accidental double-counting. `--show-wsl-host` exposes them for diagnostics; tree mode always collects them for use as parents.
## Docker / WSLC behavior
WSLC collection uses the current/default CLI session. A single available `vmmemwslc-*` host can be associated with its containers. If multiple hosts make the mapping ambiguous, `wsltop` reports the mapping as unresolved and does not guess; flat WSLC rows remain available.
Docker collection is optional. Container CPU and memory come from Docker statistics. For each container, `docker top <id> -eo pid,ppid,pcpu,rss,time,comm,args` independently discovers processes in the Docker daemon's PID namespace. Process `%CPU` is divided by the Windows host logical CPU count and processes are nested under their container. `unattributed` and `over_attributed` residuals are calculated without scaling process values to fit the container. If the process backend does not support `time`, wsltop retries the older column set and leaves TIME+ unavailable instead of dropping the container detail.
Docker Desktop's WSL 2 backend shares the WSL kernel, so its CPU is already included
in the WSL category total. A separate Hyper-V backend does not share that kernel.
Kernel sharing does not establish a shared PID namespace or a verified attribution
parent: Docker stays a top-level group unless host/PID mapping is proven.
See [Docker's WSL backend documentation](https://docs.docker.com/desktop/features/wsl/).
Text/TUI output includes Docker and WSLC process rows by default while preserving each container row; use `--hide-container-processes` to suppress them (`--show-docker-processes` remains a compatibility alias). Flat ranking and `--limit` treat each container as the top-level resource; its processes and residual are displayed directly beneath it and are not independently ranked or counted toward the limit. Each container shows its top five processes by default; `--container-process-limit` changes that cap and omitted processes are summarized by count and combined CPU (`--docker-process-limit` remains an alias).
A missing `wslc.exe`, missing Docker CLI, or recognized unavailable Docker daemon is treated as an expected absence: its rows are silently omitted and monitoring continues. Unexpected command, output, parse, or per-container attribution failures are reported through the common warning path. Use `--no-wslc` or `--no-docker` to disable a collector intentionally.
## Windows application grouping
Application CPU is exactly the sum of observed member-process CPU; child PIDs explain the application total and must not be added to it. Ordinary processes fall back to conservative executable-name grouping. A WebView2 process joins another application only when command-line, package, or a currently matching parent process provides unambiguous evidence.
## Multiple WSL distributions
When wsltop runs inside WSL, the current distribution is sampled directly from `/proc`. Other running distributions are discovered with `wsl.exe --list --running --quiet`, sampled through `wsl.exe -d`, and labelled with their distribution name. These additional remote samples are best-effort and introduce more timing skew than direct `/proc` access.
When `wsltop.exe` runs on Windows, primary selection uses `--distro NAME`, then
the WSL default, then the first running distribution. The selected primary may
be started by `wsl.exe`; additional distributions are collected only while
running. All are sampled remotely through `wsl.exe`. Primary failure aborts a
one-shot sample; the TUI reports sampling errors and retries while retaining
last-good data. Failure to select a primary prevents streaming startup.
Additional distributions remain best-effort. JSON omits `source` for the primary
and includes the distro name for additional sources.
`--wsl-only` limits WSL distribution collection to the primary distribution and disables Windows and WSLC collection. Optional Docker collection remains enabled unless `--no-docker` is also passed. In WSL-native execution it uses the WSL-visible logical CPU count and warns that exact Windows-host normalization is unavailable. In Windows-native execution it uses the Windows logical CPU count but still disables Windows host-process attribution.
## JSON output
`--json` preserves the PID-level flat resource-array schema and host-wide CPU values. Windows application rows are a human-readable view and do not replace existing Windows PID objects in flat JSON. Each resource includes fields such as `environment`, `kind`, identity, CPU percentage, and memory bytes. When known, cumulative CPU time is added as `cpu_time_seconds`; the field is omitted when unavailable. `--cpu-scale core` is rejected with JSON because display scaling does not alter machine-readable values; omitted scale or `--cpu-scale host` is accepted.
```console
wsltop --once --json
```
`--tree --json` emits a structured object containing `host_logical_cpu_count`, attribution groups, additive Windows application groups, Docker subgroups, unmapped children, `unattributed_cpu_percent`, and sampling-skew information.
JSON is a one-shot interface; `--interactive --json` is rejected explicitly.
<a id="mcp"></a>
## Use wsltop from AI agents
With v0.5.1 or later, your MCP client can launch `wsltop mcp` as a local stdio
server. It is **read-only and observability-only**: no shell or arbitrary command
execution tools, process kill/termination, container stop/control, or network listener.
The four tools are `get_system_summary`, `list_resources`, `inspect_resource`,
and `list_children`.
For a client running inside WSL2, add this stdio server configuration (replace
`<user>` with your username). Run `command -v wsltop` to check the absolute path:
```json
{
"mcpServers": {
"wsltop": {
"command": "/home/<user>/.cargo/bin/wsltop",
"args": ["mcp"]
}
}
}
```
For a Windows client, use the absolute path to `wsltop.exe` as `command`, with
the same `["mcp"]` args. Configuration formats vary by client; see
[MCP quick start and snapshot semantics](docs/mcp.md#quick-start).
Once connected, ask your agent:
- “What is using the most CPU on my machine?”
- “Which Windows, WSL, Docker, or WSLC workload uses the most memory?”
- “Inspect the busiest resource and explain its immediate children.”
- “Compare current Windows, WSL, Docker, and WSLC usage.”
### Agent example: a slow build
An actual Codex session used this prompt:
> My build is running slowly.
> Use wsltop MCP to identify the likely bottleneck and explain which environment,
> container, and process are responsible.
The agent called `get_system_summary`, `list_resources`, `inspect_resource`, and
`list_children`, plus a container-filtered listing, keeping the **same snapshot
while drilling down**. wsltop MCP distinguishes Windows, WSL, Docker, and WSLC
observations. The result, summarized for readability:
> The current load is mainly from WSL. The busiest process, `gw_sh` in Ubuntu,
> is using about one CPU core. No Docker or WSLC container is observed as
> responsible in this snapshot. Overall host CPU usage is moderate, so if this
> process is the build, limited parallelism is a likely bottleneck; host CPU
> is not saturated.
Parent/child and environment values may overlap and **must not be summed**.
This is a likely diagnosis: agents should not infer paging, disk-I/O stalls,
memory-pressure causality, container membership, or the exact build task without
supporting observations; see the [observation limits](docs/mcp.md#memory-and-causal-limits).
See the [agent workflow](docs/mcp.md#agent-workflow-example) and
[manual agent evaluation guide](docs/mcp-agent-evaluation.md).
## Limitations
- WSL1 is unsupported and untested; use WSL2.
- Additional-distro startup/stop discovery is best effort. Docker/WSLC process detail may lag aggregate rows because collection runs at independent cadences.
- Sampling is best effort. Linux, PowerShell, WSLC, Docker, and remote-distro snapshots are not captured atomically.
- PowerShell process collection adds latency, but runs independently of other interactive collectors; the Windows logical CPU count is cached after its first successful query.
- WSLC session attribution is deliberately conservative when multiple host mappings are possible.
- Docker process `%CPU` from `docker top` is a ps-style lifetime/decay average and may not align precisely with interval-sampled container or `/proc` CPU.
- Memory values from Windows, WSLC, and Docker have different meanings and are not attributed by subtraction.
- Both executables require access to a WSL2 distribution; Windows-only monitoring without WSL is not supported.
- Remote WSL discovery and snapshots can wait on `wsl.exe`. Optional TUI discovery runs separately from primary sampling, but primary selection/collection can still be delayed.
## Documentation
- [Documentation index](docs/README.md)
- [Architecture](docs/architecture.md)
- [CPU accounting](docs/cpu-accounting.md)
- [Validation and test plan](docs/test-plan.md)
- [Changelog](CHANGELOG.md)
- [Contributing](CONTRIBUTING.md)
- [Security policy](SECURITY.md)
## Development
```console
cargo fmt --all -- --check
cargo test --locked --all-targets
cargo clippy --locked --all-targets --all-features -- -D warnings
cargo build --release --locked
cargo package --locked
```
CI runs build, lint, test, and help/version checks on Ubuntu, a Windows GNU cross-target check, and native Windows tests and release-build checks. Tagged releases package Linux x86_64 archives and Windows x86_64 ZIPs with SHA-256 checksums. Windows/WSL interoperability, WSLC, Docker, attribution accuracy, and terminal recovery require real-host validation; see the test plan.
## License
Licensed under the [MIT License](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessWithin a week