0

I'd like to view how the thread loads CPU like it is made in ProcessExplorer. Number of thread CPU cycles can be retrieved by QueryThreadCycleTime, number of idle cycles - by QueryIdleProcessorCycleTime, but to know busy cycles I have to reiterate through all processes using QueryProcessCycleTime that I don't think is very efficient. Is there a simpler method of finding it?

Molochnik
  • 704
  • 5
  • 23
  • [How to get the cpu usage per thread on windows (win32)](https://stackoverflow.com/questions/1393006/how-to-get-the-cpu-usage-per-thread-on-windows-win32) – Roman R. Jan 07 '18 at 16:14
  • From this thread: "...But this only gives you the time the thread was spent running". I have permanent threads and I need cycles count, not times. – Molochnik Jan 07 '18 at 16:22
  • Time and cycles are related by a multiplicative factor, the clock frequency – David Heffernan Jan 07 '18 at 19:03
  • But clock frequency can vary @DavidHeffernan (e.g. speedstep, turbo mode), maybe better to offset the cpu time against idle time? – Remko Jan 08 '18 at 13:29

1 Answers1

1

but to know busy cycles I have to reiterate through all processes using QueryProcessCycleTime that I don't think is very efficient

It's efficient enough. It takes ~15ms on my machine to enumerate every thread of every process and get its cycle time.

Here's a quick hack which prints top 20 threads by CPU cycles used:

var threads = new Dictionary<int, (DateTime at, ulong ctime, ulong perSec, string procName)>();
var seen = new HashSet<int>();
while (true)
{
    var cpuFreq = new ManagementObjectSearcher("select CurrentClockSpeed from Win32_Processor")
        .Get().Cast<ManagementObject>().Max(o => (uint) o.Properties["CurrentClockSpeed"].Value) * 1_000_000;
    seen.Clear();
    var start = DateTime.UtcNow;
    foreach (var p in Process.GetProcesses())
    {
        foreach (var t in p.Threads.Cast<ProcessThread>())
        {
            if (t.Id == 0)
                continue;
            seen.Add(t.Id);
            var thandle = OpenThread(ThreadAccess.QUERY_INFORMATION, false, (uint) t.Id);
            QueryThreadCycleTime(thandle, out ulong ctime);
            CloseHandle(thandle);
            var at = DateTime.UtcNow;
            if (!threads.ContainsKey(t.Id) || threads[t.Id].procName != p.ProcessName)
            {
                threads[t.Id] = (at: at, ctime: ctime, perSec: 0, procName: p.ProcessName);
            }
            else
            {
                var was = threads[t.Id];
                var diff = ctime >= was.ctime ? ctime - was.ctime : 0;
                threads[t.Id] = (at: at, ctime: ctime, perSec: (ulong) (diff / (at - was.at).TotalSeconds), procName: p.ProcessName);
            }
        }
    }
    foreach (var notSeen in threads.Keys.Where(id => !seen.Contains(id)).ToList())
        threads.Remove(notSeen);
    Console.Clear();
    Console.WriteLine($"Time taken to read thread counters: {(DateTime.UtcNow - start).TotalMilliseconds:0} ms");
    Console.WriteLine();
    Console.WriteLine($"Current CPU clock speed: {cpuFreq / 1_000_000:#,0} MHz");
    Console.WriteLine();
    foreach (var kvp in threads.OrderByDescending(x => x.Value.perSec).Take(20))
        Console.WriteLine($"{kvp.Value.perSec,15:#,0}   {kvp.Value.perSec / (double) cpuFreq * 100,3:0}%   {kvp.Value.procName + "/" + kvp.Key,-20}");
    Thread.Sleep(1000);
}

Output:

Time taken to read thread counters: 15 ms

Current CPU clock speed: 3,801 MHz

  1,125,936,851    30%   sqlservr/6292
    141,635,479     4%   sqlservr/6588
    106,651,766     3%   StatusScreenSite/7656
     80,182,644     2%   firefox/1880
     77,899,627     2%   ServiceHub.Host.CLR.x86/12668
     74,568,381     2%   ServiceHub.Host.CLR.x86/17304
     73,911,491     2%   AquaComputerService/14840
     72,843,646     2%   ServiceHub.Host.CLR.x86/13104
     64,164,832     2%   dwm/1404
     46,584,563     1%   firefox/14492
     46,538,943     1%   firefox/17924
     44,885,944     1%   sqlservr/12880
     41,697,624     1%   firefox/17164
     33,780,283     1%   AllThreadCpuUsage/10604    <------- this program
     33,165,365     1%   ProcessHacker/8588
     29,938,943     1%   TeamViewer/7356
     29,005,700     1%   OpenHardwareMonitor/11196
     28,808,683     1%   nvcontainer/3456
     28,536,182     1%   nvcontainer/5128
     28,294,193     1%   svchost/15732

The % column shows % usage of a single core, rather than the entire CPU. Also note that MSDN warns us not to ascribe any particular units to the value returned by thread cycle count, but in practice, on my machine, it returns actual CPU cycles.

Roman Starkov
  • 59,298
  • 38
  • 251
  • 324