Labora: Dealing with power hungry virtual machines

Every active virtual machine in VirtualBox consumes extra power. In an economical and efficient setup (debloated), it is 10 watts for Ubuntu machines running idle and even 14 watts for Windows 10 and 11 machines. In normal cases, the extra power consumption is considerably higher. ln a mobile working environment or on a laptop, it means that your battery runs out faster.

The problem is that when the focus is not on the virtual machine, the machine still eats power and drains your battery. To avoid this, you can turn off such a machine but that is also not optimal and not practical.

What turns out? If you pause a machine, its power consumption also goes to 0 watts. Nice and all, but then of course we want this to happen automatically so we never have to worry about it again. Welcome to an article on polishing the process of pausing and resuming…

TIP!
Manually pausing a virtual machine is easy: Press HostKey-P. HostKey is usually the right Control key.

The problem is not new but apparently few people are interested in a solution – until your laptop battery runs out of power very quickly after all, or until you rely on an on-board battery in an RV or boat. Besides, there is a moral obligation for everyone to reduce your energy footprint on this world. So this article is actually for anyone working with VirtualBox and Linux. Other OS’s? Modify to taste!

A final note. You might think it smart to lull a virtual machine into automatic sleep mode to save power. Unfortunately, that doesn’t work – the electricity meter just keeps running.

What follows is all a bit “hackish” but it serves a great purpose and works, so my itch is gone, Pfew!

The global plan

You can pause and continue a virtual machine via the interface. This can be done by right clicking a machine in VirtualBox Manager and toggling the “Pause” entry.

Such a machine logs events such as “has focus” and “has no focus”.

A paused machine can acquire focus while remaining 0 Watt usage. Therefore remarkably, these focus events are also logged, even in a paused condition.

This creates a window of opportunity: By continuously monitoring the last line of the log file, the machine can be paused or resumed automatically on that basis.

“All we need for this is a script running invisibly in the background.”

Details

Before discussing the script, several details follow here.

Start at login

In order to start the script at user login, an entry can be made in ~/.profile or ~/.bashrc. This is not a good idea, it prevents you from login to your system because the “while” loop never stops. Xubuntu has Settings > Session and Startup. An entry there works like a charm. An entry example:

~/bin/vmpower.sh "MachineName"

Make sure file vmpower.sh is executable. Your standardized preferred file location for it may be ~/bin.

Log file

A log file is created as ~/vmpower_Machinename.log.

Testing

If you understand this page and the script, you can troubleshoot and tune the script. Possibilities: start ./vmpower.sh in a terminal and tail -f the vmpower and VirtualBox log files.

Pause and resume commands

VirtualBox has two commands for pausing and resuming a running virtual machine MachineName. They are:

# Pause MachineName
VBoxManage controlvm MachineName pause
# Resume MachineName
VBoxManage controlvm MachineName resume

Examples of log entries

In a terminal you can view log entries live:

tail -f ~/VirtualBox\ VMs/MachineName/Logs/VBox.log
04:45:11.570947 VMMDev: Guest Log: VBOXNP: DLL loaded.
05:02:49.946321 VMMDev: Guest Log: VBOXNP: DLL loaded.
07:06:15.949380 Console: Machine state changed to 'Paused'
07:06:15.949712 GUI: Releasing keyboard on pause/stuck
07:06:15.949734 GUI: Releasing mouse on pause/stuck
14:02:59.796187 VMMDevNotifyGuest: fAddEvents=0x2 ignored because enmVMState=15

Click on and outside a machine window (or Alt-Tab) and see what happens…

Focus:

Date stamp GUI: Machine-window #0 activated
Date stamp GUI: Machine-view #0 focused, reason=3

Unfocus:

Date stamp GUI: Machine-window #0 deactivated
Date stamp GUI: Releasing mouse on focus out
Date stamp GUI: Machine-view #0 unfocused, reason=3

Design

Our “monitor” is actually a while loop of, let say, 1 second, that continuously stores and compares values from the last VirtualBox log file input.

LastLine is the Last Line of the log file, with date stamp stripped.

In order to compare this:
The focus-string is FocStr, “GUI: Machine-view #0 focused, reason=3
The unfocus-string is UnFocStr, “GUI: Machine-view #0 unfocused, reason=3

Important to understand: The log file can have a lot of notifications so LastLine can have a lot of different values and the trick is to deal with situations where LastLine only has values FocStr, UnFocStr or different.

We need a counter Cntr (initial 0) to determine the moment, after a time-out, the pause command is issued.

During this time-out a variable Gracing is 1. After CntrMax the machine will be paused.

Also needed is a pointer Running for the state of the machine, 1 for a running, resumed, active, not paused machine.

Let’s put it in perspective, inside a while loop lives a $case/if construction:

  • case ;; Dealing with LastLine
    • LastLine = FocStr
      • if Running = 0 ;; Plain request to resume, do it and set variable.
        • then command resume
        • then Running =1
    • LastLine = UnFocStr ;; Start grace period.
      • then Gracing =1
      • then Cntr=Cntr+1
    • LastLine = * ;; End of case statement…
      • if Gracing =1
        • then Cntr=Cntr+1
  • if Cntr > CntrMax ;; Pause and reset variables
    • then command pause
    • then Running = 0
    • then Gracing =0
    • then Cntr=0
  • Repeat this loop endlessly…

The script

Just copy the script to a file vmpower.sh… I’ll try to keep this code updated.

2023-07-23

#!/bin/bash
 
# Pause and resume a VirtualBox machine automatically for power saving.
# See https://vanderworp.org/power-management-virtual-machines
 
## Declare:
 
# Seconds between unfocused and machine pause.
PauseDelay=60
# Polling frequency as loops per second.
PolFreq=2
 
# Locations...
# Name of machine is the directory name. $1 as argument of this script
# or, without argument, the machine name.
MachineName=$1
 
# Partial locations for concatenation. Together with MachineName it
# forms the full logfile name "LogVB".
VMRoot="/home/$USER/VirtualBox VMs"
LogLoc="Logs/VBox.log"
 
# Trigger log values
UnFocStr="GUI: Machine-view #0 unfocused, reason=3"
FocStr="GUI: Machine-view #0 focused, reason=3"
 
# Amount of date stamp characters to cut away from start of log line in
# order to match UnFocStr or FocStr.
DateStrLen=16
 
# Run this script at log in 1 (from terminal 0)
# AtLogIn=1
 
## Preprocessing variables:
 
# Log file for reading:
LogVB=$VMRoot/$MachineName/$LogLoc
 
if [ -f "$LogVB" ]; then
	echo "$LogVB exists."
else 
	echo "$LogVB does not exist. You may want to stop and check your script settings."
	read -p "Press Enter to continue or Ctrl-C to stop..."
fi
 
# Log file for writing:
LogVMP="$HOME/vmpower_$MachineName.log"
 
# Times
SleepTime=$(bc -l <<< "1/$PolFreq")
CntrMax=$(bc -l <<< "$PauseDelay*$PolFreq")
 
## Actions
 
# Determine machine state, set initial states
Gracing=0
Cntr=0
State=$(vboxmanage showvminfo $MachineName | grep "State:"| cut -c 30-40)
LastLine="Dummy"
case $State in
	"paused (sin")
		Running=0 ;;
	"running (si")
		Running=1 ;;
	"powered off") ;;
	*) ;;
esac
 
# Log
printf "This is the log file of virtual machine $MachineName.\nStart time: $(date +%T)\nIt logs events while running the power script vmpower.sh after user login.\nFor more: See https://vanderworp.org/power-management-virtual-machines\n\nSettings:\nTime before machine pauses without focus (seconds): $PauseDelay\nPolling frequency (loops per second): $PolFreq\nVirtualBox log file for analyzing: $LogVB\nInitial machine state string: $State\n\nEvents:\n" > $LogVMP
 
# Engine room
while true; do
	LastLinePrev=$LastLine
	LastLine=$(tail -n 1 "$LogVB")
	if [ "$LastLine" != "$LastLinePrev" ]; then
		printf "VBox: $LastLine\n" >> $LogVMP
	fi
 
	LastStr=${LastLine:$DateStrLen}
	sleep $SleepTime
	case $LastStr in
		$FocStr)
			if [ $Running = 0 ]; then
				VBoxManage controlvm $MachineName resume
				Running=1
				Gracing=0
				printf "VMPower: Resume command issued, status: Running - $(date +%T) \n" >> $LogVMP
			fi
			;;
		$UnFocStr)
			Gracing=1
			Cntr=$((Cntr+1))
			if [ $Cntr = 1 ]; then
				printf "VMPower: Starting Grace period before pausing...- $(date +%T) \n" >> $LogVMP
			fi
			;;
		*)
			if [ $Gracing = 1 ]; then
				Cntr=$((Cntr+1))
			fi
			;;
	esac
	if (("$Cntr" >= "$CntrMax")); then
		VBoxManage controlvm $MachineName pause
		Running=0
		Gracing=0
		Cntr=0
		printf "VMPower: Grace period elapsed, pause command issued, status: Paused - $(date +%T) \n" >> $LogVMP
	fi
done

Featured image modified from source, courtesy.

Leave a comment