84 lines
2.9 KiB
Bash
Executable File
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
|