Some basics about PerformanceCounter.
1. A performance category has many performance counters underneath. A performance counter may be single-instanced or contain multiple running instances. The structure is like this:
PerformanceCategory -- PerformanceCounter -- InstancesTo get all the built-in categories:
PerformanceCategory.GetCategories()
To get all the counters under the category that has exactly one running instance:
PerformanceCategory.GetCounters()
2. The constructor to create a performance counter is:
PerformanceCounter(string categoryName, string counterName, string instanceName.
To monitor the IO (file or network) data bytes sent/received by FireFox:
PerformanceCounter counter = new PerformanceCounter("Process", "IO Data Bytes/sec", "firefox");
To monitor the CPU percentage used by FireFox:
PerformanceCounter counter = new PerformanceCounter("Process", "% Processor Time", "firefox");
3. To take a sample and get the calculated value:
counter.NextValue();Before calling this method, make sure you wait for at least one second.
counters.Add(new PerformanceCounter("Process", "IO Data Bytes/sec", "firefox")); counters.Add(new PerformanceCounter("Process", "% Processor Time", "firefox")); System.Threading.Thread.Sleep(1000); foreach(PerformanceCounter counter in counters) Console.WriteLine(string.Format("{0}:{1}}, counter.CounterName, counter.NextValue());
Some lessons I have learned:
1. The calculated value of the first sample is always 0. E.g. the very first time you call NextValue(), the returned value is always 0.
2. If an application has more than one running instances, even though from the Task Manager, these running instances all have the same process name, E.g. "TestProcess", their instance names are not the same. The instance started first has its name as "TestProcess", the second "TestProcess#1", the third "TestProcess#2"...the nth, "TestProcess#n". So make sure the right instance name gets passed into the performance counter constructor.
3. If any of the running instances of the same application exits, the instance names will be changed. If your code doesn't update the instance name accordingly, an "Instance not found" error will occur.
E.g. If "TestProcess" of the 2 running instances, "TestProcess" and "TestProcess#1", stopps running, "TestProcess#1" will be renamed to "TestProcess". If the counter still has the name "TestProcess#1", it will throw an exception when NextValue() is called. The instance name can be updated by simply calling counter.InstanceName = "new instance name".
4. In my case, I am only interested in getting per-Process statistics. Unfortunately, the PerformanceCounter does not operate against the process id. To get around this limitation, I have created my own wrapper class called ProcessPerformanceCounter.
public class ProcessPerformanceCounter { ... public ProcessPerformanceCounter(string counterName, int processId) {...} public string CounterName {get{...}} public string InstanceName {get{...} set{...}} public NextValue(){...} }
Based on the process id, I can get the process name. I can then get all running processes under the same name. From there, I can get the correct instance name for that particular process:
private string GetInstanceName(int processId) { string instanceName = GetProcessNameById(processId); bool found = false; if (!string.IsNullOrEmpty(instanceName)) { Process[] processes = Process.GetProcessesByName(instanceName); if (processes.Length > 0) { int i = 0; foreach (Process p in processes) { instanceName = FormatInstanceName(p.ProcessName, i); if (PerformanceCounterCategory.CounterExists("ID Process","Process")) { PerformanceCounter counter = new PerformanceCounter("Process", "ID Process", instanceName); if (processId == counter.RawValue) { found = true; break; } } i++; } } } if (!found) instanceName = string.Empty; return instanceName; }
private string FormatInstanceName(string processName, int count) { string instanceName = string.Empty; if (count == 0) instanceName = processName; else instanceName = string.Format("{0}#{1}", processName, count); return instanceName; }
5. When calling counter.NextValue(), do not forget to handle the instance name changed scenario:
public float NextValue() { float nextValue = 0; try { nextValue = counter.NextValue(); } catch (Exception ex) { if (InstanceNotExistError(ex)) { // adjust instance name based on process id instanceName = GetInstanceName(processId); counter.InstanceName = instanceName; nextValue = counter.NextValue(); } else throw; } return nextValue; }