sots-re/guides/lab-screen-wall.md
alex 962cdb6981 tools: live screen wall + contact sheet for the five lab guests
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.
2026-09-08 16:53:04 -04:00

3 KiB
Raw Permalink Blame History

Watching the lab guests

The lab is five Windows guests now. This is how to see all of them at once.

The live wall

http://192.168.3.201:8140/

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 monitor screendump is more reliable than the in-guest click helper's shot.

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.