by Phil Ekins (@SQLPhilUK), Principal Architect
Here at House of Brick, virtualization is near and dear to our hearts, we heavily utilize VMware Workstation to create secure client access, create labs to test new features, etc.
This is a great product, but my current laptop only has a single socket with four cores. So when I’m running a multi-node cluster, domain controller, etc. the single socket easily becomes overwhelmed and the result is my lab grinds productivity to a halt.
There are various tweaks I could do, like turn off Windows Update, stagger when I start VMs, pause the VMs I’m not currently using, and more. But sometimes I am running Linux, ESX hosts, and other flavors of VMs that are just busy due to the nature of what I’m working on. However, these are all manual fixes that may work, but that may also cause other issues in my lab down the road.
I am one of those DBAs who is categorically against manual solutions (in almost all cases), so I spun up the VMware Workstation help files to see what my options were and used PowerShell to implement.
I was hoping to find an option to associate a single VM to a subset of cores, or even the whole product to a subset of cores. However, in the version I use, 12.5.7, there did not appear to be such an option.
So my next option was to look behind the scenes and see what I was dealing with from a process perspective. As you can see, there is a process for the main app, but also an additional process for each VM you start.
Restricting the cores on the main process, vmware.exe, prior to start new VMs did not place any restrictions on the new processes.
So the manual process here would be to set the CPU affinity on each vmware-vmx.exe process, however I don’t know which one corresponds to each VM. I use some VMs for client support, and they need to take priority over my lab VMs CPU wise.
In the Directory housing the VM files, there is a vmware.log file with an entry that displays the PID value.
So now I have all the pieces to create a solution.
First, I have a function that will extract the PID value from the vmware.log.
Next, I need to do the math on the appropriate mask to set the CPU affinity and create my groups.
By adding the values of the cores we want to include, we can assign “Elevated” all four cores (1+ 2 + 4 + 8). We could also do OddCores (1+4) and EvenCores (2+8) or any other variation to divide up the CPU load. Our goal is to not have all four cores at 100%.
The remainder of the code will poll the active VMs, linking their PIDs via the log file and adjusting the affinity of that single process based on the high, normal or low criteria.
The end result is still a busy laptop, but pressure is reduced on one of my cores allowing productivity to be maintained.
The final solution is below:
################################################################################### # # # Set VMware Workstation Affinity - PowerShell Solution # # # # This code is provided as is for demonstration purposes. It may not be suitable # # for your environment. Please test this on your own systems. # # This code may not be republished or redistributed by anyone without permission. # # You are free to use this code inside of your own organization. # # # ################################################################################### CLS FUNCTION Get-PID($VM) { $Path = Split-Path $VM -Parent $nPID = 0 IF ((Test-Path $Path\vmware.log)) { $Contents = Get-Content $Path\vmware.log ForEach ($Line IN $Contents) { IF ($nPID -gt 0) {Break} ForEach ($Element IN ($Line.Split(" "))) { IF ($nPID -gt 0) {Break} IF ($Element.ToLower() -like "pid=*") { $nPID = $Element.Split("=")[1] Break } } } } RETURN $nPID } # CPU Value # 1 1 # 2 2 # 3 4 # 4 8 $ElevatedAffinity = 15 $NormalAffinity = 7 $LowAffinity = 1 $ElevatedVMList = @() $ElevatedVMList += "C:\Hob\VMs\hobvm01\Win7EntHob.vmx" $ElevatedVMList += "C:\Hob\VMs\hobvm02\Win7EntHob.vmx" $ElevatedVMList += "C:\Hob\VMs\hobvm03\Win7EntHob.vmx" ## add more VMs as needed $ReducedVMList = @() $ReducedVMList += "C:\Hob\VMs\Win2016-DC\Win2016-SQL2016-VM1.vmx" $PIDList = @() get-process -processname 'vmware-vmx' | SELECT ID | % { $PIDList += $_.ID } $command = @' cmd.exe /C "C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe" list '@ $RunningVMList = @() Invoke-Expression -Command:$command | % { $RunningVMList += $_ } $ct = -1 ForEach ($VM IN $RunningVMList) { IF (!($VM -like "*Total running VMs:*")) { $GetPID = Get-PID $VM IF ($GetPID -ne 0) { IF ($ElevatedVMList -contains $VM) { (Get-Process -id $GetPID).processoraffinity = $ElevatedAffinity Write-Host "$VM ($GetPID) has been set to Elevated Affinity" } ElseIf ($ReducedVMList -contains $VM) { (Get-Process -id $GetPID).processoraffinity = $LowAffinity Write-Host "$VM ($GetPID) has been set to Low Affinity" } ELSE { (Get-Process -id $GetPID).processoraffinity = $NormalAffinity Write-Host "$VM ($GetPID) has been set to Normal Affinity" } } } $ct ++ }
Hopefully the ideas presented in this blog will be useful to someone having similar issues. Please keep in mind the following caveats when considering utilizing the ideas discussed in this blog.
Caveats :
This works for me, and was tested on a single threaded laptop. It has not been tested on a multi-socket machine (and may not be needed with multi-socket architecture).
This is NOT recommended for a production workload. Test, and test again, before using on your systems.
Please note: this blog contains code examples provided for your reference. All sample code is provided for illustrative purposes only. Use of information appearing in this blog is solely at your own risk. Please read our full disclaimer for details.