The lab went from one Windows guest to five and there was no way to see what they were all doing without issuing a QEMU screendump per guest by hand. - tools/vmwatch.py: always-on HTTP service serving an auto-refreshing wall of live guest screens. Runs on spicy as vmwatch.service; browse it at http://192.168.3.201:8140/. Click a tile for that guest full size. Guests are discovered from /etc/pve/qemu-server by matching sots-re, so clones appear and vanish on their own. A stopped, paused or unreachable guest gets a labelled placeholder tile carrying the monitor's own error, never a broken image or a 500. Python 3 stdlib only. - tools/vmwatch-install.sh: install/update/uninstall the unit on the host. - tools/vmshot.py: one-shot contact sheet, and --one <id> for a full-size grab. Pulls frames from the vmwatch service when it is up (0.5s) and falls back to ssh + qm monitor when it is not (4s). - guides/lab-screen-wall.md: how to use both, and why. Capture goes over each guest's QMP socket rather than forking qm: qm is a Perl program, and one fork per guest per tick cost ~90% of a host core and a 728 MB cgroup peak. Direct QMP is 0.33 CPU-seconds per 88s and 23 MB RSS. QEMU 11 here dumps PNG natively; the fallback PPM encoder was verified pixel-identical to QEMU's own on a real framebuffer. Read-only throughout: screendump does not perturb the guest (method-rule 19), so reading VM 140's screen is not an experiment and does not take its lock.
3 KiB
Watching the lab guests
The lab is five Windows guests now. This is how to see all of them at once.
The live wall
Leave the tab open. Tiles refresh every 5 s; click a tile for that guest full size.
Each tile carries the VM id, name, status and the capture timestamp. A guest that is
stopped, paused or unreachable shows a labelled NO SIGNAL tile with the monitor's
own error text, never a broken image.
Served by vmwatch.service on spicy (/opt/vmwatch/vmwatch.py, systemd, enabled
at boot). Source of truth is tools/vmwatch.py in this repo; redeploy with:
tools/vmwatch-install.sh spicy # install or update
tools/vmwatch-install.sh spicy --uninstall
Nothing was installed on spicy beyond that file and its unit — the service is Python 3 standard library only (spicy has no ImageMagick, no netpbm, no Pillow).
The one-shot contact sheet
tools/vmshot.py # all guests -> dumps/vmshot/sheet-<ts>.png + latest.png
tools/vmshot.py --one 140 # one guest, full size
tools/vmshot.py 144 145 --cols 2
tools/vmshot.py --ssh # bypass the service, capture over SSH
vmshot pulls frames from the vmwatch service when it is reachable (~0.5 s, no extra
load on the guests) and falls back to SSH + qm monitor when it is not (~4 s). Output
lands in dumps/vmshot/, which is gitignored.
Why this mechanism
Both tools capture with QEMU screendump, reached over each guest's
/var/run/qemu-server/<id>.qmp socket — the same socket and command qm monitor uses.
- It needs no guest agent, no guest network and nothing installed in the guest.
- It does not perturb the guest. That is the point (method-rule 19: an instrument that perturbs the thing it measures has already cost this campaign once). Reading VM 140's screen is therefore not an experiment and does not take 140's exclusivity lock.
- The campaign board already records that
qm monitorscreendump is more reliable than the in-guest click helper'sshot.
Two things worth knowing
Do not fork qm in a loop. The first version of vmwatch shelled out to
qm monitor once per guest per 5 s tick. qm is a Perl program: that cost ~90 % of
a host core and a 728 MB cgroup peak. Talking to the QMP socket directly — same
socket, same command, no fork — brought it to 0.33 CPU-seconds per 88 s and 23 MB
RSS, roughly a 300× reduction. The fleet list comes from /etc/pve/qemu-server/*.conf
plus query-status for the same reason. qm remains the fallback path only.
QEMU 11 on spicy dumps PNG natively (screendump <file> -f png), so no PPM
conversion is needed. vmwatch.ppm_to_png exists as a fallback for an older QEMU and
was checked against QEMU's own encoder on a real 1024×768 framebuffer: pixel-identical.
Guests are discovered dynamically by matching sots-re in the guest name, so clones
added or destroyed later appear and vanish on their own — nothing to edit.
Both tools are strictly read-only. Neither starts, stops, resets nor reconfigures a VM.