VMware Basic VM Performance as XML (Powershell)

Well continuing my recent foray into scripting, I wrote a small script to dump some basic stats about each VM into formatted XML.

The purpose is to get a basic idea of average use accross all our VM’s. We’re using this information as part of a virtual infrastructure review and redeployment. We have budget to replace aging ESX hardware and we’re taking the opportunity to tidy things up a bit.

The script is using the same framework as my dump-virtual-machine-info-as-xml script posted previously. I’ve grown quite fond of XML, although I’m sure I can definitely improve it…. for a start creating a proper schema.

So here it is. I’ll post up a rundown of how it works later :)

#
# Sources:     http://www.peetersonline.nl/index.php/vmware/examine-vmware-cpu-ready-times-with-powershell/
#            http://powershell.com/cs/blogs/tobias/archive/2009/02/02/xml-part-2-write-add-and-change-xml-data.aspx
#
# Notes : Written to export VM stats.
# Author : Doug Youd
#
#-------------------------------------------------------------------------------------------------------------

#--- Parameters ----------------------------------------------------------------------------------------------
#XML Template
$XMLTemplateFile = "C:/tmp/VMachines_TemplateStat.xml"
#Output XML File
$XMLOutput = "c:/tmp/VMachinesStat.xml"
#Use Shortname?
$UseShortname = "True"

#Interval (in days)
$Interval = "30"

#--- Main ----------------------------------------------------------------------------------------------------

#Define the XML Template
$XMLTemplate = @'
<VMachines version="1.0">
    <VMachine>
        <VMName></VMName>
        <VMCpu></VMCpu>
        <VMMem></VMMem>
        <Stats>
            <Cpu>
                <AvgMhz></AvgMhz>
                <MaxMhz></MaxMhz>
                <VCpus>
                    <VCpu>
                        <Instance></Instance>
                        <AvgMhz></AvgMhz>
                        <MaxMhz></MaxMhz>
                        <CpuRdy></CpuRdy>
                        <CpuUsed></CpuUsed>
                        <CpuWait></CpuWait>
                    </VCpu>
                </VCpus>
            </Cpu>
            <Memory>
                <AvgMemGranted></AvgMemGranted>
                <MaxMemGranted></MaxMemGranted>
                <AvgMemActive></AvgMemActive>
                <MaxMemActive></MaxMemActive>
            </Memory>
            <Disk>
                <AvgDiskIO></AvgDiskIO>
                <MaxDiskIO></MaxDiskIO>
            </Disk>
            <Network>
                <AvgNetIo></AvgNetIo>
                <MaxNetIo></MaxNetIo>
            </Network>
        </Stats>
    </VMachine>
</VMachines>
'@
$XMLTemplate | Out-File $XMLTemplateFile -encoding UTF8

$XMLVMachines = New-Object XML
$XMLVMachines.load($XMLTemplateFile)
$XMLVMachine = @($XMLVMachines.VMachines.VMachine)[0]

#Define the stats interval
$IntervalFinish = Get-Date
$IntervalStart = $IntervalFinish.addDays(-1 * $Interval)

#Get list of VM and ESXHost objects for vCenter
ForEach ($VMHost in (Get-VMHost))
{
    foreach($vm in ($VMHost | Get-VM | where {$_.PowerState -ne "PoweredOff"} | Sort Name)){
        $Temp = "" | Select-Object Match

        #Define a custom VMachine PSObject
        $VMachine = $XMLVMachine.Clone()

        #Populate Basic VM Info
        If($UseShortname -eq "True"){
            $Temp.Match = $vm.Name -match '^[a-zA-Z0-9\-]*'
            If($Temp.Match) {
                $VMachine.VMName = [String]$matches[0]
            }else {
                $VMachine.VMName = [String]$vm.Name
            }
        }else {
            $VMachine.VMName = [String]$vm.Name
        }
        $VMachine.VMMem = [String]$vm.memorySizeMB
        $VMachine.VMCpu = [String]$vm.numCpu
    
        #Get-stats
        #Can only get Ready Info on Realtime (unfortunately)
        $Ready = $vm | Get-Stat -Stat Cpu.Ready.Summation -RealTime -ErrorAction SilentlyContinue
        $Used = $vm | Get-Stat -Stat Cpu.Used.Summation -RealTime -ErrorAction SilentlyContinue
        $Wait = $vm | Get-Stat -Stat Cpu.Wait.Summation -RealTime -ErrorAction SilentlyContinue
        #At defaul stats levels, only able to get Avg rollup.
        $AvgMhz = $vm | Get-Stat -Stat Cpu.usagemhz.average -Start $IntervalStart -Finish $IntervalFinish -ErrorAction SilentlyContinue
        $MaxMhz = $vm | Get-Stat -Stat Cpu.usagemhz.maximum -Start $IntervalStart -Finish $IntervalFinish -ErrorAction SilentlyContinue
        $AvgMemGranted = $vm | Get-Stat -Stat Mem.granted.average -Start $IntervalStart -Finish $IntervalFinish -ErrorAction SilentlyContinue
        $MaxMemGranted = $vm | Get-Stat -Stat Mem.granted.maximum -Start $IntervalStart -Finish $IntervalFinish -ErrorAction SilentlyContinue
        $AvgMemActive = $vm | Get-Stat -Stat Mem.active.average -Start $IntervalStart -Finish $IntervalFinish -ErrorAction SilentlyContinue
        $MaxMemActive = $vm | Get-Stat -Stat Mem.active.maximum -Start $IntervalStart -Finish $IntervalFinish -ErrorAction SilentlyContinue
        $AvgDiskIO = $vm | Get-Stat -Stat disk.usage.average -Start $IntervalStart -Finish $IntervalFinish -ErrorAction SilentlyContinue
        $MaxDiskIO = $vm | Get-Stat -Stat disk.usage.maximum -Start $IntervalStart -Finish $IntervalFinish -ErrorAction SilentlyContinue
        $AvgNetIO = $vm | Get-Stat -Stat net.usage.average -Start $IntervalStart -Finish $IntervalFinish -ErrorAction SilentlyContinue
        $MaxNetIO = $vm | Get-Stat -Stat net.usage.maximum -Start $IntervalStart -Finish $IntervalFinish -ErrorAction SilentlyContinue
        
        #Populate Per-VM Stats
        $VMachine.Stats.Cpu.AvgMhz = [String]([Math]::Round((($AvgMhz | Measure-Object -Property Value -Average).Average),1))
        $VMachine.Stats.Cpu.MaxMhz = [String]([Math]::Round((($MaxMhz | Measure-Object -Property Value -Average).Average),1))
        $VMachine.Stats.Memory.AvgMemGranted = [String]([Math]::Round((($AvgMemGranted | Measure-Object -Property Value -Average).Average),1))
        $VMachine.Stats.Memory.MaxMemGranted = [String]([Math]::Round((($MaxMemGranted | Measure-Object -Property Value -Average).Average),1))
        $VMachine.Stats.Memory.AvgMemActive = [String]([Math]::Round((($AvgMemActive | Measure-Object -Property Value -Average).Average),1))
        $VMachine.Stats.Memory.MaxMemActive = [String]([Math]::Round((($MaxMemActive | Measure-Object -Property Value -Average).Average),1))
        $VMachine.Stats.Disk.AvgDiskIo = [String]([Math]::Round((($AvgDiskIo | Measure-Object -Property Value -Average).Average),1))
        $VMachine.Stats.Disk.MaxDiskIo = [String]([Math]::Round((($MaxDiskIo | Measure-Object -Property Value -Average).Average),1))
        $VMachine.Stats.Network.AvgNetIo = [String]([Math]::Round((($AvgNetIo | Measure-Object -Property Value -Average).Average),1))
        $VMachine.Stats.Network.MaxNetIo = [String]([Math]::Round((($MaxNetIo | Measure-Object -Property Value -Average).Average),1))
        
        #Populate per-vCPU Stats
        For ($a = 0; $a -lt $VM.NumCpu; $a++)
        {
            #Create a new CPU instance
            $VCpu = (@($VMachine.Stats.Cpu.VCPUs.VCpu)[0]).Clone()
            $VCpu.Instance = [String]$a            
            $VCpu.CpuRdy = [String]([Math]::Round((($Ready | Where {$_.Instance -eq $a} | Measure-Object -Property Value -Average).Average)/200,1))
            $VCpu.CpuUsed = [String]([Math]::Round((($Used | Where {$_.Instance -eq $a} | Measure-Object -Property Value -Average).Average)/200,1))
            $VCpu.CpuWait = [String]([Math]::Round((($Wait | Where {$_.Instance -eq $a} | Measure-Object -Property Value -Average).Average)/200,1))
            $VCpu.AvgMhz = [String]([Math]::Round((($AvgMhz | Where {$_.Instance -eq $a} | Measure-Object -Property Value -Average).Average),1))
            $VCpu.MaxMhz = [String]([Math]::Round((($MaxMhz | Where {$_.Instance -eq $a} | Measure-Object -Property Value -Average).Average),1))
            $VMachine.Stats.Cpu.VCpus.AppendChild($VCpu) > $null
        }
        #Clear the variables.
        Clear-Variable Ready -ErrorAction SilentlyContinue
        Clear-Variable Wait -ErrorAction SilentlyContinue
        Clear-Variable Used -ErrorAction SilentlyContinue
        Clear-Variable CpuMhz -ErrorAction SilentlyContinue
        Clear-Variable AvgMemGranted -ErrorAction SilentlyContinue
        Clear-Variable MaxMemGranted -ErrorAction SilentlyContinue
        Clear-Variable AvgMemActive -ErrorAction SilentlyContinue
        Clear-Variable MaxMemActive -ErrorAction SilentlyContinue
        Clear-Variable AvgDiskIo -ErrorAction SilentlyContinue
        Clear-Variable MaxDiskIo -ErrorAction SilentlyContinue
        Clear-Variable AvgNetIo -ErrorAction SilentlyContinue
        Clear-Variable MaxNetIo -ErrorAction SilentlyContinue
        
        #Clear Blank CPU Instances
        $VMachine.Stats.Cpu.VCpus.VCpu | Where-Object {$_.Instance -eq ""} | ForEach-Object {[Void]$VMachine.Stats.Cpu.VCpus.RemoveChild($_)}

        #Add VM to VMachines array
        $XMLVMachines.VMachines.AppendChild($VMachine) > $null
    }
}

#Clear blank VMs
$VMachines.VMachines.VMachine | Where-Object {$_.VMName -eq ""} | ForEach-Object {[Void]$VMachines.VMachines.RemoveChild($_)}

#Export to XML
$XMLVMachines.Save($XMLOutput)

Download it from Here

Leave a Reply

The opinions expressed on this site are my own and not necessarily those of my employer.

All code, documentation etc is my own work and is licensed under Creative Commons and you are free to use it, at your own risk.

I assume no liability for code posted here, use it at your own risk and always sanity-check it in your environment.