Summary: Microsoft Scripting Guy Ed Wilson teaches how to use performance counter sets with Windows PowerShell to simplify profiling a system.
Hey, Scripting Guy! I am wondering if there is an easier way to work with performance counters? For example, rather than having to pick out a whole bunch of counters, are there groups of counters that I can use? If not, that is ok, but I feel like I had to ask. By the way, the Scripting Guys rock. I just had to say it.
—BB
Hello BB,
Microsoft Scripting Guy Ed Wilson here. I want to thank you for your vote of confidence. One of the things I enjoy so much about speaking in person to users groups (like I did in Columbus, Ohio) or appearing at events (like SQLSaturday in Wheeling, West Virginia) is getting to meet people who come up to me and say, “I have been reading your stuff for years, and you have saved me so many times.” And though it is fun to meet “groupies,” it is more fulfilling to meet people who have read my material and have been able to reduce their workload.
Anyway, BB, if you don’t ask, I cannot help you. Yes, you can query groups of counters! To see a list of all the counter sets, use the ListSet parameter and use a wildcard character. The ListSet parameter returns detailed information about counter sets. To limit the output to only the counter set names, select only the counterset property. The commands that will return counter set information or limit to only counter set names are shown here:
#Produce listing of all counter sets
Get-Counter -ListSet *
#to see only the counter set names
Get-Counter -ListSet * | select countersetname
Using the wildcard character trick, it is possible to search for specific counter sets that relate to a particular technology. A few sample searches and their associated output are shown here.
PS C:\Users\edwilson> Get-Counter -ListSet *disk*| select countersetname
CounterSetName
LogicalDisk
PhysicalDisk
PS C:\Users\edwilson> Get-Counter -ListSet *processor* |select countersetname
CounterSetName
Processor Information
Per Processor Network Activity Cycles
Per Processor Network Interface Card Activity
Processor
PS C:\Users\edwilson> Get-Counter -ListSet *memory* | select countersetname
CounterSetName
.NET CLR Memory
MSSQL$SQLEXPRESS:Memory Manager
Memory
.NET Memory Cache 4.0
If I use a wildcard character pattern that matches a single countersetname value and I do not select only the countersetname with the Select-Object cmdlet (as I was doing earlier), I receive detailed information about the counter set. The properties and values associated with the LogicalDisk counter set (I use the wild card pattern L*disk) are shown in the output here:
PS C:\> Get-Counter -ListSet l*disk
CounterSetName : LogicalDisk
MachineName : .
CounterSetType : MultiInstance
Description : The Logical Disk performance object consists of counters that monitor logical partitions of hard or fixed disk drives. Performance Monitor identifies logical disks by their drive letter, such as C.
Paths : {\LogicalDisk(*)\% Free Space, \LogicalDisk(*)\Free Megabytes, \LogicalDisk(*)
\Current Disk Queue Length, \LogicalDisk(*)\% Disk Time...}
PathsWithInstances : {\LogicalDisk(HarddiskVolume1)\% Free Space, \LogicalDisk(C:)\% Free Space, \LogicalDisk(_Total)\% Free Space, \LogicalDisk(HarddiskVolume1)\Free Megabytes...}
Counter : {\LogicalDisk(*)\% Free Space, \LogicalDisk(*)\Free Megabytes, \LogicalDisk(*)
\Current Disk Queue Length, \LogicalDisk(*)\% Disk Time...}
Two properties are of particular interest: the paths property and the pathsWithInstances properties. The counter paths in the paths property use a wildcard character mapping, and do not map to specific instances of the resource. The command and associated output are shown here:
PS C:\Users\edwils> (Get-Counter -ListSet l*disk).paths
\LogicalDisk(*)\% Free Space
\LogicalDisk(*)\Free Megabytes
\LogicalDisk(*)\Current Disk Queue Length
\LogicalDisk(*)\% Disk Time
\LogicalDisk(*)\Avg. Disk Queue Length
\LogicalDisk(*)\% Disk Read Time
\LogicalDisk(*)\Avg. Disk Read Queue Length
\LogicalDisk(*)\% Disk Write Time
\LogicalDisk(*)\Avg. Disk Write Queue Length
\LogicalDisk(*)\Avg. Disk sec/Transfer
\LogicalDisk(*)\Avg. Disk sec/Read
\LogicalDisk(*)\Avg. Disk sec/Write
\LogicalDisk(*)\Disk Transfers/sec
\LogicalDisk(*)\Disk Reads/sec
\LogicalDisk(*)\Disk Writes/sec
\LogicalDisk(*)\Disk Bytes/sec
\LogicalDisk(*)\Disk Read Bytes/sec
\LogicalDisk(*)\Disk Write Bytes/sec
\LogicalDisk(*)\Avg. Disk Bytes/Transfer
\LogicalDisk(*)\Avg. Disk Bytes/Read
\LogicalDisk(*)\Avg. Disk Bytes/Write
\LogicalDisk(*)\% Idle Time
\LogicalDisk(*)\Split IO/Sec
If I use the pathsWithInstances property, it will triple the number of counters (on my laptop anyway). The pathsWithInstances property returns the counter path, and then a path for each instance. On my Hyper-V server with seven drives, the pathsWithInstances property will return an instance for each logical drive, as well as an instance for the _Total instance. Compare the truncated output here with the first few lines of output from the previous command:
PS C:\Users\edwils> (Get-Counter -ListSet l*disk).PathsWithInstances
\LogicalDisk(HarddiskVolume1)\% Free Space
\LogicalDisk(C:)\% Free Space
\LogicalDisk(_Total)\% Free Space
\LogicalDisk(HarddiskVolume1)\Free Megabytes
\LogicalDisk(C:)\Free Megabytes
\LogicalDisk(_Total)\Free Megabytes
To verify the number of paths, I piped the Get-Counter command to the Measure-Object cmdlet and returned only the count property. The two commands I used are shown here:
PS C:\Users\edwils> ((Get-Counter -ListSet l*disk).Paths | Measure-Object).count
23
PS C:\Users\edwils> ((Get-Counter -ListSet l*disk).PathsWithInstances | Measure-Object).count
69
After I have picked out the counter set, I can use either the paths property or the pathsWithInstances property to query. All I need to do is to supply the counter paths directly to the counter parameter of the Get-Counter cmdlet. I like to pipe the results to More so that I can page through the output (the pager does not work in the output pane of the Windows PowerShell ISE). The code to do this is shown here:
Get-Counter -Counter (Get-Counter -ListSet l*disk).Paths | more
The command and associated output are shown in the following figure.
I can use the Get-Counter cmdlet to return processor information, memory utilization, and logical and physical disk activity. The following commands accomplish this task:
Get-Counter -Counter(Get-Counter -ListSet "Processor Information").paths
Get-Counter -counter (Get-Counter -listSet "memory").paths
Get-Counter -counter (Get-Counter -listSet "LogicalDisk").paths
Get-Counter -counter (Get-Counter -listSet "PhysicalDisk").paths
Because the counter property accepts an array (in fact, each command above supplies an array), I can create my own array of counter paths and make a single query with the Get-Counter cmdlet. In the following command, I get all of the paths from the above four commands and store them in a single variable.
$a = (Get-Counter -ListSet "Processor Information").paths
$a += (Get-Counter -listSet "memory").paths
$a += (Get-Counter -listSet "LogicalDisk").paths
$a += (Get-Counter -listSet "PhysicalDisk").paths
The $a variable contains 99 paths on my laptop. I found this by using the following command:
($a | Measure-Object).count
I can now query for performance information from all 99 counters at once:
Get-Counter -Counter $a
What about performance? On my laptop, the first command takes about four seconds. This command is shown here:
PS C:\Users\edwils> Measure-command -expression `
{
Get-Counter -Counter(Get-Counter -ListSet "Processor Information").paths
Get-Counter -counter (Get-Counter -listSet "memory").paths
Get-Counter -counter (Get-Counter -listSet "LogicalDisk").paths
Get-Counter -counter (Get-Counter -listSet "PhysicalDisk").paths
}
Days : 0
Hours : 0
Minutes : 0
Seconds : 4
Milliseconds : 96
Ticks : 40963324
TotalDays : 4.74112546296296E-05
TotalHours : 0.00113787011111111
TotalMinutes : 0.0682722066666667
TotalSeconds : 4.0963324
TotalMilliseconds : 4096.3324
After I have put all of the paths in a variable, the command takes about a second, as shown here:
PS C:\Users\edwils> Measure-command -Expression { Get-Counter -Counter $a }
Days : 0
Hours : 0
Minutes : 0
Seconds : 1
Milliseconds : 69
Ticks : 10690628
TotalDays : 1.2373412037037E-05
TotalHours : 0.000296961888888889
TotalMinutes : 0.0178177133333333
TotalSeconds : 1.0690628
TotalMilliseconds : 1069.0628
Okay, that is cheating a bit because I did not time obtaining the paths first. But when I run the command to pick up all the paths and then perform the query, I still see a huge improvement in performance. (I would need to do some additional testing, such as rebooting my machine between each query, to get rid of any caching that might be taking place. It would bear checking, and this might be a great way to speed up querying for lots of performance counters).
PS C:\Users\edwils> Measure-command `
{
$a = (Get-Counter -ListSet "Processor Information").paths
$a += (Get-Counter -listSet "memory").paths
$a += (Get-Counter -listSet "LogicalDisk").paths
$a += (Get-Counter -listSet "PhysicalDisk").paths
Get-Counter -Counter $a
}
Days : 0
Hours : 0
Minutes : 0
Seconds : 1
Milliseconds : 102
Ticks : 11021647
TotalDays : 1.27565358796296E-05
TotalHours : 0.000306156861111111
TotalMinutes : 0.0183694116666667
TotalSeconds : 1.1021647
TotalMilliseconds : 1102.1647
Well, BB, this ends another Hey, Scripting Guy! Blog post. I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy