Get the maximum IOPS
A quick post that is triggered by a tweet from @GernotNusshall I saw passing today. He wanted to know how to find the maximum IOPS values over the last 5 minutes for a number of VMs. The IOPS values are readily available from the vSphere statistics but the problem is that the values are returned as summation values over the measuring interval and that you have a read and a write value.
An ideal job for PowerShell to get the values Gernot was after.
Update April 26th 2011: following changes are made
- The output will also show the datastorename
- A suggestion from Glenn Sizemore, the script uses the New-Object cmdlet to produce the output
Update June 29th 2011: A version that retrieves the average read and write IOPS was added.
Update August 6th 2011: Yet another version was added, now one that supports NFS datastores.
The script
$metrics = "disk.numberwrite.summation","disk.numberread.summation"
$start = (Get-Date).AddMinutes(-5)
$report = @()
$vms = Get-VM | where {$_.PowerState -eq "PoweredOn"}
$stats = Get-Stat -Realtime -Stat $metrics -Entity $vms -Start $start
$interval = $stats[0].IntervalSecs
$lunTab = @{}
foreach($ds in (Get-Datastore -VM $vms | where {$_.Type -eq "VMFS"})){
$ds.ExtensionData.Info.Vmfs.Extent | %{
$lunTab[$_.DiskName] = $ds.Name
}
}
$report = $stats | Group-Object -Property {$_.Entity.Name},Instance | %{
New-Object PSObject -Property @{
VM = $_.Values[0]
Disk = $_.Values[1]
IOPSMax = ($_.Group | `
Group-Object -Property Timestamp | `
%{$_.Group[0].Value + $_.Group[1].Value} | `
Measure-Object -Maximum).Maximum / $interval
Datastore = $lunTab[$_.Values[1]]
}
}
$report
Annotations
Line 1: The 2 metrics that measure IOPS. The metrics return the number of read and write operations over the measurement interval.
Line 5: The script stores all powered on guests in an array. The Get-VM statement can be adapted to return just the guests for which you want the IOPS numbers.
Line 6: The script uses only 1 Get-Stat cmdlet for all VMs and all requested metrics. This will optimise the use of resources on the vCenter Server to retrieve the metrics.
Line 7: To avoid hard-coding the duration of the interval the script retrieves the value from the first returned measurement.
Line 9-14: The script creates a hash table that contains all the canonical names of the LUNs used for the datastores on which the virtual machine are stored.
Line 16: The Group-Object cmdlet does all the hard work. It will group the statistical measurements just the way we would like them
Line 17: The New-Object cmdlet is used to create the output object.
Line 20-23: Calculates the maximum IOPS number by adding the read and write operations together. Since the IOPS value is ‘per second’, the script divides the summation value by the length, in seconds, of the interval.
Line 24: The datastorename is retrieved from the hash table with the canonical name of the LUN as the key.
A sample run
The script produces output similar to this
The sample output shows the only disadvantage, in my opinion, of using the New-Object cmdlet, you have no control over the order of the properties in the object.
The Read/Write Average version
A reader asked if it was possible to retrieve the average read and write IOPS as well. The following script should do the trick
$metrics = "disk.numberwrite.summation","disk.numberread.summation"
$start = (Get-Date).AddMinutes(-5)
$report = @()
$vms = Get-VM | where {$_.PowerState -eq "PoweredOn"}
$stats = Get-Stat -Realtime -Stat $metrics -Entity $vms -Start $start
$interval = $stats[0].IntervalSecs
$lunTab = @{}
foreach($ds in (Get-Datastore -VM $vms | where {$_.Type -eq "VMFS"})){
$ds.ExtensionData.Info.Vmfs.Extent | %{
$lunTab[$_.DiskName] = $ds.Name
}
}
$report = $stats | Group-Object -Property {$_.Entity.Name},Instance | %{
New-Object PSObject -Property @{
VM = $_.Values[0]
Disk = $_.Values[1]
IOPSWriteAvg = ($_.Group | `
where{$_.MetricId -eq "disk.numberwrite.summation"} | `
Measure-Object -Property Value -Average).Average / $interval
IOPSReadAvg = ($_.Group | `
where{$_.MetricId -eq "disk.numberread.summation"} | `
Measure-Object -Property Value -Average).Average / $interval
Datastore = $lunTab[$_.Values[1]]
}
}
$report
The NFS datastore version
As one of my readers noticed, the original script didn’t handle NFS based datastores.
The following version remediates that shortcoming.
$metrics = "virtualdisk.numberwriteaveraged.average","virtualdisk.numberreadaveraged.average"
$start = (Get-Date).AddMinutes(-5)
$report = @()
$vms = Get-VM | where {$_.PowerState -eq "PoweredOn"}
$stats = Get-Stat -Realtime -Stat $metrics -Entity $vms -Start $start
$interval = $stats[0].IntervalSecs
$hdTab = @{}
foreach($hd in (Get-Harddisk -VM $vms)){
$controllerKey = $hd.Extensiondata.ControllerKey
$controller = $hd.Parent.Extensiondata.Config.Hardware.Device | where{$_.Key -eq $controllerKey}
$hdTab[$hd.Parent.Name + "/scsi" + $controller.BusNumber + ":" + $hd.Extensiondata.UnitNumber] = $hd.FileName.Split(']')[0].TrimStart('[')
}
$report = $stats | Group-Object -Property {$_.Entity.Name},Instance | %{
New-Object PSObject -Property @{
VM = $_.Values[0]
Disk = $_.Values[1]
IOPSMax = ($_.Group | `
Group-Object -Property Timestamp | `
%{$_.Group[0].Value + $_.Group[1].Value} | `
Measure-Object -Maximum).Maximum / $interval
Datastore = $hdTab[$_.Values[0] + "/"+ $_.Values[1]]
}
}
$report


Hello Luc,
i am using the below script for disk IOPs calculation, but it is not working if i remove “-Realtime” switch, lets say i am calculating it for last week, please have a look where its going wrong.
$metrics = “disk.numberwrite.summation”,”disk.numberread.summation”
$start = (Get-Date).AddDays(-7)
$report = @()
$vms = Get-VM THASCCMSRV
$stats = Get-Stat -Realtime -Stat $metrics -Entity $vms -Start $start
$interval = $stats[0].IntervalSecs
$lunTab = @{}
foreach($ds in (Get-Datastore -VM $vms | where {$_.Type -eq “VMFS”})){
$ds.ExtensionData.Info.Vmfs.Extent | %{
$lunTab[$_.DiskName] = $ds.Name
}
}
$report = $stats | Group-Object -Property {$_.Entity.Name},Instance | %{
New-Object PSObject -Property @{
VM = $_.Values[0]
Disk = $_.Values[1]
IOPSMax = ($_.Group | `
Group-Object -Property Timestamp | `
%{$_.Group[0].Value + $_.Group[1].Value} | `
Measure-Object -Maximum).Maximum / $interval
IOPSAvg = ($_.Group |
Group-Object -Property Timestamp | `
%{$_.Group[0].Value + $_.Group[1].Value} | `
Measure-Object -Average).Average / $interval
Datastore = $lunTab[$_.Values[1]]
}
}
$report | Export-Csv “C:\IOPSMax-report.csv” -NoTypeInformation -UseCulture -ErrorAction SilentlyContinue
I’m having a hard time with the script. I’m using the vSphere PowerCLI, I connect to a vSphere host and execute the script. It repeats this line over and over:
[vSphere PowerCLI] C:\Scripts\vmware\PowerCLI\VC02> C:\Scripts\vmware\PowerCLI\V
C02\get_iops.ps1
New-Object : A parameter cannot be found that matches parameter name ‘Property’
.
At C:\Scripts\vmware\PowerCLI\VC02\get_iops.ps1:17 char:34
+ New-Object PSObject -Property <<<< @{
New-Object : A parameter cannot be found that matches parameter name 'Property'
.
I copied the code using the widget and it looks right to my eye. What could be missing?
Hi Matt, I suspect you might be using PowerShell v1.
The Property parameter was introduced in PowerShell v2.
Do a
$PSVersionTable
The PSVersion property should be 2.0 or 3.0.
Hi LucD,
How can I get disk size details in same script
Hi LucD,
Thank you so much for the script. It’s exactly what I need to get the necessary information for our DR Site requirements. I’m currently battling to get the same information, but for a specific date/time range. I added the following, but I receive a “Cannot index into a null array” error after I run the script and the csv file is empty. I also removed the “-Realtime” as that would ignore the start/finish options.
$start = “06 Aug 2012 08:00″
$finish = “06 Aug 2012 17:00″
$stats = Get-Stat -Stat $metrics -Entity $vms -Start $start -Finish $finish
Any help would be greatly appreciated.
Many thanks,
Kenny
I would like to use The NFS datastore version to get the report for the period of last 30 days. Could you please help me to append the script.
@ LucD
How can I get the disk size information in this script.
Hi Luc,
In the NFS version of this script is it possible to have another field with the disk capacity? like this:
IOPSMaxDisk VM Datastore TotalSize
Hi MrG, yes it is.
See the code in VM Correct DISK IOPS.
Hi Luc,
Thx a lot for these scripts. I do have a question to ask you: I’m not sure how to interpret the values that are returned by the NFS script. For instance for one of my VM’s I get 11,5 IOPS Max for one of its vdisks whilst in esxtop CMD/s for this vdisk is constantly between 250 and 450?
I’m using the NFS version of the script exactly like you posted on this page.
Thx!
B.
Hi Lucd, Excellent script and very helpful.
Need help i wanted to run as schedule task every hour and append the info with time stamp in to an file. How do i achieve that if you could help???
Many Thanks
Hi Harry, you can use the Windows Scheduler.
Have a look at Alan’s post called Running a PowerCLI Scheduled task.
You can add a Timestamp property to the New-Object structure, something like this
$report = $stats | Group-Object -Property {$_.Entity.Name},Instance | %{
New-Object PSObject -Property @{
Timestamp = $start
VM = $_.Values[0]
Disk = $_.Values[1]
IOPSMax = ($_.Group | `
Group-Object -Property Timestamp | `
%{$_.Group[0].Value + $_.Group[1].Value} | `
Measure-Object -Maximum).Maximum / $interval
Datastore = $lunTab[$_.Values[1]]
}
}
You can store the results in a CSV file and append the new data.
You can do something like this
$oldReport = $null
if(Test-Path .\report.csv){
$oldReport += Import-Csv .\report.csv -UseCulture
}
$oldReport + $report | Export-Csv .\report.csv -NoTypeInformation -UseCulture
In PowerShell v3 the Export-Csv cmdlet will have an Append switch, and you don’t have to do tricks like the one above anymore.
Hey Luc,
Getting an error I can’t explain:
Cannot index into a null array.
At C:\Users\roderick\Desktop\iops.ps1:7 char:20
Cheers
Roderick
@Roderick, it looks as if the Get-Stat didn’t return anything.
Are there any poweredon VMs present, in other words are there any objects in the variable $vms ?
Great script, saved me a bunch of time today….. now time to go investigate some ‘top talkers’.
Cheers,
Doug
@LucD
Hi LucD,
Hope you had a nice weekend. I increased the level to 3 and was still getting an error before I realized I needed to add the following switch
$stats = Get-Stat -Stat $metrics -Entity $vms -Start $start -Finish -Disk
Now I am getting results.
Thanks again
@Jbuddy, great. Glad you have it working now.
@LucD
Hi LucD,
They were set to 2. So I set it to 3 in the 5min interval duration saving for 1 day. So the original script worked because it had the realtime switch.
Thanks again
Thanks again
@Jbuddy, you should now wait till the aggregation jobs have propagated the change. Should take 1 day for the values of the day before to be available.
But remember that increasing the statistics levels, will also increase the size of your VC database. Monitor the VC database closely in the coming days. And make sure the aggregation jobs complete successfully.
@LucD
Ah ok thanks again
@LucD
Hi LucD,
Thanks again for assisting me with this. I heard at your talk at vmworld you were quick to answer questions on your site
. So I tried finish before and was getting a null result. The actual error message is as follows. The metric counter “disk.numberread.summation” doesn’t exist for entity” My Start and end times are within 3 hours so
$start = (Get-Date).AddHours(-14) #Friday, October 28, 2011 12:48:50 AM
$finish = (Get-Date).AddHours(-12) #Friday, October 28, 2011 2:48:50 AM
Any help is appreciated and again these are great scripts thanks
@Jbuddy, I suspect this is because your statistics level is not set to at least level 3.
If you look at the disk metrics, you’ll see that numberRead and numberWrite require level 3.
Check your current settings from the vSphere Client.
Hi LucD,
Hopefully you are still monitoring this thread. I am using both of your disk stats script regarding IO average and maximums. We have an issue that happens at 1:00am on and off for about an hour. Could we adapt the script to just monitor that time period instead of an entire day? Is this possible with get-stat? I am about average with powershell and I am still wrapping my head around your script.
Thanks again
@Jbuddy, yes, I still look at comments for this thread
You can change the script to use statistical data from a specific time range with the Start and Finish parameters. You can’t use the Realtime parameter in this case, when the specified time interval is further back in time than approx 1 hour.
The script could look something like this
$metrics = "disk.numberWrite.summation","disk.numberRead.summation"
$start = Get-Date -Hour 1 -Minute 0 -Second 0
$finish = $start.AddHours(1)
$report = @()
$vms = Get-VM mmmstd010 | where {$_.PowerState -eq "PoweredOn"}
$stats = Get-Stat -Stat $metrics -Entity $vms -Start $start -Finish $finish
$interval = $stats[0].IntervalSecs
$lunTab = @{}
foreach($ds in (Get-Datastore -VM $vms | where {$_.Type -eq "VMFS"})){
$ds.ExtensionData.Info.Vmfs.Extent | %{
$lunTab[$_.DiskName] = $ds.Name
}
}
$report = $stats | Group-Object -Property {$_.Entity.Name},Instance | %{
New-Object PSObject -Property @{
VM = $_.Values[0]
Disk = $_.Values[1]
IOPSMax = ($_.Group | `
Group-Object -Property Timestamp | `
%{$_.Group[0].Value + $_.Group[1].Value} | `
Measure-Object -Maximum).Maximum / $interval
Datastore = $lunTab[$_.Values[1]]
}
}
$report
Note that the statistical data you will get this way is aggregated over longer intervals than 20 seconds. So the results will be more flattened out than the results you get from the Realtime interval.
You can adapt the 2nd script in a similar way.
Hi Luc
Would possible to amend this script to get the IOPS per VMDK file per VM.
Thanks
Darren
This is great stuff but I need it to work with VMs in a NFS Datastore. Can this be done?
@Happy, sorry for the delay, I completely forgot about this question.
The script in it’s current form can’t handle NFS datastores.
I’ll try to come up with something that also does NFS datastores.
@LucD
That’s exactly what I was looking for. Thanks.
Is there a way to get the average read and write ratios? Thanks.
@Pawel, today I added a new version of the script that returns the average read and write IOPS.
I hope this produces what you were looking for.
@LucD
Sorry I was unclear. Using your max IOPS script, what if I wanted to add together two NoteProperty values such as MaxIOPS and IOPSAve to create a third noteproperty value called IOPSNumber for each VM (lines 18-24). Of course adding those two specific values makes no sense, but that’s the general concept I can’t quite master in PS. Is that clearer?
@LucD
LucD,
How would you perform a math function on existing group-object NoteProperty values and create a new NoteProperty based on that calculated value?
@Derek, I’m not sure that I understand 100% what you want, but this is my interpretation.
A small test array with a property called Value.
I then group the elements based on the Value being odd or even.
This is added as a new property, called newValue, to each element.
$array = @()
1..10 | %{
$array += New-Object -TypeName PSObject -Property @{
Value = $_
}
}
$newArray = $array | Group-Object -Property {$_.Value % 2} | %{
$groupValue = if($_.Name -eq 0){"even"}else{"odd"}
$_.Group | %{
Add-Member -InputObject $_ -Name NewValue -Value $groupValue `
-MemberType NoteProperty -PassThru
}
}
$newArray | Sort-Object -Property Value
Thanks for the great script! I used it as a basis for a more VDI oriented report that shows total read/write IOs, read/write ratios, etc.
http://derek858.blogspot.com/2011/06/powercli-script-to-dump-vm-io-stats.html
You’re welcome. Thanks for sharing your version in your post.
For some reason I’m getting this error message… Any suggestion?
vSphere PowerCLI] C:\> foreach($ds in (Get-Datastore -VM $vms | where {$_.Type -eq “VMFS”})){
$ds.ExtensionData.Info.Vmfs.Extent | %{
$lunTab[$_.DiskName] = $ds.Name
}
}
$report = $stats | Group-Object -Property {$_.Entity.Name},Instance | %{
New-Object PSObject -Property @{
VM = $_.Values[0]
Disk = $_.Values[1]
IOPSMax = ($_.Group | `
Group-Object -Property Timestamp | `
%{$_.Group[0].Value + $_.Group[1].Value} | `
Measure-Object -Maximum).Maximum / $interval
Datastore = $lunTab[$_.Values[1]]
}
}
$report | Export-Csv “C:\IOPSMax-report.csv” -NoTypeInformation -UseCulture
Missing closing ‘)’ in expression.
At line:11 char:1
+ <<<< Group-Object -Property Timestamp | `
+ CategoryInfo : ParserError: (CloseParenToken:TokenId) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingEndParenthesisInExpression
Awsome script!
Is it possible to also calculate the average iops?
This would be great addition for my daily storage reporting!
Sure, you can add the following lines to the PSObject properties hash table.
IOPSAvg = ($_.Group | `
Group-Object -Property Timestamp | `
%{$_.Group[0].Value + $_.Group[1].Value} | `
Measure-Object -Average).Average / $interval
As i already said on Twitter, thank you!
The only thing i don´t get is the convertion from canonical names to datastore names. i “studied” the other post you mentioned but as i already said, i don´t get it. can you point me out in which section of the lun report script you are doing the conversion?
thanks!
Gernot
@Gernot, I updated the script. It now shows the datastorename as well.
Great Stuff! Is there a way to export-csv the three columns?
Thanks Troy.
Sure, change the last line (20) into something like this
$report | Export-Csv "C:\IOPSMax-report.csv" -NoTypeInformation -UseCulture