ref:68c58c9b5f46f4f80ded46d11d44fd3bed104d52

docs(journal): record desktop latency, 4K diagnosis, real libvirt and systemd findings

Refs #12 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BPNw4PCgkEfhyCjQT19wsb
SHA: 68c58c9b5f46f4f80ded46d11d44fd3bed104d52
Author: Cole Christensen <cole.christensen@gmail.com>
Date: 2026-09-13 10:51
Parents: 3457767
1 files changed +67 -0
Type
JOURNAL.md +67 −0
@@ -610,3 +610,70 @@
- Not verified here: the Vulkan and EGL DMABUF paths on a GPU, libvirt (URI default, XDG autospawn), units under a
real systemd, Avahi with non-ASCII or dotted names, the raw-QEMU bus policy and socket permissions.
## 2026-09-13 — hardening without a Moonlight client (phase-1 branch)
### Review of the Phase 1 code (two read-only reviewers)
- No changes lost to the copy-based edits; all 18 earlier fixes are present.
- Fixed:
- mDNS changed the advertised name for every Linux user, not just `capture = qemu` (REQ-CMP-001).
- The backend option was ON for FreeBSD, which lacks the Linux-only headers and /proc.
- A QMP `connect()` had no timeout and ran under `shared_session`'s process-wide lock: a monitor held by
another client hung capture, audio and shutdown.
- A failed DMABUF descriptor duplicate spun the capture loop.
- Pen/touch went to every VM's MultiTouch interface. QEMU exports it even without a multi-touch device, so
touches were lost; Sunshine now finds the device on QEMU's command line or in libvirt's XML and sends mouse
input otherwise.
- Still open (minor):
- Capture's full-frame `memcpy` runs under the frame store lock, so the listener thread waits during it.
- Input console proxies are created with blocking calls on the input thread.
- The input queue is unbounded while QEMU is stopped (SIGSTOP).
- Guest volume changes made while no client streams aren't seen.
### Real desktop guest (tests/e2e/qemu/desktop)
Ubuntu 24.04 cloud image + XFCE autologin + PipeWire, built with cloud-init; streamed with the release NVENC build
(RTX 4090, WSL2), 60 fps streams at the guest's native size, 600 frames:
| Guest | Workload | Transport | fps | p50 | p95 |
|-----------|----------------------|------------|------|---------|---------|
| 1920x1080 | full-screen video | shared map | ~53 | 12.6 ms | 22.2 ms |
| 1920x1080 | full-screen video | messages (QEMU 8.2) | ~51 | 14.9 ms | 24.0 ms |
| 2560x1440 | full-screen video | shared map | ~45 | 21.2 ms | 30.4 ms |
| 3840x2160 | full-screen video | shared map | ~22 | 33.6 ms | 50.3 ms |
- **The idle desktop isn't measured.** Those runs recorded 1-2 latency samples (an idle desktop barely damages),
so their numbers mean nothing. A typing or cursor workload is needed.
- **Full-motion 1080p misses the ≤ 12 ms p95 target** that the pattern guest met (4-5 ms). Every frame is a
full-frame damage: two 8 MB copies (map → store → image) plus the RAM→CUDA upload.
- **4K is limited before the encoder.**
- It's the same ~22-24 fps at a 1080p stream and with software encoding.
- Alone, the guest's mpv renders ~55 fps.
- perf: Sunshine's threads aren't saturated (listener copy 25%, capture copy 19%, CUDA upload 34% of a core),
while the guest's vCPUs run 70-90%, mostly in KVM guest memory reads. The likely limit is the guest
pushing 33 MB frames through virtio-gpu 2D transfers.
- The GL/DMABUF path (#3) is the fix for motion at 4K. Removing one of Sunshine's two copies is still worthwhile.
### Real libvirt 10.0 and systemd 255 (Ubuntu 24.04), no GUI
- **Session libvirt, bus mode** (QEMU 11.1): `qemu_dbus_address = libvirt:<domain>?uri=qemu:///session` streams.
- **System libvirt, `p2p='yes'`** (QEMU 8.2), Sunshine as an unprivileged member of the libvirt group: the
handshake failed with "Exhausted all available authentication mechanisms".
- QEMU authenticates p2p D-Bus clients with EXTERNAL, which compares the claimed uid with the socket's peer
credentials.
- `virDomainOpenGraphicsFD` makes libvirtd create the pair, so those credentials are libvirtd's.
- Fixed by creating the pair in Sunshine and calling `virDomainOpenGraphics`.
- **The sunshine-qemu@ unit didn't start for any VM name containing '-' or a space.**
- The cause is the escaped instance name in `StateDirectory=sunshine-qemu/%i`: systemd 255's executor fails
with "Failed to deserialize: Invalid argument" before running anything, and `systemd-analyze verify`
doesn't notice.
- Bisected to `StateDirectory=` alone. The units now use the shared parent and create
`/var/lib/sunshine-qemu/<vm>` (`%I`) in `ExecStartPre`.
- **End to end:** defining and starting a system domain makes the hook start `sunshine-qemu@<vm>`, which runs as
sunshine-qemu in its sandbox, waits for the domain, opens the p2p display and streams to the headless client.
Destroying the domain stops the unit.
- libvirt 10.0 rejects `<input type='touch'>` (newer libvirt only).
- Not done: the virt-manager click-through.
### CI (#9)
- `.anvil.yml` plus `scripts/ci/anvil-test.sh`.
- The only online x86_64 runner is this WSL2 machine (the `anvil runner` service). Container steps get no
devices, so the KVM end-to-end step runs `image: bare` on the host.