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

github gist

maybe this helps someone or at least me when reinstalling my machine ;)

greetings