Im trying to implement a performance monitoring tool, I want to monitor basic things such as Memory and CPU.
I am attempting to do so by using Performance Counters as I believe this is the "correct" way of querying process performance in C#, this is some example code:
class Program
{
static void Main(string[] args)
{
while (true)
{
var pcs = Process.GetProcesses()
.Select(p => new PerformanceCounter("Process", "Working Set - Private", p.ProcessName));
var sw = Stopwatch.StartNew();
foreach (var pc in pcs)
pc.NextValue();
Console.WriteLine($"Time taken to read {pcs.Count()} performance counters: {sw.ElapsedMilliseconds}ms");
Thread.Sleep(1000);
}
}
}
So obviously ~12.5 milliseconds to query a process on my system is unacceptably slow. How is it supposed to be done?
I already asked a related question in this post: Performance Counter read access very slow - How does Task Manager do it?
But I realize I wasnt specific enough in that post and asked the wrong qestion. What I really want to know if how can I do what I want to do with Performance Counters or is it simply not possible?
EDIT1:
I am running Windows 10 Pro 1607 - Build 14393.479
Windows is a bit notorious about being slow whenever you do something with all running processes on a machine. But this is excessive. There is something rotten in the state of PerformanceCounter, I got the first cue from this question. Something you can easily visualize in your own test program by altering the Console.WriteLine() call:
Console.Write($"Time taken to read {pcs.Count()} performance counters:");
Console.WriteLine($"{sw.ElapsedMilliseconds}ms, {GC.CollectionCount(2)} collections");
Output on my machine:
Time taken to read 124 performance counters: 1633ms, 15 collections
Time taken to read 124 performance counters: 923ms, 30 collections
Time taken to read 124 performance counters: 928ms, 45 collections
Time taken to read 124 performance counters: 934ms, 59 collections
Time taken to read 124 performance counters: 922ms, 74 collections
Time taken to read 124 performance counters: 925ms, 89 collections
...etc
Or in other words, a full garbage collection for every ~8 calls to PerformanceCounter.NextValue(). Ouch. Yes, that's going to bog down your program a lot.
This is remarkably strange behavior, to put it mildly. I cannot find any good cues in the PerformanceCounter class itself, the code looks very innocent. There is a big chunk of unmanaged code under the hood that is hard to see (C:\Windows\Microsoft.NET\Framework\v4.0.30319\PerfCounter.dll) but looking at its dependencies don't provide convincing evidence that it is responsible. This happens both on the v2.0.50727 and the v4.0.30319 runtime versions so an unfortunate hack in V4.x doesn't easily explain it. It is not a nice multiple of 8 so a simple counter that forces finalizers to run doesn't easily explain it. It is not the native performance counter, querying it with WMI does not trigger any collections. It is not specific to the memory counters, querying for, say, "Thread Count" also does it. Might have something to do with the Windows version, mine is Win10 version 1607 (can't easily test another).
Microsoft needs to get involved with this. The author of the question I linked appears to have created a bug feedback report for it, I added this Q+A as supporting evidence. Keep an eye on feedback report, vote for it, hopefully they'll start paying attention. Or contact Microsoft Support directly if you don't want to wait.
Meanwhile, you can get ahead with the System.Management namespace. The WMI Code Creator utility is quite handy to auto-generate the code:
...
using System.Management; // Project > Add Reference required
public static void QueryWorkingset() {
ManagementObjectSearcher searcher =
new ManagementObjectSearcher("root\\CIMV2",
"SELECT Name, WorkingSetPrivate FROM Win32_PerfRawData_PerfProc_Process");
foreach (ManagementObject queryObj in searcher.Get()) {
Console.WriteLine("{0}: {1}", queryObj["Name"], queryObj["WorkingSetPrivate"]);
}
}
Still no speed demon, takes about 1.0 msec per process on my machine. But doesn't burn 100% core like PerformanceCounter does and no forced collections.