2015-11-16

Windows PerfCounters and Powershell - Fetching the values

Summary from last blog:

  • Tip: An alias for Get-CimInstance is GCim, for Select-Object it's Select and for Get-WmiObject is GWmi.
  • There are Raw and Formatted counters. Watch out for formula converting Raw samples to Formatted values.


NAMESPACE organization

The general organization of namespaces is as follows:
  Category (Class if you prefer)
    Counter(s)
      Instance(s)
Every Category has Counters but not all of the Counters have Instances. The full path to the desired value is called a __PATH:
PS > GWmi Win32_PerfFormattedData_PerfOS_Processor | Select __Path

__PATH
------
      namespace                  Category/Class         InstanceName
\\localhost\root\cimv2:Win32_PerfFormattedData_PerfOS_Processor.Name="0"
...
\\.\root\cimv2:Win32_PerfFormattedData_PerfOS_Processor.Name="_Total"
Note: "." and "localhost" are synonyms.
Note: Knowing paths will allow us to move faster around as well as writing WMI queries as I will demonstrate later.


Putting .NET to work

Using System.Diagnostics.PerformanceCounterCategory class (which I prefer for fetching single values that update often) lets take two Categories as an example.
Note: I saw a lot of questions regarding WMIRefresher that exists in VB and has no apparent counterpart in Powershell and this is it, IMO, since variable pointing to counter object holds (lightweight) connection to path reducing the overhead when fetching next value. Another good example of refreshing WMI data is described in Tip 10/c. This "trick" will do the fetch of entire Processor Information category in approximately 0.3s on my laptop which is about as fast as it gets. Remember that the first execution usually takes a while (couple of seconds).
PS > [System.Diagnostics.PerformanceCounterCategory]::GetCategories() |  Select  CategoryName | Sort CategoryName

CategoryName
------------
.NET CLR Data
.NET CLR Security
.NET Data Provider for Oracle
Cache
Memory
Network Interface
Objects
PhysicalDisk
Process
Thread
.... there are 91 in total.
PS > $Category = "Memory"
PS > (New-Object Diagnostics.PerformanceCounterCategory($Category)).GetCounters("") |
  Select  CounterName | Sort CounterName

CounterName
-----------
Available Bytes
Cache Bytes
Cache Faults/sec
Modified Page List Bytes
Page Faults/sec
Pages/sec
Pool Nonpaged Bytes
Standby Cache Reserve Bytes
System Driver Resident Bytes
Write Copies/sec
.... there are 35 in total.
PS > (New-Object Diagnostics.PerformanceCounterCategory($Category)).GetInstanceNames()

PS >
So, Memory category has 35 Counters and no Instances which means you can fetch values directly:
PS > $tmp = (New-Object Diagnostics.PerformanceCounter($Category, "Available MBytes")
PS > $tmp.NextValue()

5009
Note: Available MBytes is ever updating value not dependant on number of samples. Thus calling NextValue() was unnecessary but good practice.

Fetching data from Processor Information category is a bit different:
PS > $Category = "Processor Information"
PS > (New-Object Diagnostics.PerformanceCounterCategory($Category)).GetInstanceNames() | Sort

_Total
0,_Total
0,0
0,1
0,2
0,3
Processor Information has 6 instances which means I am writing this on dual-core laptop with HT enabled (2 physical CPUs, 2 logical CPU's and the 2 Totals). It is rather interesting to play with these counters on proper servers. For now, important thing is to notice that "_Total" stands for entire box, "N, _Total" represents "Socket N" instance while "N,M" stands for "Socket,CPU" instance.

To fetch particular perf-counter value, I have to provide InstanceName:
PS > $Category = "Processor Information"
PS > $InstanceName = "_Total"
PS > $tmp = New-Object Diagnostics.PerformanceCounter($Category, "% Processor Time")
PS > $tmp.InstanceName = $InstanceName
PS > $tmp.NextValue()

21.97821
Tip:There is an overload allowing you to write New-Object Diagnostics.PerformanceCounter("Processor Information", "% Processor Time", "_Total") instead of providing Category/InstanceName members via variables.

Let's check on New-Object members:
PS > $tmp

CategoryName     : Processor Information
CounterHelp      : % Processor Time is the percentage of elapsed
time that the processor spends to execute a non-Idle thread. It is
calculated by measuring the percentage of time that the processor spends
executing the idle thread and ...
CounterName      : % Processor Time
CounterType      : Timer100NsInverse
InstanceLifetime : Global
InstanceName     : Total
ReadOnly         : True
MachineName      : 
RawValue         : 131585400020
Site             : 
Container        : 
Note: It is always a good practice to read CounterHelp and make note of CounterType. This will tell you a lot about values obtained.
PS > $tmp.RawValue

131585502870
PS > $tmp.NextSample()

RawValue         : 131585517490
BaseValue        : 0
SystemFrequency  : 2533388
CounterFrequency : 2533388
CounterTimeStamp : 117273907751
TimeStamp        : 44097511588
TimeStamp100nSec : 130899951977398973
CounterType      : Timer100NsInverse
Note: Please check on previous blog, paragraph about Raw/Formatted counters and sampling.
Note: There is a plenitude of counters and you are free to explore them in search for one that suits your needs best. I.e. if you do not want to bother with Instances here, you can use Win32_PerfFormattedData_PerfOS_Processor Class which uses absolute Index to each CPU (see below):
PS > GCim Win32_PerfFormattedData_PerfOS_Processor | Select Name

Name
----
0
1
...
_Total

So, how do we tell if Counter has Instances?

PS > $Category = "Processor Information"
PS > New-Object Diagnostics.PerformanceCounterCategory($Category)

CategoryName CategoryHelp CategoryType MachineName
------------ ------------ ------------ -----------
Processor InformationThe Processor Information ...MultiInstance .
PS > $Category = "Memory" PS > New-Object Diagnostics.PerformanceCounterCategory($Category)
CategoryName CategoryHelp CategoryType MachineName
------------ ------------ ------------ -----------
Memory The Memory performance obj...SingleInstance .
The answer is obviously in CategoryType member of PerformanceCounterCategory class which you should check while iterating.
Note: The dot in MachineName stands for localhost.

You can check instance values directly with WMI too using their individual instance paths. Remember WMI classes: GWmi -List | Select Name | Where {$_.Name -match "Win32_PerfForm"}

Let's take Win32_PerfFormattedData_PerfOS_Processor for the example:
PS > GWmi Win32_PerfFormattedData_PerfOS_Processor | Select __Path

__PATH
------
\\localhost\root\cimv2:Win32_PerfFormattedData_PerfOS_Processor.Name="0"
...
\\.\root\cimv2:Win32_PerfFormattedData_PerfOS_Processor.Name="_Total"
Note: Remember that Path property begins with two underscores.

Now that you know the path to a WMI instance, you can access it directly by converting the WMI path to a WMI object:
PS > [WMI]'Win32_PerfFormattedData_PerfOS_Processor.Name="0"'

__GENUS               : 2
__CLASS               : Win32_PerfFormattedData_PerfOS_Processor
__SUPERCLASS          : Win32_PerfFormattedData
__DYNASTY             : CIM_StatisticalInformation
__RELPATH             : Win32_PerfFormattedData_PerfOS_Processor.Name="0"
__PROPERTY_COUNT      : 24
__DERIVATION          : {Win32_PerfFormattedData, Win32_Perf, CIM_StatisticalInformation}
__SERVER              : ...
__NAMESPACE           : root\cimv2
__PATH                : \\...\root\cimv2:Win32_PerfFormattedData_PerfOS_Processor.Name="0"
C1TransitionsPersec   : 263
DPCRate               : 0
DPCsQueuedPersec      : 27
InterruptsPersec      : 1054
Name                  : 0
PercentDPCTime        : 0
PercentIdleTime       : 99
PercentInterruptTime  : 0
PercentPrivilegedTime : 0
PercentProcessorTime  : 0
PercentUserTime       : 0
Note: __PROPERTY_COUNT : 24 means I trimmed some lines.
Pick one counter:
PS > ([WMI]'Win32_PerfFormattedData_PerfOS_Processor.Name="0"').PercentProcessorTime

68
PS > [WMI]'Win32_Service.Name="RemoteAccess"'

ExitCode  : 1077
Name      : RemoteAccess
ProcessId : 0
StartMode : Disabled
State     : Stopped
Status    : OK

PS > [WMI]'\\.\root\cimv2:Win32_LogicalDisk.DeviceID="C:"'

DeviceID     : C:
DriveType    : 3
ProviderName : 
FreeSpace    : 68152655872
Size         : 168037445632
VolumeName   : System
Note that '.' stands for 'localhost'. You can provide Server name here.
Note: You can also specify the full WMI path, including a machine name to access WMI objects on remote systems (provided you have sufficient access rights).

Tip: There is a hidden object property called "PSTypeNames" which will tell you the object type as well as the inheritance chain:
PS > (GWmi Win32_PhysicalMemory).PSTypeNames

System.Management.ManagementObject#root\cimv2\Win32_PhysicalMemory
System.Management.ManagementObject#root\cimv2\CIM_PhysicalMemory
System.Management.ManagementObject#root\cimv2\CIM_Chip
System.Management.ManagementObject#root\cimv2\CIM_PhysicalComponent
System.Management.ManagementObject#root\cimv2\CIM_PhysicalElement
System.Management.ManagementObject#root\cimv2\CIM_ManagedSystemElement
System.Management.ManagementObject#Win32_PhysicalMemory
System.Management.ManagementObject#CIM_PhysicalMemory
System.Management.ManagementObject#CIM_Chip
System.Management.ManagementObject#CIM_PhysicalComponent
System.Management.ManagementObject#CIM_PhysicalElement
System.Management.ManagementObject#CIM_ManagedSystemElement
System.Management.ManagementObject
System.Management.ManagementBaseObject
System.ComponentModel.Component
System.MarshalByRefObject
System.Object
Of course, the type listed at the top is telling you the most:
PS > (GWmi Win32_PhysicalMemory).PSTypeNames[0]

System.Management.ManagementObject#root\cimv2\Win32_PhysicalMemory
PSTypeNames will work for all objects and might come handy navigating namespaces.

Note: You can do things with WMI objects, not just read counters. Check, for example, Win32_LogicalDisk device Chkdsk method:
PS > ([WMI]'\\.\root\cimv2:Win32_LogicalDisk.DeviceID="C:"').Chkdsk
OverloadDefinitions
-------------------
System.Management.ManagementBaseObject Chkdsk(System.Boolean FixErrors,
System.Boolean VigorousIndexCheck, System.Boolean SkipFolderCycle,
System.Boolean ForceDismount, System.Boolean RecoverBadSectors,
System.Boolean OkToRunAtBootUp)
This functionality as well as accessing remote machines is beyond scope of the document and mentioned here just for the sake of completeness.

It is also worth noting you can call WMI methods with CIM cmdlets. Please see this POWERTIP for details if you're interested.

Useful WMI links:

Powertip 1, auto-discovering online help for wmi
Powertip 2, getting help on wmi methods


WQL

I mentioned earlier you can write your own WQL queries to fetch data from WMI objects. WQL is the WMI Query Language, a subset of the ANSI SQL with minor semantic changes.
GWmi Win32_Process -Filter "Name like ""power%.exe""" translates to WQL query 'select * from Win32_Process where Name like "power%.exe"'. So, to get process owner for example:
$processes = GWmi -Query 'select * from Win32_Process where Name like "power%.exe"'
$extraproc =
  ForEach ($process in $processes)
  {
    Add-Member -MemberType NoteProperty -Name Owner
    -Value (($process.GetOwner()).User)
    -InputObject $process -PassThru
  }
$extraproc | Select-Object -Property Name, Owner
Note: Make sure Add-Member ... -PassThrough line is not broken if you want this code to work.
Personally, I find WQL inadequate since it's missing aggregation functions thus I use it very rarely.


Summing it up

get classes:

GWmi -List
or
Get-CimClass | Select CIMClassName
or
[System.Diagnostics.PerformanceCounterCategory]::GetCategories()

shorten the list:

GWmi -List Win32_*memory* | Select Name
or
Get-CimClass | Select CIMClassName | Where {$_.CimClassName -match "memory"}
or
[System.Diagnostics.PerformanceCounterCategory]::GetCategories() | Where {$_.CategoryName -match "memory"} | Select CategoryName

list counters:

GWmi Win32_PhysicalMemory
or
GCim CIM_PhysicalMemory
or
(New-Object Diagnostics.PerformanceCounterCategory("Memory")).GetCounters("") | Select CounterName | Sort CounterName

and for WQL, write a query:

GWmi -Query 'select Manufacturer from Win32_PhysicalMemory where BankLabel = "BANK 0"'
or
GCim -Query 'Select Manufacturer from CIM_PhysicalMemory Where BankLabel = "BANK 0"'

Note: Opening communication and fetching objects from WMI server might take considerable amount of time. Counting CPU's takes at least few seconds on my boxes:
PS > Measure-Command {((GCim -Namespace root/CIMV2 -ClassName CIM_Processor).NumberOfLogicalProcessors | Measure-Object -Sum).Sum}
...
TotalSeconds : 2.2558991
...

Tip: The fastest way to learn how many CPUs there are on the box is (GCim Win32_ComputerSystem).NumberOfLogicalProcessors
Note: In my experience, best time to fetch some value (if not cached) is about 0.3 seconds so that's what I'm aiming for always.


Conclusion:

To speed up fetching counter data, where available, I like to use System.Diagnostics .NET class:
$System_CSpS = New-Object Diagnostics.PerformanceCounter("System", "Context Switches/sec")
$System_CSpS.NextValue()

Since some of the performance counters are empty upon first access, calling NextValue() is a good habit. Subsequent calls to $var.NextValue() are lightning fast so put it in variable. The next thing this technique is good for are values that are always there; such as amount of (free)RAM, Context Switches per second and so on. Although possible, I do not use this mechanism for values that might disappear, such as number of threads belonging to some process as process might die.
If you are not able to use System.Diagnostics or you prefer CIM approach you can always fall back to Tip 10/c:
PS > # Get instance of a class
PS > $p = Get-CimInstance -ClassName Win32_PerfFormattedData_PerfOS_Processor
PS > # Perform get again by passing the instance received earlier, and get the updated properties. The value of $p remains unchanged.
PS > $p | Get-CimInstance | select PercentProcessorTime

Beware that for counters with instances, you need to supply InstanceName:
$InstanceName = "_Total"
$PI_PT = New-Object Diagnostics.PerformanceCounter("Processor Information", "% Processor Time")
$PI_PT.InstanceName = $InstanceName
#or $PI_PT = New-Object Diagnostics.PerformanceCounter("Processor Information", "% Processor Time", "_Total")
$PI_PT.NextValue()

If you are interested in measuring just one value, Get-Counter is your friend but I prefer TAB completion of CIM classes approach over iterating through paths (say, (Get-Counter -listset memory).paths).

You can almost always accomplish the same thing using WMI and CIM cmdlest. Prefer CIM over WMI.

WQL is cumbersome and lacking many commands. Avoid.

Remember the hierarchy:
  Category (Class if you prefer)
    Counter(s)
      Instance(s)
Every Category has Counters but not all of the Counters have Instances. Check before using.


Next blog will deal with several specific counters and the meaning of the values obtained in terms of performance.

In this series:
BLOG 1: PerfCounters infrastructure
BLOG 2: PerfCounters Raw vs. Formatted values
BLOG 3: PerfCounters, fetching the values
BLOG 4: PerfCounters, CPU perf data
BLOG 5: PerfCounters, Memory perf data
BLOG 6: PerfCounters, Disk/IO perf data
BLOG 7: PerfCounters, Network and Contention perf data

5 comments:

  1. Can anyone explain why I get more PerformanceCounter instances returned when I use the PowerShell Get-Counter than when I use the C# function?

    PS C:\> (New-Object System.Diagnostics.PerformanceCounterCategory("SMB Client Shares")).GetInstanceNames()
    *
    _Total
    PS C:\>
    PS C:\>
    PS C:\> Get-Counter -ListSet "SMB Client shares"

    CounterSetName : SMB Client Shares
    MachineName : .
    CounterSetType : MultiInstance
    Description : This counter set displays information about server shares that are being accessed by the client us
    ing SMB protocol version 2 or higher.
    Paths : {\SMB Client Shares(*)\Credit Stalls/sec, \SMB Client Shares(*)\Metadata Requests/sec, \SMB Client
    Shares(*)\Avg. Data Queue Length, \SMB Client Shares(*)\Avg. Write Queue Length...}
    PathsWithInstances : {\SMB Client Shares(\cl-sofs-smb\VMs3)\Credit Stalls/sec, \SMB Client Shares(\localhost
    \IPC$)\Credit Stalls/sec, \SMB Client Shares(\cl-sofs-smb\VMs2)\Credit Stalls/sec, \SMB
    Client Shares(\cl-sofs-smb\VMs1)\Credit Stalls/sec...}
    Counter : {\SMB Client Shares(*)\Credit Stalls/sec, \SMB Client Shares(*)\Metadata Requests/sec, \SMB Client
    Shares(*)\Avg. Data Queue Length, \SMB Client Shares(*)\Avg. Write Queue Length...}

    PS C:\>

    ReplyDelete
    Replies
    1. Get-Counter is showing PathsWithInstances and not just Paths. Please see https://technet.microsoft.com/en-us/library/hh849685.aspx for details.

      Delete
    2. Thanks. How can I get PathsWithInstances returned with use of C#?

      Delete
    3. Thanks. The mystery to solve is what C# code is hidden behind getting PathsWithInstances returned. Do anyone know?

      Delete