Home > IOPS, LUN, performance, PowerShell > Get the maximum IOPS

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
  1. Mandeep
    May 19th, 2013 at 12:46 | #1

    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

  2. Matt M
    February 20th, 2013 at 15:14 | #2

    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?

    • February 20th, 2013 at 15:20 | #3

      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.

  3. sandesh
    August 11th, 2012 at 20:03 | #4

    Hi LucD,

    How can I get disk size details in same script

  4. KennyF
    August 7th, 2012 at 17:30 | #5

    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

  5. Jebaz
    August 6th, 2012 at 14:03 | #6

    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.

  6. Sandesh
    August 1st, 2012 at 12:38 | #7

    @ LucD

    How can I get the disk size information in this script.

  7. MrG
    May 15th, 2012 at 15:36 | #8

    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

  8. BerreB
    April 23rd, 2012 at 16:06 | #10

    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.

  9. Harry
    March 28th, 2012 at 07:33 | #11

    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

    • March 28th, 2012 at 16:30 | #12

      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.

  10. January 13th, 2012 at 21:40 | #13

    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

    • January 13th, 2012 at 21:52 | #14

      @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 ?

  11. January 11th, 2012 at 10:45 | #15

    Great script, saved me a bunch of time today….. now time to go investigate some ‘top talkers’. :)

    Cheers,
    Doug

  12. Jbuddy
    October 31st, 2011 at 18:49 | #16

    @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

    • October 31st, 2011 at 18:54 | #17

      @Jbuddy, great. Glad you have it working now.

  13. Jbuddy
    October 29th, 2011 at 01:33 | #18

    @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

    • October 29th, 2011 at 07:19 | #19

      @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.

  14. Jbuddy
    October 29th, 2011 at 00:15 | #20

    @LucD

    Ah ok thanks again

  15. Jbuddy
    October 28th, 2011 at 23:58 | #21

    @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

    • October 29th, 2011 at 00:06 | #22

      @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.

  16. Jbuddy
    October 28th, 2011 at 19:50 | #23

    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

    • October 28th, 2011 at 23:30 | #24

      @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.

  17. Darren Phillips
    September 23rd, 2011 at 10:25 | #25

    Hi Luc

    Would possible to amend this script to get the IOPS per VMDK file per VM.

    Thanks
    Darren

  18. Happy
    July 2nd, 2011 at 00:02 | #26

    This is great stuff but I need it to work with VMs in a NFS Datastore. Can this be done?

    • July 5th, 2011 at 23:00 | #27

      @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.

  19. Pawel
    June 29th, 2011 at 22:07 | #28

    @LucD
    That’s exactly what I was looking for. Thanks.

  20. Pawel
    June 29th, 2011 at 04:21 | #29

    Is there a way to get the average read and write ratios? Thanks.

    • June 29th, 2011 at 21:47 | #30

      @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.

  21. June 14th, 2011 at 04:39 | #31

    @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?

  22. June 12th, 2011 at 17:06 | #32

    @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?

    • June 13th, 2011 at 16:02 | #33

      @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

  23. June 12th, 2011 at 05:32 | #34

    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

    • June 12th, 2011 at 08:59 | #35

      You’re welcome. Thanks for sharing your version in your post.

  24. Paul
    May 4th, 2011 at 19:06 | #36

    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

  25. tuensel2k
    May 4th, 2011 at 14:22 | #37

    Awsome script!

    Is it possible to also calculate the average iops?

    This would be great addition for my daily storage reporting!

    • May 4th, 2011 at 14:35 | #38

      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

  26. Gernot Nusshall
    April 26th, 2011 at 11:15 | #39

    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

    • April 26th, 2011 at 18:34 | #40

      @Gernot, I updated the script. It now shows the datastorename as well.

  27. Troy Clavell
    April 25th, 2011 at 17:33 | #41

    Great Stuff! Is there a way to export-csv the three columns?

    • April 25th, 2011 at 17:46 | #42

      Thanks Troy.
      Sure, change the last line (20) into something like this

      $report | Export-Csv "C:\IOPSMax-report.csv" -NoTypeInformation -UseCulture

  1. May 3rd, 2011 at 00:58 | #1
  2. June 13th, 2011 at 17:46 | #2
  3. June 12th, 2012 at 20:52 | #3