Tuesday, February 22, 2011

Getting % Processor Time for Process By PID

I had to search and search on how to do this, and now hopefully no one else will. It’s fairly easy to get the % CPU Usage for a process using the System.Diagnostics.PerformanceCounter (as long as you remember to sleep a second so you can get the a correct value and divide by the number of CPUs). That looks like this:



PerformanceCounter pc = new PerformanceCounter("Process", "% Processor Time", processName, true);
pc.NextValue();
Thread.Sleep(1000);
int cpuPercent = (int)ppc.PerformanceCounter.NextValue() / Environment.ProcessorCount;


The PerformanceCounter constructor accepts the processName, but it doesn’t accept a process id, so if you had multiple processes with the same name (w3wp, svchost, etc.) you can’t get the value for a specific process easily. If there are multiple processes with the same name running, they’ll have names like “w3wp#1” and “w3wp#2”, where the 1 and the 2 are completely unrelated to ProcessId. I ended up creating this method to get the performance counter process name for a given process. You could be extra fancy and make it an extension method on the Process Class, but this works for now:



private string GetPerformanceCounterProcessName(int pid)
{
return GetPerformanceCounterProcessName(pid, System.Diagnostics.Process.GetProcessById(pid).ProcessName);
}

private string GetPerformanceCounterProcessName(int pid, string processName)
{
int nameIndex = 1;
string value = processName;
string counterName = processName + "#" + nameIndex;
PerformanceCounter pc = new PerformanceCounter("Process", "ID Process", counterName, true);

while (true)
{
try
{
if (pid == (int)pc.NextValue())
{
value = counterName;
break;
}
else
{
nameIndex++;
counterName = processName +
"#" + nameIndex;
pc =
new PerformanceCounter("Process", "ID Process", counterName, true);
}
}
catch (SystemException ex)
{
if (ex.Message == "Instance '" + counterName + "' does not exist in the specified Category.")
{
break;
}
else
{
throw;
}
}
}

return value;
}


Then calling it would look like this:


PerformanceCounter pc = new PerformanceCounter("Process", "% Processor Time", GetPerformanceCounterProcessName(PID), true);
pc.NextValue();
Thread.Sleep(1000);
int cpuPercent = (int)ppc.PerformanceCounter.NextValue() / Environment.ProcessorCount;

3 comments:

heli2reg said...

I would wrap the very first call to

PerformanceCounter pc = new PerformanceCounter("Process", "ID Process", counterName, true);

in a try clause.
Reason: I have seen that the very first process may actually be called similar to
<ProcessName>_<ProcessId> such as
MyApp_12352

Bottom line, the entire function may need a bit of a rewrite if you want to be flexible across WinXP, Win2K3 Server, Win Vista, Win 7 and Win 2008 Server.

Daryl said...

@heli2reg - What Windows OS were you running in? I believe I used this in a Windows 2008 Server box with no issues...

Unknown said...

Very cool approach. I just had to make the following edits to get it up and running
Note the first iteration does not have a number appended and the nameIndex is initialized to 0 instead of 1.

private string GetPerformanceCounterProcessName(int pid, string processName)
{
int nameIndex = 0;
string value = processName;
string counterName = processName;
PerformanceCounter pc = new PerformanceCounter("Process", "ID Process", counterName, true);

while (true)
{
try
{
if (pid == (int)pc.NextValue())
{
value = counterName;
break;
}
else
{
nameIndex++;
counterName = processName + "#" + nameIndex;
pc = new PerformanceCounter("Process", "ID Process", counterName, true);
}
}
catch (SystemException ex)
{
if (ex.Message == "Instance '" + counterName + "' does not exist in the specified Category.")
{
break;
}
else
{
throw;
}
}
}

return value;
}