Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ProcessManager

License Language

CI Last Commit Activity

Stars Forks Issues Code Size Repo Size

Release Nightly Downloads

A cross-platform process manager in C#: a Process-Explorer-shaped desktop UI and an htop-shaped terminal UI over one sampling engine. The desktop UI is built on NativeForms, so the same binary puts real Win32 windows on Windows and real GTK windows on Linux without a second UI codebase.

Important

Working, with the gaps written down. Linux is complete: engine, probe, both front-ends, the six detail views, and the privileged helper. Windows is complete bar the environment block, and has been executed and checked against a kernel under Wine — 21 of 21 self-test checks, 47 modules, 95 handles — but not yet on a genuine Windows machine. macOS throws by design. Three desktop features (row colours, click-to-sort, in-cell sparklines) are blocked on one missing hook in the UI toolkit. docs/PRD.md tracks all of it box by box; §12 is the per-feature coverage matrix and §10 the milestone state.

📸 What it looks like

The desktop UI

Rows are coloured by what kind of process they are, and three of the columns are drawn rather than written — CPU, memory and I/O history, one pixel per sample, sharing one scale so the rows can be compared with each other. The plots read as instruments rather than as part of the desktop: black ground, green graticule, filled area.

The terminal UI

The same three histories in the terminal, drawn with the eighth-block characters (▁▂▃▄▅▆▇█) — and an ASCII ramp on a terminal whose locale is not UTF-8, because a column of replacement boxes is worse than no plot at all.

Both are regenerated by tools/shoot-screenshots.sh, which gives the machine some real work first so the plots have a shape. The window is photographed from inside the process — a runner has no compositor to grab pixels from — and the terminal is written as SVG, which needs no font, weighs nothing, and shows which line changed in a pull request.

✨ What it is

Three things that are usually three separate programs:

  • A process explorer — the process tree with per-process CPU, memory, I/O, handles and threads; the detail views (threads, modules, open handles, environment, network endpoints); and the search that answers "which process has this file open?".
  • A task manager — end a process or its tree, suspend and resume it, change priority and CPU affinity, and see at a glance what is eating the machine.
  • A terminal monitor — the same data as a full-screen console UI over SSH, where no display exists and installing a desktop toolkit is not an option.

All three read from one sampling engine (ProcessManager.Core) behind one platform probe interface. A metric is implemented once, and both front-ends get it; a front-end has no privilege to read anything the other cannot.

🧩 Architecture

ProcessManager.Core                Sampling engine: snapshots, deltas, rates, history, tree building,
                                   sort/filter. No platform code, no UI, no I/O beyond the probe.
ProcessManager.Platform.Linux      /proc, /sys, cgroup v2, netlink                       — shipping
ProcessManager.Platform.Windows    NtQuerySystemInformation, ToolHelp32, PDH             — shipping
ProcessManager.Platform.MacOS      libproc / sysctl                                      — stub, throws
ProcessManager.Ui.Terminal         Terminal renderer, no toolkit dependency
ProcessManager.Ui.Desktop          Desktop UI on NativeForms
ProcessManager.App                 The one binary: CLI plus both front-ends           — procman
ProcessManager.Elevated            procman-helper: the only component that ever runs as root/admin

One executable carries both front-ends, so procman and procman --tui are the same program. A headless machine still never loads GTK: a NativeForms backend that is not registered is never asked for its native library.

Core never calls a native API; it asks an ISystemProbe for a SystemSnapshot and does the arithmetic. That is what makes the whole engine testable against recorded /proc trees and captured Windows structures rather than against the machine running the tests — see PRD §9.

Platform support: Windows and Linux. macOS is a stated future direction, not a shipped feature — the macOS probe is a stub whose every member throws PlatformNotSupportedException with an actionable message.

🚀 Usage

procman                       # desktop UI (Win32 on Windows, GTK on Linux)
procman --tui                 # full-screen terminal UI
procman --tui --sort=cpu      # start sorted by CPU, tree mode off
procman --list --json         # one snapshot to stdout as JSON, then exit
procman --find "libssl"       # which processes have a handle/mapping matching this?
procman --kill 1234 --tree    # end a process and its descendants

procman --flat                # start as a sorted list rather than a tree
procman --self-test           # ask the probe about itself; the runtime checks its answer
procman --helper-check        # talk to the privileged helper over its pipe, unelevated
procman --no-helper           # never start the helper, even for an action that needs it

The terminal UI keeps the keys htop users already have in their fingers — F5 tree, F6 sort, F9 kill, F10 quit, / search, \ filter, u filter by user — plus Enter for a process's details and h to read handle counts for the visible rows. The desktop UI keeps the layout Process Explorer users already have in their eyes: plots and per-core meters on top, the process tree below them, and a tabbed detail pane under that — overview, threads, modules, handles, environment, network. Click a header to sort by it, click it again to reverse; View → Select columns chooses from sixteen, and View → Colour legend says what every row colour means.

The colours

Green started since the last refresh
Red ended since the last refresh
Pale yellow yours
Blue the system's (root / SYSTEM)
Teal a service
Grey suspended
Orange a zombie — exited, not yet reaped

Not distinguished: packed, .NET, elevated and store processes. Telling those apart needs information neither probe collects, and a colour that is sometimes right is worse than none.

📊 What it shows

Area Contents
Process tree Name, PID, PPID, user, state, CPU %, private bytes, working set / RSS, virtual size, I/O read + write rates, handle / file-descriptor count, thread count, start time, session, priority, command line, working directory
Per-process details Threads (TID, state, CPU, start address) · modules and mappings (path, base, size, permissions) · handles and open files (type, name, access) · environment block · TCP/UDP endpoints · memory regions
System overview Per-core CPU history, load average, memory and swap with cache breakdown, I/O throughput, network per-interface throughput, disk per-device throughput, uptime, context switches, interrupts
Search One query across process names, command lines, open files, mapped modules and listening ports — the "who is holding this file" question, answered in one place

Rows are coloured the way Process Explorer colours them — new green, exited red — in the terminal UI. The window does not colour them yet; see the limitations below for why.

🔐 Privileged operations

Most of what this program shows needs no privileges at all. A few things do: reading another user's command line and open files, ending another user's process, and per-process network capture.

ProcessManager stays unprivileged and starts a small separate helper (procman-helper) only when an action needs one — polkit on Linux; Windows is not implemented, because it cannot both elevate a child and redirect its standard handles in one call. The helper accepts a fixed set of typed requests over a private pipe, checks each one against an allowlist, and exits with the program. It does not evaluate anything it receives, and it never runs the UI. When the helper is not available the affected columns and actions are disabled with the reason shown, rather than the whole program refusing to start.

See PRD §8 for the protocol and its threat model.

🛠️ Build

ProcessManager builds against the NativeForms sibling repository. Clone it next to this one:

work/
├─ ProcessManager/   # this repo
└─ NativeForms/      # https://github.com/Hawkynt/NativeForms

The process list needs TreeListView's row-colour, cell-paint and column-click seams, which are on NativeForms' main but not yet in a published package. When one ships, this goes back to three PackageReferences and the sibling clone stops being necessary; the build fails with one sentence saying so if the sibling is missing.

dotnet build ProcessManager.slnx -c Release
dotnet test  ProcessManager.slnx -c Release
dotnet run --project ProcessManager.App -- --tui              # terminal UI
dotnet run --project ProcessManager.App                       # desktop UI; needs GTK 3 on Linux
dotnet run --project ProcessManager.Benchmarks                # the PRD §4 budget harness
./tools/shoot-screenshots.sh                                  # regenerate docs/screenshots

# Everything replays against a recorded /proc tree, on any OS:
dotnet run --project ProcessManager.App -- --list --tree \
  --probe-root ProcessManager.Tests/Fixtures/proc-desktop

Publishing produces a single self-contained binary per platform, NativeAOT where the platform allows it. Trim and AOT warnings are build errors — see PRD §4 for the footprint budget the CI enforces.

CI

GitHub Actions, same four-workflow layout as the other repos here:

Workflow Trigger Does
ci.yml push / PR Build + test on Linux, Windows and macOS; a NativeAOT publish per RID with trim warnings as errors; a headless run of both front-ends against recorded fixtures
_build.yml called The shared publish block — NativeAOT self-contained binaries, one runner per RID (AOT cannot cross-compile), so release and nightly can never diverge
nightly.yml after green CI on main Nightly prerelease + the sampling benchmark harness, GFS-pruned to 7 daily / 4 weekly / 3 monthly
screenshots.yml manual, and at release Re-photographs both front-ends and commits the pictures when they changed. Not on every push: a capture of a live machine differs every run, so that would be a bot commit per push
release.yml manual dispatch CI, build, changelog, and a dated vyyyyMMdd GitHub Release

Versions are never taken from a tag: .github/workflows/scripts/version.pl --stamp rewrites each project's own <Version>X.Y.Z</Version> to X.Y.Z.<commit count of that folder> at build time.

Inspiration

  • Process Explorer — the tree, the handle search, the color legend
  • System Informer / Process Hacker — the privilege split and the depth of the detail views
  • htop — the terminal layout and its keybindings
  • btop — the system graphs
  • Windows Task Manager — the "what is wrong right now" first screen

Known limitations

These are consequences of the design, not a to-do list; the to-do list is the PRD.

  • macOS does not work. The probe is a stub whose every member throws. Nothing samples, nothing renders.
  • Elevation is Linux-only. The helper, its framed protocol and its polkit policy work and are tested; Windows elevation needs a named pipe the elevated child connects back to, which is not written. See packaging/.
  • macOS is the only platform with no probe at all. Windows and Linux are both verified against their own kernels on every push by procman --self-test, which asks the probe about the process it is running in and has the runtime check every answer.
  • Sampling costs a third more than the budget says. 33 ms of CPU per 1000 processes against a target of 25 — three files are read per process, and syscalls are the entire cost. Closing it means dropping status and with it the private-memory column and the owner id, which is a worse trade than three milliseconds. Measured and written down in PRD §4 rather than left as a number nobody intends to meet.
  • Per-process property windows are not implemented. The detail pane shows one process at a time; Process Explorer opens several at once, which is what makes it good at comparing two of them.
  • Nothing is persisted. Column choice, sort and window size are back to their defaults on every start.
  • Per-process network capture needs the helper. Linux attributes sockets to processes through /proc/net plus inode matching, which is unprivileged but coarse; anything finer needs root.
  • No kernel driver, ever. Everything Process Explorer does through its driver — real thread stacks with symbols, kernel object inspection, protected-process access — is out of reach here and stated as a non-goal in the PRD, rather than promised and quietly missing.
  • Sampled, not traced. Rates come from differencing counters at an interval. A process that lives and dies inside one interval is a gap in the data, and the UI says so instead of drawing a zero.

❤️ Support

If ProcessManager is useful to you, consider supporting development:

GitHub Sponsors PayPal

📜 License

Licensed under LGPL-3.0-or-later — see LICENSE.

About

Cross-platform process manager in C#: a Process Explorer-style desktop UI and an htop-style terminal UI over one sampling engine. Windows and Linux, NativeAOT, zero-allocation /proc and NtQuerySystemInformation probes.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages