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!
Table of Contents
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
- then command
- if Running = 0 ;; Plain request to resume, do it and set variable.
- 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 Gracing =1
- LastLine = FocStr
- if Cntr > CntrMax ;; Pause and reset variables
- then command
pause
- then Running = 0
- then Gracing =0
- then Cntr=0
- then command
- 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.