Files
vfio-native/scripts/libvirt-hook-cpu-isolation.sh

84 lines
2.9 KiB
Bash
Executable File

#!/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 <cputune> 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