Save to My DOJO
Table of contents
Storage resource contention is one of the hardest resource metrics to track down in vSphere. There is not an easy way to tell in native vCenter which VM on which ESXi host is consuming a lot of storage I/O. Luckily, we can gather this metric on the fly within a matter of minutes with PowerShell! I’m going to share a script I created that allows VMware Administrators to search through their entire vCenter environment and output a list of all VMs and their respective average I/O. It also takes advantage of PowerShell runspaces so that each ESXi host will gather I/O data on its own hosted VMs in its own separate runspace and report back once completed. This allows PowerShell to simply kick off each job without having to wait for each ESXi host to complete its query before moving on to the next ESXi host. It allows the script to scale at a much higher level for those larger vSphere environments.
How To Run The Script
To get started running the script in your environment. Make sure that PowerCLI is installed (Alternative Install How to For Linux Here) on whichever endpoint is going to be running the script. Install PowerCli by opening up and Administrative PowerShell console and run the below or take a look at the links just mentioned if you require a more detailed run down.
[CmdletBinding()] param( [Parameter(Mandatory=$True,ValueFromPipeline=$true,ParameterSetName="Session")]$session, [Parameter(Mandatory=$True,ParameterSetName="Server")] [String]$Server, [Parameter(Mandatory=$True,ParameterSetName="Server")] [System.Management.Automation.PSCredential]$Credential, [Parameter(ParameterSetName="Session")] [parameter(ParameterSetName = "Server")] [String]$Minutes, [Parameter(ParameterSetName="Session")] [parameter(ParameterSetName = "Server")] [String]$Hours ) Begin{ #Create Start Time if ($minutes){ $starttime = (get-date).AddMinutes(0-$Minutes)} if ($hours) { $starttime = (get-date).AddHours(0-$Hours)} if ($null -eq $Minutes -and $null -eq $hours) {$starttime = (get-date).AddMinutes(-5)} #connect to host If ($server){$Session= connect-viserver -server $server -credential $credential -NotDefault} #Get list of ESXi Hosts in VCenter $ESXiHosts = Get-vmhost * -server $Session | Where {$_.PowerState -eq "PoweredOn"} } Process{ #Set up Runspace increase Max Threads to increase number of runspaces running at a time $MaxThreads = 10 $RunspacePool = [RunspaceFactory ]::CreateRunspacePool(1, $MaxThreads) $RunspacePool.Open() $Jobs = @() #Get I/O for all VMs on each Host Foreach ($ESXihost in $ESXiHosts){ #Create scriptblock to run for each ESXi host, parameters are passed through to script $scriptblock = { #Get list of VMs $VMs = Get-vm * -server $args[1] -location $args[0] | Where {$_.PowerState -eq "PoweredOn"} Foreach($VM in $VMs){ Try{ #Gather I/O statistics for the VM based on the StartTime parameter $numberwrite = get-stat -realtime -entity (get-vm $vm -server $args[1]) -stat disk.numberWriteAveraged.average -start $args[2] -finish (get-date) -ErrorAction Stop $numberread = get-stat -realtime -entity (get-vm $vm -server $args[1]) -stat disk.numberReadAveraged.average -start $args[2] -finish (get-date) -ErrorAction Stop Foreach ($writesample in $numberwrite){[array]$writedata += $writesample.value} Foreach ($readsample in $numberread){[array]$readdata += $readsample.value} $averageread = [int]($readdata | measure-object -average).average $averagewrite = [int]($writedata | measure-object -average).average $writedata = $null $readdata = $null } Catch{ #If metrics are not available set them to 0 $averageread = 0 $averagewrite = 0 } #Set up Array for reporting I/O stats $name = $vm.name $ESXiHost = $vm.vmhost $status = New-Object System.Object $status | Add-Member -type NoteProperty -name VM -value $name $status | Add-Member -type NoteProperty -name AvgReadIO -value $averageread $status | Add-Member -type NoteProperty -name AvgWriteIO -value $averagewrite $status | Add-Member -type NoteProperty -name AvgTotalIO -value ($averagewrite + $averageread) $status | Add-Member -type NoteProperty -name ESXHost -value $ESXiHost [array]$VMreport += $status } #Output Report $VMreport } #Run the script block in a separate runspace for the ESXi Host and pass parameter's through $VMinfoJob = [powershell ]::Create().AddScript($ScriptBlock).AddArgument($ESXihost).AddArgument($Session).AddArgument($starttime) $VMinfoJob.RunspacePool = $RunspacePool $Jobs += New-Object PSObject -Property @{ Pipe = $VMinfoJob Result = $VMinfoJob.BeginInvoke() JobName = "ESXi-$esxihost" } } #Wait until jobs are complete Do {Start-sleep -Milliseconds 300 } While ( $Jobs.Result.IsCompleted -contains $false) #Display output from each Runspace Job for status ForEach ($Job in $Jobs){ $info= $Job.Pipe.EndInvoke($Job.Result) [array]$report += $info } $report }
In my example below, I saved it to c:\scripts\VMware and named the file Get-VMIO.ps1:
Now, open up an Administrative Powershell console and type in the following syntax to run the command. You can use either the -hours or -minutes parameter to specify how far back you want statistics for. So if we wanted I/O stats from the last 5 minutes we would use -minutes 5 :
c:\scripts\vmware\Get-VMIO.ps1 -server 192.168.0.7 -minutes 5 | ft
We can see the result is a table with the average IO statistics. I don’t have a lot of heavy I/O going at the time of creating these screenshots, so the numbers are going to be pretty low.
Adding the Tool to the Dashboard
Don Jones always says, “Be the Toolmaker, not the Tool User”. These are wise words of wisdom to live by, and so if we are going to make a script that can be run from the PowerShell console, we might as well make sure our script can be run from a PowerShell Dashboard. The trick for this is to allow for a VIServer session object to be passed through as a parameter, then our script can use that session to run it’s various PowerCLI commands against. This is the piece that allows us to create a parameter that will pass through a session:
[CmdletBinding()] param( [Parameter(Mandatory=$True,ValueFromPipeline=$true,ParameterSetName="Session")]$session,
In my testing, I found that not specifying the object type works better than specifying the [VMware.VimAutomation.ViCore.Impl.V1.VIServerImpl] type name within the parameter. This allows us to run the script from the console like this. We set our connection to VCenter as a variable and specify the -NotDefault parameter. This is SUPER important, as you will run into errors about the $global:defaultVIServer variable if it is not specified :
$newvcsession = connect-viserver -server 192.168.0.7 -notdefault
Now that we have a session, we can pass that session through to our script with the following syntax:
c:\scripts\vmware\Get-VMIO.ps1 -session $newvcsession -minutes 5 | ft
You can see we get the same results as before but instead, we are using a vCenter session. This is how the PowerShell Universal Dashboard works, the initial login prompt will authenticate to vCenter and cache that session into a variable which is stored in memory. That variable is then passed between each dashboard page which allows users to click around without being prompted for login each time. The beauty of it is that since we wrote our initial script with the ability to handle VISession objects, the integration of this script into our dashboard can be done within a matter of minutes. All we need to do is just call our script from the dashboard page like the following. With only a few lines for this page. We are just setting up a Grid Table and outputting the data from Get-VMIO.ps1 to a table. I set the refresh interval to 120 seconds, which means the dashboard will update every 2 minutes with the command:
New-UDPage -Name "VMIO" -Content { #Create Rows New-UDRow { New-UdGrid -Title "VM Average IO" -Headers @("VM","AvgReadIO","AvgWriteIO","AvgTotalIO") -Properties @("VM","AvgReadIO","AvgWriteIO","AvgTotalIO") -Endpoint { $params = @{Session=$cache:VCSession;Minutes=2} & "C:\ESXi Dashboard\Pages\Tools\Get-VMIO.ps1" @params | Select-object VM,AvgReadIO,AVGWriteIO,AvgTotalIO | Out-UDGridData } -AutoRefresh -RefreshInterval 120 } }
The end result looks like this:
Wrap-Up
I’ll be adding this as a tool (as well as many others in the future) into the ESXi Dashboard project on Altaro’s Community Git hub. If you haven’t seen our post on how to create your own ESXi Dashboard’s with the Universal Dashboard module, be sure to check it out. As you can see if you plan for your scripts to be run from various environments, you can quickly integrate the tools you make into the dashboards with only a few lines of code, and the dashboards look pretty too. You can “wow” your boss with a Dashboard made entirely by you!
[the_ad id=”4738″][thrive_leads id=’18673′]
Not a DOJO Member yet?
Join thousands of other IT pros and receive a weekly roundup email with the latest content & updates!
10 thoughts on "How to Show Virtual Machine I/O on an ESXi Dashboard [Free Script]"
No output when run
What process are you following to run the script? It is an advanced function, so a simple right click and run won’t return any output, you’ll need to call the .ps1 through another script or the command line.
I am having a problem running the script on an esxi host. It just exits without error after “Credential”.
I know I can connect to my esxi server.
Any ideas?
Thanks, Jason
Make sure that you are not using connect-viserver in the same PowerShell session before running the script. This will cause the issue your describing. This is due to a limitation with PowerCli and the way connect-viserver connects to hosts.