Find a performance counter instance by a process ID

Performance Counters are a great feature of Windows which allow you to monitor applications and the whole system in a noninvasive manner. Which makes them even better is a fact that you can gather the performance data remotely. You can find some more information on using Performance Counters in my previous post: Diagnosing applications using Performance Counters. When diagnosing a faulting application I usually start with IO, Memory and CPU metrics available in the Process counter. The Process counter has a separate instance for each process that is running on your machine. By default, instance names are composed of the process name they represent and some identifier (in case there are multiple processes with the same name), eg. w3wp, w3wp#1, w3wp#2. As I’m mostly working with web applications hosted in IIS I often ran into problem which counter instance represents an application pool I want to monitor. So I wrote a simple Powershell script that will find the counter by process id:

function GetPidsFromPerfCounter([string]$ComputerName, [string]$pcounter, [int]$ProcessId, [string]$f) {
  # dump counter values
  typeperf -f CSV "$ComputerName$pcounter" -sc 1 -o $f -y | Out-Null
  # import and parse the CSV file
  $t = import-csv $f -Delimiter ","
  $t.psobject.Properties | select -skip 1 | ? { 
    if ($ProcessId -ge 0) { $_.value -eq $ProcessId } else { $true }
  } | % { "{1,6} => {0}" -f $_.Name,$_.Value } | Sort-Object

function Find-ProcessCounterByPid(
  [Parameter(Mandatory=$False,ValueFromPipeline=$True)][int]$ProcessId = -1,
  [Parameter(Mandatory=$False,ValueFromPipeline=$False)][string]$ComputerName = $null,
  [Parameter(Mandatory=$False,ValueFromPipeline=$False)][switch]$IncludeDotNet = $False
) {
  if (-not [String]::IsNullOrEmpty($ComputerName) -and -not $ComputerName.StartsWith("\\")) {
      $ComputerName = "\\" + $ComputerName
  $f = [System.IO.Path]::GetTempFileName()
  # native counters
  GetPidsFromPerfCounter $ComputerName "\Process(*)\ID Process" $ProcessId $f
  # for .net counters we need to grap CLR Memory\ID Process
  if ($IncludeDotNet) {
    Write-Host "  ------- .NET PIDs ---------"
    GetPidsFromPerfCounter $ComputerName "\.NET CLR Memory(*)\Process ID" $ProcessId $f
  del $f

You might now wonder why I haven’t used the Get-Counter cmdlet. Unfortunately there is some bug in it and it does not print counter instance indexes (the part coming after # in the instance name) which, in our case, is a core requirement. You can paste the above code into your Powershell module and make the GetPidsFromPerfCounter private. Then you can call Find-ProcessCounterByPid with no arguments – in which case the cmdlet will print all available counter instances:

PS Diagnostics> Find-ProcessCounterByPid
     0 => \\PECET\Process(_Total)\ID Process
     0 => \\PECET\Process(Idle)\ID Process
     4 => \\PECET\Process(System)\ID Process
   280 => \\PECET\Process(smss)\ID Process
   396 => \\PECET\Process(csrss)\ID Process
   404 => \\PECET\Process(svchost#5)\ID Process
   456 => \\PECET\Process(wininit)\ID Process
   464 => \\PECET\Process(csrss#1)\ID Process
   532 => \\PECET\Process(winlogon)\ID Process
   548 => \\PECET\Process(services)\ID Process
   572 => \\PECET\Process(lsass)\ID Process
   576 => \\PECET\Process(nvxdsync)\ID Process
   664 => \\PECET\Process(svchost)\ID Process
   700 => \\PECET\Process(nvvsvc)\ID Process
   724 => \\PECET\Process(nvSCPAPISvr)\ID Process
   752 => \\PECET\Process(nvvsvc#1)\ID Process
   768 => \\PECET\Process(svchost#1)\ID Process
   796 => \\PECET\Process(SkyDrive)\ID Process
   820 => \\PECET\Process(svchost#2)\ID Process
   860 => \\PECET\Process(dwm)\ID Process
   896 => \\PECET\Process(svchost#3)\ID Process
   912 => \\PECET\Process(chrome#11)\ID Process
   964 => \\PECET\Process(svchost#4)\ID Process
  1008 => \\PECET\Process(vpnui)\ID Process
  1148 => \\PECET\Process(vmware-authd)\ID Process
  1204 => \\PECET\Process(vpnagent)\ID Process
  1236 => \\PECET\Process(svchost#6)\ID Process
  1244 => \\PECET\Process(googledrivesync)\ID Process
  1384 => \\PECET\Process(spoolsv)\ID Process
  1420 => \\PECET\Process(svchost#7)\ID Process

or specify a concrete PID:

PS Diagnostics> Find-ProcessCounterByPid 768
   768 => \\PECET\Process(svchost#1)\ID Process

Additionally, if you are also interested in profiling .NET Applications, add -IncludeDotNet switch that will list .NET counters instance indexes, eg.:

PS Diagnostics> Find-ProcessCounterByPid -IncludeDotNet 5824
  5824 => \\PECET\Process(powershell)\ID Process
  ------- .NET PIDs ---------
  5824 => \\PECET\.NET CLR Memory(powershell)\Process ID

By adding -ComputerName switch you may also collect performance data from a remote machine. I hope you will find the script useful. If you have any ideas how to improve it drop me a comment 🙂

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

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