#!/bin/bash # libvirt qemu hook: keep host processes off the cores a pinned guest is using. # # Install as /etc/libvirt/hooks/qemu (or drop into /etc/libvirt/hooks/qemu.d/). # # On start it reads the domain's own from the XML libvirt passes on # stdin, works out which host CPUs the guest occupies, and confines the systemd # slices to the rest. On stop it hands everything back. # # Measured on a 16 vCPU guest pinned to one CCD of a 7950X: stalls over 10 us in # a 10 second window fell from ~100 to ~52, and CPU throughput rose about 2%. # # Deliberately exits 0 on every path. libvirt treats a non-zero hook exit during # prepare/start as fatal, and a tuning helper must never stop a VM booting. DOMAIN="$1" OPERATION="$2" ALL=$(cat /sys/devices/system/cpu/present) # not nproc: that honours this hook's own affinity NCPU=$(( ${ALL##*-} + 1 )) SLICES="system.slice user.slice init.scope" restore() { for s in $SLICES; do systemctl set-property --runtime -- "$s" AllowedCPUs="$ALL" 2>/dev/null done } case "$OPERATION" in prepare) XML=$(cat) # libvirt feeds the domain XML on stdin # every cpuset the guest pins itself to: vcpupin, emulatorpin, iothreadpin GUEST=$(printf '%s' "$XML" | grep -oE "cpuset='[0-9,-]+'" | sed "s/cpuset='//;s/'//" | tr ',' '\n' | sort -u | tr '\n' ',' | sed 's/,$//') [ -n "$GUEST" ] || exit 0 # not a pinned domain, nothing to do HOST=$(python3 - "$GUEST" "$NCPU" <<'PY' 2>/dev/null import sys def expand(spec): out = set() for part in spec.split(','): if not part: continue if '-' in part: a, b = part.split('-'); out.update(range(int(a), int(b) + 1)) else: out.add(int(part)) return out guest, n = expand(sys.argv[1]), int(sys.argv[2]) rest = sorted(set(range(n)) - guest) if not rest: sys.exit(1) # guest wants everything; leave the host alone # collapse back into ranges out, start, prev = [], rest[0], rest[0] for c in rest[1:] + [None]: if c == prev + 1: prev = c; continue out.append(str(start) if start == prev else "%d-%d" % (start, prev)) if c is None: break start = prev = c print(",".join(out)) PY ) [ -n "$HOST" ] || exit 0 # nothing left for the host, or it failed for s in $SLICES; do systemctl set-property --runtime -- "$s" AllowedCPUs="$HOST" 2>/dev/null done logger -t libvirt-cpu-isolation "$DOMAIN starting: host slices confined to $HOST (guest has $GUEST)" ;; release|stopped) restore logger -t libvirt-cpu-isolation "$DOMAIN $OPERATION: host slices restored to $ALL" ;; *) # Deliberately does nothing. An earlier version called virsh here to check # whether any domain was still running - that deadlocks libvirtd, because # libvirt is blocked waiting for this hook to return while the hook waits on # libvirt. Never call virsh from a libvirt hook. ;; esac exit 0