PowerShell: the issues you bump into when using Get-Counter and Measure-Object aggregation
Posted by jpluimers on 2019/07/17
Single process data
You can get many process properties using , for instance the total running time in minutes [WayBack] Get-Process:
New-TimeSpan -Start (Get-Process "devenv").StartTime | Select-Object -ExpandProperty "TotalMinutes"
Actually, the it matches multiple process instances, then you need to aggregate, for instance using this:
Get-Process "cmd" | ForEach-Object { New-TimeSpan -Start $_.StartTime } | Measure-Object -Property "TotalMinutes" -Sum | Select-Object -ExpandProperty "Sum"
If you want to see which properties are available, then use either of these:
Get-Process | Select-Object * | Out-GridView
Get-Process | Get-Member
Getting aggregated process CPU usage times
Lets start getting the total CPU time of all processes by using Get-Process
which – without arguments – returns data for all processes it has access to:
PS C:\Users\Developer> Get-Process | Measure-Object -Property CPU -Sum
You’d think this gives only the sum. Wrong:
Count : 80
Average :
Sum : 2410.5625
Maximum :
Minimum :
Property : CPU
Ensure you get data for all processes
And you need it with an Administrative UAC token in order to get all processes on the system. It doesn’t show many more processes, but a lot more CPU time since boot:
Count : 83
Average :
Sum : 6078.125
Maximum :
Minimum :
Property : CPU
Showing one aggregate
So how to get rid of [WayBack] Measure-Object showing all aggregates?
The only stable solution I found is to expand that into | Measure-Object -Property CPU -Sum | Select-Object -ExpandProperty Sum
which I found in the answer by davidhigh at [WayBack] Listing processes by CPU usage percentage in powershell – Stack Overflow and got me this by using [WayBack] Select-Object.
Note you have to use -ExpandProperty
as just -Property
will not cut it as you get an empty result (I think this has to do with Sum
being a nullable type: Sum Property System.Nullable[double]
):
PS C:\Windows\system32> Get-Process | Measure-Object -Property CPU -Sum | Select-Object -Property Sum
PS C:\Windows\system32> Get-Process | Measure-Object -Property CPU -Sum | Select-Object -ExpandProperty Sum
6126.40625
CPU % is hard
You’d think a CPU % is an easy thing to measure as it has been available in the Task Manager for such a long time, but it’s harder to determine than for instance process memory. Let me try to explain that (please post comments when it is unclear).
For process memory, you can measure it at the current instant: if you freeze the CPU, you can count the number of bytes used by the process.
For relative CPU usage however, you need to measure how a process executes over time. During that time, CPU usage may (or will) vary a little bit, so you need to measure over a little time-delta. If CPU core frequencies vary over time, you need to adjust for that so it gets even more difficult. Two links with a bit more background information on this:
- [WayBack] Interpreting CPU Utilization for Performance Analysis – Windows Server Performance Team Blog
- [WayBack] How Linux CPU Usage Time and Percentage is calculated · Leo-G/DevopsWiki Wiki
I think the above difference is the reason that Get-Process
doesn’t return a CPU percentage.
Performance Counters using Get-Counter
Instead, you can use [WayBack] Get-Counter to get processor CounterSamples
like this:
(Get-Counter '\Process(*)\% Processor Time').CounterSamples
It gets you the % Processor Time
which is the percentage of CPU determined by a CPU [WayBack] Hardware performance counter.
Filtering results
If you want this for a specific process, you first need to find out which processes exist. You can do that by getting the unique InstanceName
property values by using [WayBack] Sort-Object:
(Get-Counter '\Process(*)\% Processor Time').CounterSamples | Select-Object -Property InstanceName | Sort-Object -Property InstanceName -Unique
(Here you do not need the -ExpandProperty
on Select-Object
as the -Property
suffices)
Special InstanceName
values
There are always entries with InstanceName
having values _total
and idle
:
(Get-Counter '\Process(*)\% Processor Time').CounterSamples | Where-Object {$_.InstanceName -eq "_total" }
(Get-Counter '\Process(*)\% Processor Time').CounterSamples | Where-Object {$_.InstanceName -eq "idle" }
You can even get both at the same time by using the -or
operator with [WayBack] Where-Object:
(Get-Counter '\Process(*)\% Processor Time').CounterSamples | Where-Object {($_.InstanceName -eq "_total") -or ($_.InstanceName -eq "idle") }
Since the outputs are CPU core based, and sampled over time, they can be above 100%, heck even above the 100% * amount-of-CPU-cores as seen on a 2-core CPU system:
Path InstanceName CookedValue ---- ------------ ----------- \\w81entx64vs2015\process(idle)\% processor time idle 186.168932138452 \\w81entx64vs2015\process(_total)\% processor time _total 200.016208082634
You see that the CookedValue
is what you’re after. If you just want the value for one instance, just use the -ExpandProperty
trick shown above:
(Get-Counter '\Process(*)\% Processor Time').CounterSamples | Where-Object {$_.InstanceName -eq "_total" } | Select-Object -ExpandProperty CookedValue
Accommodating for multi-instance processes
(Get-Counter '\Process(*)\% Processor Time').CounterSamples | Where-Object {$_.InstanceName -eq "svchost" } | Select-Object -ExpandProperty CookedValue
This gets you a list like this:
PS C:\Windows\system32> (Get-Counter '\Process(*)\% Processor Time').CounterSamples | Where-Object {$_.InstanceName -eq "svchost" } | Select-Object -ExpandProperty CookedValue
0
0
...
0
0
For that, you need to aggregate by using Measure-Object
like described in the Get-Process
section:
PS C:\Windows\system32> (Get-Counter '\Process(*)\% Processor Time').CounterSamples | Where-Object {$_.InstanceName -eq "svchost" } | Measure-Object -Property CookedValue -Sum | Select-Object -ExpandProperty Sum
0
A more elaborate example of aggregating is at [Archive.is] ExportPerfomanceCountersHelpers.ps1.
Aggregated output usually works
This works for a single-instance process as well:
(Get-Counter '\Process(*)\% Processor Time').CounterSamples | Where-Object {$_.InstanceName -eq "lsass" } | Measure-Object -Property CookedValue -Sum | Select-Object -ExpandProperty Sum
Example output:
PS C:\Windows\system32> (Get-Counter '\Process(*)\% Processor Time').CounterSamples | Where-Object {$_.InstanceName -eq "lsass" } | Measure-Object -Property CookedValue -Sum | Select-Object -ExpandProperty Sum
0
The dreaded error
The above often works, but sometimes you get an error, often even earlier in the process.
The error is always like Get-Counter : The data in one of the performance counter samples is not valid. View the Status property for each PerformanceCounterSample object to make sure it contains valid data.
:
PS C:\Windows\system32> (Get-Counter '\Process(*)\% Processor Time').CounterSamples | Where-Object {$_.InstanceName -eq "lsass" } | Measure-Object -Property CookedValue -Sum | Select-Object -ExpandProperty Sum Get-Counter : The data in one of the performance counter samples is not valid. View the Status property for each PerformanceCounterSample object to make sure it contains valid data. At line:1 char:2 + (Get-Counter '\Process(*)\% Processor Time').CounterSamples | Where-Object {$_.I ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidResult: (:) [Get-Counter], Exception + FullyQualifiedErrorId : CounterApiError,Microsoft.PowerShell.Commands.GetCounterCommand 0
It took me a while to figure this out, but the root cause is that in-between Get-Counter
retrieving the hardware process counters and processing the values, the CPU continues executing thereby changing some of the values. For instance, processes can quit (the CPU is not aware of processes, so binding CPU thread information from the hardware process counters to logical processes can fail in that step).
These links helped me tremendously as the -ErrorAction SilentlyContinue
is not documented by Microsoft at Get-Counter
:
- The answer by Paweł Dyl at [WayBack]windows – Continuously monitors the CPU usage % of top X processes – Stack Overflow
- [WayBack] Diagnosing applications using Performance Counters – CodeProject (which could not workaround this problem)
- [WayBack] Handling Errors the PowerShell Way – Hey, Scripting Guy! Blog explaining you can use this option anywhere in your PowerShell script
Summary
Always use a construct like this to search for CPU usages on one or more processes named powershell
:
(Get-Counter '\Process(*)\% Processor Time' -ErrorAction SilentlyContinue).CounterSamples | Where-Object {$_.InstanceName -eq "powershell" } | Measure-Object -Property CookedValue -Sum | Select-Object -ExpandProperty Sum
–jeroen
Leave a comment