Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Capturing Performance Counter Data for a Process by Process Id


:P
On this page:

The .NET PerformanceCounter class generally is pretty easy to use in order to retrieve performance information. You create a perf counter, initialize the first value to read, let some time pass and then read the next value and Windows gives you access to a plethora of performance information. It’s nice what’s available to make that happen.

Process Specific Performance Counters

On several occasions, however, I’ve had a need to profile Process specific performance counters. For example, I need to look at a specific running application and display its CPU usage similar to the way Process Manager does. At first glance that seems easy enough as you can simply request a PerformanceCounter for a process by its name.

Here’s the simple code to do this (I’m using Chrome as my instance I’m profiling here):

var perfCounter = new PerformanceCounter("Process", "% Processor Time", "chrome");

// Initialize to start capturing
perfCounter.NextValue();
 
for (int i = 0; i < 20; i++)
{
    // give some time to accumulate data
    Thread.Sleep(1000);

    float cpu = perfCounter.NextValue()/Environment.ProcessorCount;

    Console.WriteLine("Chrome CPU: " + cpu);
}

This works and gets me the active CPU status for Chrome.

When I run the above code, I get output that looks something like this:

ProcessCpuOutput

PerfCounters work by specifying Category (Process) and a key (% Processor Time) to create a counter. Once you have a counter instance you need to ‘start’ it collecting data, which you can do by calling NextSample() or NextValue(). This sets the counter to collecting data until the next call to NextValue() is fired at which time a value can be retrieved and provide an average for the time period measured. Typically you need to allow for a good chunk of time between the initial collection and the value collection so you get a reasonable sample period for Windows to collect the perf data. Here I’m using Thread.Sleep() but in an application you can possibly have the perf counter running on a background thread.

I’m collecting CPU data, which is provided as a percentage value. Note that the data is spread out over all the cores of the machine. This is why once I get the value I have to divide by Environment.ProcessorCount to get a value that resembles what’s displayed in task manager. This doesn’t quite make sense to me as single threaded code typically doesn’t spread across all cores, but it seems to be the same behavior that task manager and process explorer use. It’s also the guidance that Microsoft provides themselves.

The code above looks like it works fine collecting data and looking at task. But do you see a problem with this code especially in light of profiling Chrome which uses multiple process with the same name?

Eenie meenie miney mo – which Process has to go?

I used Chrome as my profiling target and the problem is that there are more than one instance of Chrome running. Check out task manager -even though I only have a single browser instance open, each tab inside of the browser runs as its own executable. In Process Explorer there are many instances of Chrome running and I have really no idea which one I was specifically monitoring.

MultiProcesses

The PerformanceCounter API has an annoying limitation – you can specify only a process name! It would be much more useful if you could actually specify a process ID rather than a process name, but well, that would be too easy.

Getting a Process Specific Performance Counter

It turns out there are a few workarounds for this. Essentially there’s a special performance counter API that lets you enumerate all processes and another that gives you an ‘Instance Name’. Specifically there’s the PerformanceCounterCategory class which allows you to retrieve a full list of ‘instance’ names for running processes. This list has unique IDs for each process and if there are multiple processes they are referenced like this:

chrome
chrome#1
chrome#2
chrome#3

and so on.

You can iterate over this list and match the Process ID from the PerfCounter returned and based on that get the InstanceName. You can then use these unique names to pass to the PerformanceCounter Process instance instead of the Process Name to get at a specific process for profiling information. And yeah, the code to do this is kind of ugly and can be also be very slow depending on how you handle it.

When I ran into this initially I found a number of Stackoverflow references as well as a post that shows a partial solution. But all of them were either incorrect (missing instances) or very slow (iterating over all objects) or require an explict process name – none of which worked for what I need this functionality for.

I’m working on a monitoring application that specifically monitors a group of processes and needs to display all of their CPU load characteristics in addition to other process data like memory and uptime.

It took a while of tweaking to get the code correct to include all instances, and to perform adequately. Specifically the instance lookup and looping through instances to find the process ID can be excruciatingly slow especially if you don’t filter the list of process names.

In the end I created a small reusable class that provides a more performant version:

public class ProcessCpuCounter
{
    public static PerformanceCounter GetPerfCounterForProcessId(int processId, string processCounterName = "% Processor Time")
    {
        string instance = GetInstanceNameForProcessId(processId);
        if (string.IsNullOrEmpty(instance))
            return null;

        return new PerformanceCounter("Process", processCounterName, instance);
    }

    public static string GetInstanceNameForProcessId(int processId)
    {
        var process = Process.GetProcessById(processId);
        string processName = Path.GetFileNameWithoutExtension(process.ProcessName);

        PerformanceCounterCategory cat = new PerformanceCounterCategory("Process");
        string[] instances = cat.GetInstanceNames()
            .Where(inst => inst.StartsWith(processName))
            .ToArray();

        foreach (string instance in instances)
        {
            using (PerformanceCounter cnt = new PerformanceCounter("Process",
                "ID Process", instance, true))
            {
                int val = (int)cnt.RawValue;
                if (val == processId)
                {
                    return instance;
                }
            }
        }
        return null;
    }
}
There are two static methods here, with the GetPerfCounterForProcessId() being the high level one that returns you a full perf counter instance. The useful stuff relevant to this discussion however is the GetInstanceNameForProcessId() which receives only a Process Id and then spits back a normalized instance name – ie. Chrome, Chrome#1, Chrome#2 etc.

The slight optimization that results in significant performance improvements over the other samples is the filter for the process name so that new perf counter instances are only created for matching process names, not against all processes. On my machine I have 180 processes running and the process access was excruciatingly slow. By filtering down to only hit those names that match performance drastically improved. Note also that I’m not passing in a process name, but rather do a Process lookup using the Process class to get the name. Process returns the full file name but the Process Perf API expects just the file stem, so the extension is stripped by the code.

Trying it out

To check out this class I can now create a small test program that shows me the CPU load of all Chrome instances running:

// grab all Chrome process instances
var processes = Process.GetProcessesByName("chrome");

for (int i = 0; i < 10; i++)
{
    foreach (var p in processes)
    {
        var counter = ProcessCpuCounter.GetPerfCounterForProcessId(p.Id);

        // start capturing
        counter.NextValue();

        Thread.Sleep(200);

        var cpu = counter.NextValue()/(float) Environment.ProcessorCount;
        Console.WriteLine(counter.InstanceName + " -  Cpu: " + cpu );
    }

}

Console.WriteLine("Any key to exit...");
Console.Read();

The code basically runs in a dumb loop for 10 times and on each pass it goes through all the chrome instances and collects the perf data for each instance displaying the instance name (Chrome, Chrome#1, Chrome#2 etc.) and the current CPU usage.

Here’s what it looks like:

ChromeOutput

Performance is decent – there’s still a good deal of overhead on start up for the first time. As the Performance Counter API initializes apparently there’s a bit of overhead. But after the initial delay, performance is pretty swift.

Workey, Workey

I was able to plug this code into my process monitoring Web application that needed to display server status for a number of application servers running on the backend. I’m basically monitoring the worker processes for an admin summary page as well as for notifications if the CPU load goes into the 80%+ range. It works well.

This is another kind of obscure topic, but when you need to do per process monitoring I hope this article will come in handy to some of you…

Posted in .NET  Windows  C#  

The Voices of Reason


 

Andrei
October 14, 2014

# re: Capturing Performance Counter Data for a Process by Process Id

Good article! One thing to mention related to windows process instance names is that they change dynamically when one of the processes exits.

For example if chrome#8 exits, chrome#9 will become chrome#8 and chrome#10 will become chrome#9. At this point getting the value of the counter previously created for chrome#10 will throw an exception.
This is really annoying if you want to to monitor multiple instances of multiple processes as it gets down to monitoring process exits and recreating all the counters (really ugly).

One way would be to change the way process instance names are generated (see http://support.microsoft.com/kb/281884) but this has the potential of affecting other apps using the perfmon api.

Rick Strahl
October 14, 2014

# re: Capturing Performance Counter Data for a Process by Process Id

Thanks for the reference, Andrei. I've run into the 'process goes away' problem with long running process monitoring myself so I can relate to that.

OTOH, in cases when you are monitoring specific processes it seems that you are likely profiling a longer running process or service so this is probably much less of an issue than with just a desktop application that might come and go frequently.

Matt
October 10, 2015

# re: Capturing Performance Counter Data for a Process by Process Id

According to the documentation I've found and in my experiments, the PerformanceCounter.RawValue property accesses the value of the counter, not the process id. As a result, I'm not sure your code sample is accurate.

https://msdn.microsoft.com/en-us/library/system.diagnostics.performancecounter.rawvalue(v=vs.110).aspx

I'm still looking for a way to connect performance counters to their process id properly, so unfortunately I can't provide a more effective tactic.

Tim
September 08, 2016

# re: Capturing Performance Counter Data for a Process by Process Id

How did Microsoft ever create the idea to not use the process' id to use with the performance counter... Doesn't make sense at all for me but ok. Thank you for this snippet, Rick. Works great :)

crokusek
April 25, 2017

# re: Capturing Performance Counter Data for a Process by Process Id

Looks like there is a race condition with the workaround that searches on prefix combined with @Andrei's comment.

  1. The workaround determines the instance name by looping on prefix and matching Pid. Say its "Chrome#9".

  2. Then Chrome#1 happens to exit. @Andrei indicates Chrome#9 becomes Chrome#8.

  3. The program then tries to create new PerformanceCounter on "Chrome#9" however it is no longer the original Pid or it does not exist at all.


Arun
February 15, 2018

# re: Capturing Performance Counter Data for a Process by Process Id

Does the PerformanceCounter nextvalue() results in high CPU and more time while getting the total of Processor.%Processing_time ? Also does the API call have any relation with wmiPrvSE process ?


Chris Bailiss
October 30, 2018

# re: Capturing Performance Counter Data for a Process by Process Id

Useful code Rick, thanks.

Re: the problem described above of counters shuffling, e.g. when one instance of chrome closes. I took the following approach:

  • Use the .NET Process class to track all running instances of the EXE I am interested in monitoring.
  • When any of these instances exits, I get all of the perf counters again using GetPerfCounterForProcessId().

This approach works because the .NET Process class is quite lightweight and it means I don't need to continuously call the heavy (but useful) GetPerfCounterForProcessId() to validate that none of the instances have shuffled around. It is still a faff though. It's fairly unbelievable Windows has been around for so long and there is still no way to directly lookup a Performance Counter using a Process ID...


Fry
November 06, 2019

# re: Capturing Performance Counter Data for a Process by Process Id

Thank you for this info. But unfortunately I'm using C++…

If you are using Win32 API directly, you can use the following Code (providing a big enough chunk)

PdhExpandCounterPath("\\Process(chrome*)\\ID Process", chunk, size);

Returned is a list of performance counter strings for all instances. These can be added with PdhAddCounter() and queried e.g. with PdhCollectQueryData(). Be aware that the strings ('Process' and 'ID Process') are language dependent.


Richard Külling
September 28, 2021

# re: Capturing Performance Counter Data for a Process by Process Id

@Matt I thought that too at first, but 'PerformanceCounter.NextValue' gives back whatever value you have defined for it: look closer at the second argument "counterName" of the instantiation of the counter. If you pass it "ID Process", then the NextValue method will always return the ID of the Process. If you pass it "% Processor Time", then the NextValue method will always return the percentage of CPU usage by the process. In my opinion, this parameter should have been named "counterType", for there would have been less confusion.


Chris
August 31, 2022

# re: Capturing Performance Counter Data for a Process by Process Id

Even after years, this article helped me alot. Too bad there is still no convenient possibility to get the process cpu usage for a specific pid (except uwp). Thanks!


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024