Discover Memory Overallocations

With the vSphere 5 licensing buzz from the past days and the incredibel number of hits on my Query vRAM post, I considered that a script to help you discover your memory overallocations might be useful.

The script uses the metric mem.usage.average to find out what amount of it’s allocated memory a guest is actually using. The script produces a report that will help you to determine which guests would be good candidates to lower their memory allocation.

The script

Annotations

Line 3: By default the report contains all guests in your vCenter

Line 4: The default time option is Realtime.

Line 12-13: The function collects all the guests for which you want a report.

Line 15-36: The script retrieves the statistical data, taking into account the selected time interval.

Line 38: The statistical data is organised in groups based on the EntityId.

Line 39-50: A guest that was offline during the time interval will have no statistical data.

Line 51-58: The result is placed in the pipeline.

Sample usage

The use of the function is rather straightforward.

By default the function will report on all guests in your vCenter. With the LocationName parameter you specify a datacenter, a cluster, a resourcepool, a folder and even an ESX(i) host. The function will then return the information for all guests under that location.

I also decided to provide some switches to let you select the timeframe the function should report on.

Realtime

In this mode, which is also the deault mode, you get the most recent value for the mem.usage.average metric.

You call the function like this

or like this

Both of these will report on all the guests in your vCenter.

If you want to limit the guests, you use the LocationName parameter.

For example, to only report on the guests in a datacenter called MyDC, you would do.

Day

If you want to report on the average memory usage of the past day, you do

Week

Similarly, if you want a report over the past week, you do

Month

And finally, to get the report calculated over a month’s worth of data you do

Output

By default the report will be displayed on the console. It looks something like this.

But you can also redirect the output to,  for example a CSV file.

Which shows a similar result but of course in a CSV file.

From this type of report it should be obvious for which guest lowering the memory allocation would be an option to consider.

56 Comments

    Ravikanth

    Hi Luc,

    how can i retrieve the information for past 3 or 6 months

      LucD

      Hi Ravikanth,
      The function only provides switches for the last day/week/month I’m afraid.
      But if you have the statistical data available on your VCSA, you could adapt the value on the Start parameter of the Get-Stat cmdlet.
      Instead of doing AddDays(-31), you could do AddDays(3 * -31).

    FinnC

    Hi LucD,

    For the environment I am working in however I need to connect to multiple vCenters.
    As you can imagine, there are thousands of VMs across these vCenters.

    So far;
    I have configured Powershell to connect to multiple vCenters at the same time using the below;

    Set-PowerCLIConfiguration -ProxyPolicy NoProxy -DefaultVIServerMode Multiple -confirm:$false

    I then listed all the vCenters in I want to connect to in a txt file and created a variable containing this txt file. See below;

    $VCenterlist = Get-Content C:\Scripts\Scheduled_Tasks\VMware_Monitoring\Live_Monitoring_Scripts\VCenterlist.txt

    I then connect to the vCenters using stored credentials using the below;

    Foreach ($vcenter in $VCenterlist)

    {
    if( Connect-VIServer -server $vcenter -Protocol https -ErrorAction Ignore)
    {
    Write-Host “vCenter $vcenter Connected” -ForegroundColor Cyan
    }
    else
    {
    Write-Host “Failed to Connect vCenter $vcenter” -ForegroundColor Cyan
    }
    }

    Once connected to the required vCenters I am then able to get the Average CPU Usage, Average Memory Usage and also the Average Network Usage using the below;

    Get-VM | Where {$_.PowerState -eq “PoweredOn”} | Select Name, NumCpu, MemoryMB,
    @{N="CPU Usage (Average), Mhz" ; E={[Math]::Round((($_ | Get-Stat -Stat cpu.usagemhz.average -Start (Get-Date).AddDays(-90) -IntervalMins 5 | Measure-Object Value -Average).Average),2)}},

    @{N=”Memory Usage (Average), %” ; E={[Math]::Round((($_ | Get-Stat -Stat mem.usage.average -Start (Get-Date).AddDays(-90) -IntervalMins 5 | Measure-Object Value -Average).Average),2)}} ,
    @{N="Network Usage (Average), KBps" ; E={[Math]::Round((($_ | Get-Stat -Stat net.usage.average -Start (Get-Date).AddDays(-90) -IntervalMins 5 | Measure-Object Value -Average).Average),2)}}

    I then Export these results to CSV using the below;
    Export-Csv -Path C:\Scripts\Scheduled_Tasks\VMware_Monitoring\Live_Monitoring_Scripts\VM_Average_Usage_Stats.csv

    and convert it to a HTML file with the below;
    $css = @"

    h1, h5, th { text-align: center; font-family: Segoe UI; }
    table { margin: auto; font-family: Segoe UI; box-shadow: 10px 10px 5px #888; border: thin ridge grey; }
    th { background: #0046c3; color: #fff; max-width: 400px; padding: 5px 10px; }
    td { font-size: 11px; padding: 5px 20px; color: #000; }
    tr { background: #b8d1f3; }
    tr:nth-child(even) { background: #dae5f4; }
    tr:nth-child(odd) { background: #b8d1f3; }

    "@

    Import-CSV "C:\Scripts\Scheduled_Tasks\VMware_Monitoring\Live_Monitoring_Scripts\VM_Average_Usage_Stats.csv" -Delimiter ',' | ConvertTo-Html -Head $css -Body "Email ReportnGenerated on $(Get-Date)” | Out-File “C:\Scripts\Scheduled_Tasks\VMware_Monitoring\Live_Monitoring_Scripts\Server_Health_Report_VMware_6.7_Environment.html”

    All of that works great, however, I would like to customize the script to only export the VMs which have an average memory utilization of below 10% (over the space of 3 months).
    My reasoning for this is that I would like to minimize the VMs which are listed in the report. I only want to show the VMs which may need to be looked at.
    At the moment the report list far to many VMs and is not really working as it takes time to go through it and find the VMs which need to be looked at.

    Do you know of a way for me to get the desired result??

    Below is the script I have so far;
    #########################################################################
    ## Server Health Check ##
    ## This scripts check the server Avrg CPU, Memory & Network Utlization ##
    #########################################################################

    $VCenterlist = Get-Content C:\Scripts\Scheduled_Tasks\VMware_Monitoring\Live_Monitoring_Scripts\VCenterlist.txt

    Foreach ($vcenter in $VCenterlist)

    {
    if( Connect-VIServer -server $vcenter -Protocol https -ErrorAction Ignore)
    {
    Write-Host “vCenter $vcenter Connected” -ForegroundColor Cyan
    }
    else
    {
    Write-Host “Failed to Connect vCenter $vcenter” -ForegroundColor Cyan
    }
    }

    {

    Get-VM | Where {$_.PowerState -eq “PoweredOn”} | Select Name, NumCpu, MemoryMB,
    @{N="CPU Usage (Average), Mhz" ; E={[Math]::Round((($_ | Get-Stat -Stat cpu.usagemhz.average -Start (Get-Date).AddDays(-1) -IntervalMins 5 | Measure-Object Value -Average).Average),2)}},

    @{N=”Memory Usage (Average), %” ; E={[Math]::Round((($_ | Get-Stat -Stat mem.usage.average -Start (Get-Date).AddDays(-1) -IntervalMins 5 | Measure-Object Value -Average).Average),2)}} ,
    @{N="Network Usage (Average), KBps" ; E={[Math]::Round((($_ | Get-Stat -Stat net.usage.average -Start (Get-Date).AddDays(-1) -IntervalMins 5 | Measure-Object Value -Average).Average),2)}} |

    Export-Csv -Path C:\Scripts\Scheduled_Tasks\VMware_Monitoring\Live_Monitoring_Scripts\VM_Average_Usage_Stats.csv

    }

    $css = @”

    h1, h5, th { text-align: center; font-family: Segoe UI; }
    table { margin: auto; font-family: Segoe UI; box-shadow: 10px 10px 5px #888; border: thin ridge grey; }
    th { background: #0046c3; color: #fff; max-width: 400px; padding: 5px 10px; }
    td { font-size: 11px; padding: 5px 20px; color: #000; }
    tr { background: #b8d1f3; }
    tr:nth-child(even) { background: #dae5f4; }
    tr:nth-child(odd) { background: #b8d1f3; }

    “@

    Import-CSV “\\IECM1AP101.tycoelectronics.net\f$\Scripts\Scheduled_Tasks\TEIS_VMware_Monitoring\Live_Monitoring_Scripts\VM_Average_Usage_Stats.csv” -Delimiter ‘,’ | ConvertTo-Html -Head $css -Body “Email Report`nGenerated on $(Get-Date)” | Out-File “\\IECM1AP101.tycoelectronics.net\f$\Scripts\Scheduled_Tasks\TEIS_VMware_Monitoring\Live_Monitoring_Scripts\Server_Health_Report_TEIS_VMware_6.7_Environment.html”

      LucD

      You should be able to do that with a Where-clause.
      Something like this


      Get-VM | Where-Object { $_.PowerState -eq “PoweredOn” } |
      Select-Object Name, NumCpu, MemoryMB,
      @{N = "CPU Usage (Average), Mhz" ; E = {
      [Math]::Round((($_ | Get-Stat -Stat cpu.usagemhz.average -Start (Get-Date).AddDays(-1) -IntervalMins 5 |
      Measure-Object Value -Average).Average), 2)
      } },
      @{N = ”Memory Usage (Average), %” ; E = {
      [Math]::Round((($_ | Get-Stat -Stat mem.usage.average -Start (Get-Date).AddDays(-1) -IntervalMins 5 |
      Measure-Object Value -Average).Average), 2)
      } } ,
      @{N = "Network Usage (Average), KBps" ; E = {
      [Math]::Round((($_ | Get-Stat -Stat net.usage.average -Start (Get-Date).AddDays(-1) -IntervalMins 5 |
      Measure-Object Value -Average).Average), 2)
      } } |
      Where-Object {$_.'Memory Usage (Average), %' -lt 10} |
      Export-Csv -Path C:\Scripts\Scheduled_Tasks\VMware_Monitoring\Live_Monitoring_Scripts\VM_Average_Usage_Stats.csv

      Vikram Dhandapani

      I think if you are trying to execute the script to pull out a historical report which is >30 days, you must use the time interval to be 120 minutes. If this is already noted, please ignore my suggestion

        LucD

        No, I don’t think you need to do that.
        What is true is that due to aggregation there will only be data for 1 day intervals available.
        But you can still specify another interval.

    Ilnur

    Result: Nodata for all VMs:((

    TimeFrame Name PowerState MemAvgUsedGB MemAvgUsedPerc MemConfiguredGB
    Month VM_Name_Test PoweredOff No data

    im write in script my host name
    function Get-VMMemUse{
    param(
    [string]$LocationName = "ESXHost_Name"

    and run command:
    Get-VMMemUse -Month | Export-Csv "C:\PowerCLI\Report\memOverlocations.csv" -NoTypeInformation -UseCulture

    Whats wrong?

    Sanyam

    i want to calculate the maximum allocated size for the particular shared folder using powershell.
    can you share a code to get the maximum allocated size to any particular shared folder

      LucD

      Hi Sanyam,
      Can you be more specific, what kind of shared folder are you talking about ?
      Is that a Samba or NFS share for example, and do you have any access to the sharing host ?
      And please also defined what exactly you mean with “maximum allocated size”.

    Amit

    Hi Lucd,

    Can we provide date range? Need help for this.

    Thanks
    Amit

    Amit

    Superb script

      LucD

      Thanks

    steve

    Hi LucD.

    This is a very helpful script, can you include FolderName in the output.

    Cheers
    Steve

      steve

      anyone for the above?

    Owen

    I’m having difficulty running this script, which is a shame because it is exactly what i’m looking for!

    Calling “Get-Inventory -Name MyVMName | Get-VM” returns:

    Get-VM : The input object cannot be bound to any parameters for the command eit
    her because the command does not take pipeline input or the input and its prope
    rties do not match any of the parameters that take pipeline input.
    At line:1 char:49

    If I append Get-VMMemUse to the end of the script, then call the script using:
    ./memusescrupt.ps1 -LocationName MyDC -Day
    It will run for hours and hours (we have 1000s) of VMs. Even specifying a folder or resourcepool with only a handful of VMs takes too long, which leads me to think that it is still running it against the entire DC.

    Any thoughts?

      LucD

      Hi Owen,
      When you just do ‘Get-Inventory -Name MyVMName’ it will return a VirtualMachine object. That is sufficient, no need to do a Get-VM after that.
      The error message comes from the fact that the Get-VM cmdlet doesn’t have any parameter that accepts a VirtualMachine through the pipeline.

      Did you define the parameters in the .ps1 file ?
      In fact the .ps1 file should look something like this

      param($location)

      function Get-VMMemUse {
      # My function code
      }

      Get-VMMemUse -LocationName $location -Day

      Then you can do

      ./memusescrupt.ps1 -Location MyDC

    Santhosh

    Can someone help me modify the script to include the Peak Mem Usage.

    Bill

    Hi, I am trying to use this really nice script and I did change the name of it. That being said, I am trying to redirect the output to a csv file and get the following error:

    Missing function body in function declaration.
    At C:\Users\bseeley_c_p\Documents\Scripts\vm_mem_use.ps1:1 char:23
    + function Get-VMMemUse <<<< | Export-Csv"C:\report.csv" -NoTypeInformation
    + CategoryInfo : ParserError: (:) [], ParseException
    + FullyQualifiedErrorId : MissingFunctionBody

    eulcedes

    Very helpful script! BUT i’m getting the same error as Virag!
    “Cannot index into a null array.
    at *script location*:43 char:37
    + $tgtVM = $groups | where {$_.Group[ <<<< 0].Entity.Name -eq $vm.Name}
    + CategoryInfo : InvalidOperation: (0:Int32) [], RuntimeException
    + FullyQualifiedErrorId : NullArray"

    This occurs with any time range, and if i try to have it display in-window OR export to file.
    Any suggestions? Am I missing something easy here?

    hermouet

    HI,

    this script can’t calcul balloning memory ? because when vm is very slow often time it’s because memory are swapped to storage no ?

    for exe vm with 5gb of ram, and 4gb of dedicated, there is 1gb right on disk storage… so i want to see on all vm which server is used this “1gb” of swapped ram. i deduce it name is “balloning” exact ?

    tks advance

      LucD

      @Hermouet, I think the answer is a bit more complex. Swapping is not always bad.
      Have a look at Understanding Memory Management in VMware vSphere 5, it explains the memory management techniques that are available to the hypervisor.

    Virag Jain

    getting this error :

    Cannot index into a null array.
    + $tgtVM = $groups | where {$_.Group[ <<<< 0].Entity.Name -eq $vm.Name}
    + CategoryInfo : InvalidOperation: (0:Int32) [], RuntimeException
    + FullyQualifiedErrorId : NullArray

    Owen

    Great script LucD – thanks!
    Though it seems that no matter which vCenter and LocationName I specify, it reports back on every VM in the datacenter listing. Also need to butcher the script to force it to run for a specific timeframe, IE the -Month or -Week arguments don’t seem to work. Any ideas?

    suresh

    Hi, Can we get the Memory Usage report in Cluster level using Script .. like assigned memory to VMs and Actual VM’s usage memory and free memory in cluster level.
    Thanks
    Suresh

    Roderick

    Hi Luc,
    Thanks for publishing the script!

    Question: when I look at the MemAvgUsedGB and MemAvgUsedPerc results then I can’t help thinking that the numbers are so low.

    For example: For a certain VM the MemAvgUsedGB is 0,4 and the configured memory is 2GB. When I lower the amount of memory for this VM to 1,5 GB the guest OS starts to swap and performance drops. This scenario can be reproduced with other VM’s.

    Can you explain the MemAvgUsedPerc and MemAvgUsedGB this?

    Grtz,
    Roderick

      LucD

      @Roderick, that is indeed strange.
      Question, did you run the report with the Realtime, Day, Week or Month switch ?

    Maestriweb

    An all round well written article..

    Mr Major

    Hi LucD!

    Excellent script. Can you add peak utilization to your report’s results? We need to be able to see any deviation from the avgs…..

      LucD

      Check my answer to kfkernel below, that provides the peakuse values.

    kfkernel

    @LucD
    Thank you works perfect!

    kfkernel

    Hi,

    Thank you for yet another good script!

    I wanted to also show the maximum % of used memory. I tried to add the following lines to the script.


    $used = [math]::Round($vm.MemoryMB * $percentage / 100 / 1KB, 1)
    $peakuse = $tgtVM.Group | sort-object -Descending
    $peakuse = $peakuse[0].Value
    }

    and of course adding $peakuse to the new object. It produces some output but it seem to fail with identifying the maximum value. I cross checked one machine, in the csv file it reports 9.66 % PeakUsage. But when i look in vCenter 1 month graph peak is 22%

    Any other ideas on how to get the Peak Usage?

      LucD

      @kfkernel, try using the following

      if($tgtVM){
      $percentage = [math]::Round(($tgtVM.Group | Measure-Object -Property Value -Average).Average,1)
      $allocated = [math]::Round($vm.MemoryMB/1KB,1)
      $used = [math]::Round($vm.MemoryMB * $percentage / 100 / 1KB, 1)
      $peakuse = [math]::Round(@($tgtVM.Group | Sort-Object -Property Value -Descending)[0].Value,1)
      }

      You have to tell the Sort-Object cmdlet on which property to sort.

    Harry

    Great script, exactly what I was looking for, working like a charm.
    Thanks

    James

    Did see one warning:
    WARNING: ‘Entity’ property is obsolete. Use ‘EntityId’ instead.

    But otherwise seemed to work well! thanks!! great script again!

      LucD

      @James, thanks.
      What you see is a indeed a warning and it doesn’t break the script.
      To use EntityId would have made the script slightly more complex, so I opted to leave the Entity in there for now.

    Eugene

    @LucD
    Never got your email. Still having the same blank output issue 🙁

    Ian

    Hi,

    Great script but just on question. How do I run it? I am connected to vCenter. I have copied the script to a text file and saved it as a .ps1 and bat. When I run Get-VMMemUse I get an error “The term ‘Get-VMMemUse’ is not recognized as the name of a cmdlet, function, script file.

    Thanks,

      LucD

      @Ian, thanks.
      The script is in fact a function and you have 2 ways of using it.
      1) You save it in a .ps1 file and at the end you add the call to the function

      function Get-VMMemUse{
      ...
      }
      Get-VMMemUse

      You invoke the script from PowerCLI prompt:

      PS> .\myfile.ps1

      2) You save only the function in a .ps1 file.
      To get the function into your PowerCLI session, you will have to dot-source the .ps1 file

      PS> . ./myfile.ps1

      That is a dot, followed by a space and then the .ps1 file
      After that the function is know in your session. You can check with

      PS> Get-Function

      To call the function, you just call it from the PowerCLI prompt

      PS> Get-VMMemUse

    Matt

    Hey there, nice script. Only vice I have is that, even if I specify day, month etc, it always appears to list realtime in the CSV output? is this only cosmetic, or is it ignoring the period flag?

      LucD

      @Matt, no it wasn’t cosmetic, it was a real bug 🙁
      Thanks for discovering the flaw, the script has been corrected.

    Conrad

    Pretty Nice Script! Beats look at a bunch of graphs in vcenter

    KevinS

    @KevinS
    Thanks Luc for the help with my interface between chair and keyboard

      LucD

      Thanks for the feedback Kevin, glad the problem is solved.

    KevinS

    when I run the script I get no output. This seems similar to other post.

    Ron Davis

    This may be a dumb question, but why are you looking at cpu.usage.average to determine memory usage?

      LucD

      @Ron, not dumb, that was a typo. It’s corrected now.
      Sorry about that.

    Eugene

    Eugene :
    Nothing happens when I try to run this.

    I feel like it’s also user error on my part but still not having luck.

    I am connected to vcenter and I did try both Get-VMMemUse -LocationName MyDC and .\Get-VMMemUse -LocationName MyDC (replacing MyDC with my actual DC name). Getting no output.

      LucD

      @Eugene, send you an email.
      Let’s discuss the details offline.

    John K

    @LucD
    Definitely user error on my part. The script works perfectly. Thanks!

      LucD

      @John, thanks for letting us know.
      I hope the report allows you to find the memory gluttons in your environment 🙂

    John K

    I’m also getting no output when using my datacenter, cluster, or hostname for locationName; after connecting to my VC.

      LucD

      @John, can you share how you called the function ?
      Does the following return a list of guests ?

      Get-Inventory -Name anyname | Get-VM

    Troy Clavell

    Another great script! Does that mean I owe you more pints at VMworld?

      LucD

      @Troy, this goes on the same tab 😉

    Eugene

    Nothing happens when I try to run this.

      LucD

      @Eugene, could you give some more details ?
      How did you call the function ? Did you use the LocationName ?
      Were you connected to the vCenter ?

Leave a Reply

Your email address will not be published. Required fields are marked *

*
*

This site uses Akismet to reduce spam. Learn how your comment data is processed.