using virsh and systemd to automatically size ballooning device of kvm guest
on my primary machine that is currently using fedora i have a windows guest for some testing stuff that at some point needs more ram to do some tasks. to support that scenario i added a ballooning device and installed the qemu / virtio guest tools. at this point i’m able to manually adjust the memory allocation of the vm using the balloning driver using virsh:
sudo virsh setmem "win" 4G
now it would be really useful if this could be done automatically. i searched for existing solutions and found a kvm project that was abandoned 2013 …
so we need a script, here is my first rough approach, installed as a systemd service:
#!/bin/bash
# can be intsalled as a systemd service via
# sudo systemctl edit --full --force virshautomem
#
# [Unit]
# Description=virsh auto memory allocation for ballooning vms
#
# [Service]
# ExecStart=/usr/local/bin/virshautomem.sh -d
#
# [Install]
# WantedBy=default.target
# minimum memory to assign
MIN_MEMORY=$((2 * 1024 * 1024))
# lower and upper threshold in percent of complete assigned memory
LOWTHRES=5
UPTHRES=10
# Retrieve the connection URI
# uri=$(virsh uri)
uri="qemu:///system"
daemon=0
if [[ "$1" == "-d" ]]; then
daemon=1;
echo "running as daemon"
fi
while
# Retrieve a list of running VMs
running_vms=$(virsh --connect "$uri" list --name --state-running)
# echo "checking running vms: $running_vms"
# Iterate over each VM
for vm_name in $running_vms; do
# Retrieve VM XML configuration
vm_xml=$(virsh --connect "$uri" dumpxml "$vm_name")
# echo "checking $vm_name"
# echo "$vm_xml"
# Check if the VM has ballooning support
if echo "$vm_xml" | grep -q '<memballoon'; then
echo -n "adjusting memory for VM: $vm_name: "
# Extract maximum memory from VM info using xmllint and XPath
max_memory_value=$(echo "$vm_xml" | xmllint --xpath 'string(/domain/memory)' -)
max_memory_unit=$(echo "$vm_xml" | xmllint --xpath 'string(/domain/memory/@unit)' -)
# Convert max_memory_value to kilobytes
case $max_memory_unit in
k | kb | KiB)
MAX_MEMORY=$max_memory_value
;;
m | mb | MiB)
MAX_MEMORY=$(($max_memory_value * 1024))
;;
g | gb | GiB)
MAX_MEMORY=$(($max_memory_value * 1024 * 1024))
;;
t | tb)
MAX_MEMORY=$(($max_memory_value * 1024 * 1024 * 1024))
;;
*)
echo "Unsupported memory unit: $max_memory_unit"
continue
;;
esac
# Retrieve memory statistics
mem_stat=$(virsh --connect "$uri" dommemstat "$vm_name")
current_memory=$(echo "$mem_stat" | awk '/actual/ { print $2 }')
available_memory=$(echo "$mem_stat" | awk '/usable/ { print $2 }')
# Calculate memory thresholds
lower_threshold=$(($MAX_MEMORY * $LOWTHRES / 100))
upper_threshold=$(($MAX_MEMORY * $UPTHRES / 100))
# Adjust memory allocation
if ((available_memory < lower_threshold)); then
echo -n "increase "
new_memory=$(($current_memory + (512 * 1024)))
elif ((available_memory > upper_threshold)); then
echo -n "decrease "
new_memory=$(($current_memory - (512 * 1024)))
else
echo "keeping current $(($current_memory / 1024 ))M avail: $(($available_memory / 1024))M | lowt: $(($lower_threshold / 1024 ))M | upt: $(($upper_threshold / 1024 ))M"
continue
fi
# Ensure the new memory does not exceed the maximum
if ((new_memory > MAX_MEMORY)); then
new_memory=$MAX_MEMORY
fi
# Ensure the new memory does not fall below the minimum
if [[ $new_memory < $MIN_MEMORY ]]; then
new_memory=$MIN_MEMORY
fi
echo "max: $(($MAX_MEMORY / 1024 ))M | cur: $(($current_memory / 1024 ))M | avail: $(($available_memory / 1024 ))M | lowt: $(($lower_threshold / 1024 ))M | upt: $(($upper_threshold / 1024 ))M => $(($new_memory / 1024 ))M"
# Apply the new memory allocation
virsh --connect "$uri" setmem "$vm_name" "$new_memory"
fi
done
# echo "waiting for next execution"
[[ "$daemon" == "1" ]] && sleep 7
do true; done
maybe this helps someone or at least me when reinstalling my machine ;)
greetings