| name | fleet-monitor |
| description | Agent context for fleet-monitor — a live RAM/CPU/process monitor for remote hosts built with machin/MFL and remotecmd-cli. |
fleet-monitor
Live fleet dashboard: RAM, CPU, and top processes for each remote host, served as a single native HTTP binary.
Stack
- Language: MFL (machin), compiled to a native static binary
- Web framework: machweb (embedded HTTP server)
- Remote execution: remotecmd-cli (
remotecmd-cli exec ... / rcl --json)
- Per-host commands:
free -h, ps aux --sort=-%mem, top -bn1 -c -o %CPU
Project layout
fleet-monitor/
├── app.src # main() and HTTP handler
├── parse.src # target discovery, parallel remote queries, output parsers
├── render.src # HTML/CSS rendering
├── types.src # structs
├── build.sh # encode .src into .mfl and build the binary
├── README.md # public usage docs
└── .env.example # example TARGETS
Build
Requires machin on PATH.
./build.sh
This runs:
machin encode framework/machweb.src types.src parse.src render.src app.src > app.mfl
machin build app.mfl -o fleet-monitor
Run
With remotecmd-cli configured:
./fleet-monitor
Without remotecmd-cli, set TARGETS:
TARGETS=web-01,web-02,db-01 ./fleet-monitor
Open http://localhost:48099. Tabs:
/ or /?tab=both — summary view (RAM%, CPU%, swap%, top processes)
/?tab=ram — RAM-sorted summary + per-VM top RAM processes
/?tab=cpu — CPU-sorted summary + per-VM top CPU processes
How it works
discover_targets() tries rcl --json --no-health; if that fails it reads TARGETS.
collect_vms() spawns one goroutine per host, each running query_vm().
query_vm() runs a single remotecmd-cli exec that returns free, ps (RAM), and top (CPU) output.
- Parsers turn the raw shell output into
VMData and ProcRow structs.
render_page() builds a tabbed HTML dashboard.
Key implementation notes
- CPU process ranking uses
top -bn1 (live snapshot) rather than ps aux average-CPU, so the reported top CPU process is the real current consumer.
top and ps self-references are filtered out of the CPU process list.
- Target names are not hardcoded; discovery is via
rcl or the TARGETS environment variable.
- The source is split into
types.src, parse.src, render.src, and app.src to stay under 500 LOC per file.
Common changes
- Adjust port: change
48099 in app.src.
- Change number of top processes shown: change
head -20 / head -11 in parse.src query_vm.
- Adjust color thresholds: change
pct_class / pct_color in parse.src.
Known gotchas
machin 0.115.0+ is stricter about channel ownership; query_vm() now sends vm exactly once at the end to avoid RACE004.
- Keep
env("TARGETS") parsing in a separate helper (targets_from_env()); placing env/split in the same function as parse(out, RclResponse{}) triggered a type-mismatch in 0.115.0.