Search code examples
c#windowsperformancecputaskmanager

Is there a better way to measure CPU performance without impacting the performance?


I need to find a way to measure RAM % usage and CPU % usage (as displayed in Task Manager, despite its inaccuracies) into an existing system. The method I decided on so far has been to use CMD and Powershell commands from within C# to get this information.

Below are the commands, saved into a ps1 file I use to collect all the variables I need:

$mem = cmd.exe /c 'wmic ComputerSystem get TotalPhysicalMemory && wmic OS get FreePhysicalMemory';
$loadpc = cmd.exe /c 'wmic cpu get loadpercentage';
$maxclock = cmd.exe /c 'wmic cpu get maxclockspeed';
$cpuperf = (Get-Counter -Counter '\Processor Information(_Total)\% Processor Performance').CounterSamples.CookedValue;
$mem,$loadpc,$maxclock,$cpuperf

This returns the total memory installed, total free memory available, the load percentage on the CPU, the base clock speed and the CPU performance. I use these numbers to calculate the RAM and CPU usages.

The .ps1 file is saved on a UNC path and I execute it on remote PCs using the below code:

private static List<double> GetResourceUsages(string hostname)
{
        string output = (LaunchProcess("powershell.exe", "-command Invoke-Command -ComputerName " + hostname + " -FilePath '<path>\\GetResources.ps1'"));
        output = RemoveSubstrings(new List<string>() { "TotalPhysicalMemory", "FreePhysicalMemory", "LoadPercentage", "-", "MaxClockSpeed", " " }, output);
        List<string> list = output.Split("\r\n").ToList();
        for (int i = 0; i < list.Count; i++)
        {
            if (list[i].Length == 0)
            {
                list.RemoveAt(i);
                i--;
            }
        }
        long.TryParse(list[0], out long MaxRAM);
        long.TryParse(list[1], out long AvailableRAM);
        double RAMUsage = (double)100 - ((double)AvailableRAM / (((double)MaxRAM) / 1024) * (double)100);
        Double.TryParse(list[2], out double loadPC);
        Double.TryParse(list[3], out double baseClock);
        Double.TryParse(list[4], out double perfPC);
        double CurrentCPUClock = (perfPC / 100) * baseClock;
        double CPUUtilization = loadPC / ((baseClock / CurrentCPUClock) * 100) * 100;
        return new List<double>() { RAMUsage, CPUUtilization };
}

This seems to work well. It allows me to calculate the RAM usage perfectly accurately, and the CPU usage matches Task Manager, however inaccurate it is.

Now, the problem is, as the ps1 script is executed remotely, the remote PC's CPU usage will spike: Task Manager reading of CPU

The reason I am not using PerformanceCounter is because in order to calculate the CPU % usage as displayed in Task Manager, based on what I've learned, I need to do quite a bit of fuckery with the numbers, some of that involves getting the Processor Performance. I was not able to find a way to get this number using PerformanceCounter.

In any case, this method currently has too much of an impact on performance to be viable. Is there any other way to measure CPU performance as displayed in task manager or am I limited to WMI?


Solution

  • You can use PerformanceCounter to query the information you require (although it can be a bit tricky sometimes to use it).
    I think this is cleaner than launching a power-shell script etc.
    The code running this resource sampling can run in a separate exe, and communicate with your main one over tcp/ip sockets.

    You mentioned that you didn't use the PerformanceCounter because "I need to do quite a bit of fuckery with the numbers". The chalange with PerformanceCounter is to find the name of the proper category and counter (and sometimes also the instance name).

    The following code demonstrates sampling the CPU utilization (%) and available RAM (in MB). There are many more counters. If you launch the Windows application "Performance Monitor" you'll be able to easilty see the available counters (there are a lot).

    I used the following:
    CPU: category: "Processor Information", counter: "% Processor Utility", instance: _"Total".
    RAM: category: "Memory", counter: "Available MBytes".

    On my system it reports values that match the Task Manager.

    using System;
    using System.Diagnostics;
    using System.Threading;
    
    public class Program
    {
        public static void Main(String[] args)
        {
            var cpuCounter = new PerformanceCounter("Processor Information", "% Processor Utility", "_Total");
            var ramCounter = new PerformanceCounter("Memory", "Available MBytes");
    
            while (true)
            {
                Thread.Sleep(1000);
                double cpuSample = cpuCounter.NextValue();
                double ramSample = ramCounter.NextValue();
                Console.WriteLine("CPU utilization (%):" + cpuSample + ",  Available RAM (MB): " + ramSample);
            }
        }
    }