Search code examples
c#.netperformanceperformancecounter

C# performance counter data is "all over the map"


I need to calculate CPU and RAM usage for the overall system as well as for a specific process. I've never done this in C#. So I was able to come up with the following code (that I took primarily from samples on this web site):

try
{
    Process proc = Process.GetCurrentProcess();
    string strProcName = proc.ProcessName;
    Console.WriteLine("Process: " + strProcName);

    using (PerformanceCounter total_cpu = new PerformanceCounter("Process", "% Processor Time", "_Total", true))
    {
        using (PerformanceCounter process_cpu = new PerformanceCounter("Process", "% Processor Time", strProcName, true))
        {
            for (; ; )
            {
                Console.CursorTop = 1;
                Console.CursorLeft = 0;

                float t = total_cpu.NextValue() / Environment.ProcessorCount;
                float p = process_cpu.NextValue() / Environment.ProcessorCount;
                Console.WriteLine(String.Format("Total CPU (%) = {0}\t\t\nApp CPU (%) = {1}\t\t\nApp RAM (KB) = {2}",
                    t, p, Process.GetCurrentProcess().WorkingSet64 / (1024)
                    ));

                System.Threading.Thread.Sleep(100);
            }
        }
    }
}
catch(Exception ex)
{
    Console.WriteLine("Exception: " + ex);
}

But what it gives me is the data "all over the map". Take a look:

enter image description here

enter image description here

enter image description here

So can someone answer these points:

  1. It seems like running such performance counter is a pretty costly operation by itself -- it raises CPU usage by about 5%. I did a ton of CPU counters with C++ and they take pretty much no CPU time to run.

  2. Besides what I said above, the first time the loop above runs with a 2 second delay! It literally hangs up for 2 seconds. Is it normal? If it is, I can't believe they had an audacity to call it a performance counter :)

  3. Even though my RAM reading is pretty close to the one reported by the Task Manager, the CPU output is totally off. Can someone tell me what am I doing wrong here?

  4. Moreover, I can't seem to find any documentation for the PerformanceCounter class, that can explain all of these: % Processor Time, _Total, etc? And most importantly, are those English only? Are they supposed to be localized?

  5. The process there is specified by its name. But what if I have more than one process under the same name running. What then?


Solution

  • Here's what I came up with, but that is using unmanaged APIs:

    [DllImport("kernel32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool GetProcessTimes(IntPtr hProcess, out System.Runtime.InteropServices.ComTypes.FILETIME
        lpCreationTime, out System.Runtime.InteropServices.ComTypes.FILETIME lpExitTime, out System.Runtime.InteropServices.ComTypes.FILETIME lpKernelTime,
        out System.Runtime.InteropServices.ComTypes.FILETIME lpUserTime);
    
    [DllImport("kernel32.dll")]
    [return: MarshalAs(UnmanagedType.U4)]
    static extern UInt32 GetTickCount();
    
    static bool gbSetOldData = false;
    static UInt32 gmsOldTickCount = 0;
    static ulong gnsOldKernelTime = 0;
    static ulong gnsOldUserTime = 0;
    
    public static double getCPUUsageForProcess(int nProcID = 0)
    {
        //Get CPU usage for the process in with ID in 'nProcID'
        //'nProcID' = process ID, or 0 for the current process
        //RETURN:
        //      = CPU usage: [0.0 - 1.0]
        //      = Negative if error
        double fCPUUsage = -1.0;
        try
        {
            IntPtr hProcess = nProcID != 0 ? Process.GetProcessById(nProcID).Handle : Process.GetCurrentProcess().Handle;
    
            System.Runtime.InteropServices.ComTypes.FILETIME ftCreated, ftExit, ftKernel, ftUser;
            if (GetProcessTimes(hProcess, out ftCreated, out ftExit, out ftKernel, out ftUser))
            {
                UInt32 dwmsNewTickCount = GetTickCount();
    
                ulong nsNewKernelTime = (ulong)ftKernel.dwHighDateTime;
                nsNewKernelTime <<= 32;
                nsNewKernelTime |= (ulong)(uint)ftKernel.dwLowDateTime;
    
                ulong nsNewUserTime = (ulong)ftUser.dwHighDateTime;
                nsNewUserTime <<= 32;
                nsNewUserTime |= (ulong)(uint)ftUser.dwLowDateTime;
    
                if (gbSetOldData)
                {
                    //Adjust from 100-nanosecond intervals to milliseconds
                    //100-nanosecond intervals = 100 * 10^-9 = 10^-7
                    //1ms = 10^-3
                    fCPUUsage = (double)((nsNewKernelTime - gnsOldKernelTime) + (nsNewUserTime - gnsOldUserTime)) /
                        (double)((dwmsNewTickCount - gmsOldTickCount) * 10000);
    
                    //Account for multiprocessor architecture
                    fCPUUsage /= Environment.ProcessorCount;
    
                    //In case timer API report is inaccurate
                    if (fCPUUsage > 1.0)
                        fCPUUsage = 1.0;
                }
                else
                {
                    //For the first run, assume no CPU usage
                    fCPUUsage = 0.0;
                }
    
                //Remember data
                gnsOldKernelTime = nsNewKernelTime;
                gnsOldUserTime = nsNewUserTime;
                gmsOldTickCount = dwmsNewTickCount;
                gbSetOldData = true;
            }
        }
        catch
        {
            //Failed
            fCPUUsage = -1.0;
        }
    
        return fCPUUsage;
    }
    

    And this is how you'd call it:

    int nDummy = 1;
    
    for (; ; )
    {
        double fCPU = getCPUUsageForProcess();
    
        Console.CursorTop = 1;
        Console.CursorLeft = 0;
    
        int nCpu = (int)(fCPU * 100);
        Console.WriteLine("CPU%: {0}\t\t", nCpu);
    
        //Time filler
        long j = 0;
        for (; j < 1000000; j++)
        {
            nDummy += (int)Math.Cos(1 / (j + 1));
        }
    
        //Don't hog all CPU time!
        System.Threading.Thread.Sleep(500);
    }
    

    I'm not sure if .NET has a similar method. If there's one (that doesn't have all the limitations I posted above) I'd like to hear it?