Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# System CPU Usage and syncing with Windows Task Manager

this is a two part question, I wanted to post my code here on stack to help others with the same task.

Question 1:

I have a subset of code, which I believe, is correctly measuring CPU usage (across as many cores in the system, as per times retrieved) as per the measurement interval - I use 1 second in the thread call.

I had to decipher this from the very few articles on the web and from C++ code. My question is, for question 1, is this correct what I have done?

Sometimes the value returned is a minus figure which is why I multiply by -1. Again, I am assuming, since there is very little documentation, that this is what I should be doing.

I have the following code:

public static class Processor
{
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool GetSystemTimes(out ComTypes.FILETIME lpIdleTime, out ComTypes.FILETIME lpKernelTime, out ComTypes.FILETIME lpUserTime);

    private static TimeSpan _sysIdleOldTs;
    private static TimeSpan _sysKernelOldTs;
    private static TimeSpan _sysUserOldTs;

    static Processor()
    {
    }

    public static void Test()
    {
        ComTypes.FILETIME sysIdle, sysKernel, sysUser;

        if(GetSystemTimes(out sysIdle, out sysKernel, out sysUser))
        {
            TimeSpan sysIdleTs = GetTimeSpanFromFileTime(sysIdle);
            TimeSpan sysKernelTs = GetTimeSpanFromFileTime(sysKernel);
            TimeSpan sysUserTs = GetTimeSpanFromFileTime(sysUser);

            TimeSpan sysIdleDiffenceTs = sysIdleTs.Subtract(_sysIdleOldTs);
            TimeSpan sysKernelDiffenceTs = sysKernelTs.Subtract(_sysKernelOldTs);
            TimeSpan sysUserDiffenceTs = sysUserTs.Subtract(_sysUserOldTs);

            _sysIdleOldTs = sysIdleTs;
            _sysKernelOldTs = sysKernelTs;
            _sysUserOldTs = sysUserTs;

            TimeSpan system = sysKernelDiffenceTs.Add(sysUserDiffenceTs);

            Double cpuUsage = (((system.Subtract(sysIdleDiffenceTs).TotalMilliseconds) * 100) / system.TotalMilliseconds);

            if (cpuUsage < 0)
            {
                Console.WriteLine("CPU: " + ((int) (cpuUsage)*-1) + "%");
            }
            else
            {
                Console.WriteLine("CPU: " + (int) (cpuUsage) + "%");
            }
            Console.WriteLine("");
        }
        else
        {
            Console.WriteLine("Couldn't get CPU usage!");
            Console.WriteLine("");
        }
    }

    private static TimeSpan GetTimeSpanFromFileTime(ComTypes.FILETIME time)
    {
        return TimeSpan.FromMilliseconds((((ulong)time.dwHighDateTime << 32) + (uint)time.dwLowDateTime) * 0.000001);
    }
}

Question 2:

Is there anyway for me to sync a thread, in my program, with that of the Windows Task Manager, for the purpose of matching measurement figure e.g CPU Usage with the above code?

What I mean is, if you open Windows Task Manager, you will notice that it polls every second - which in reality it doesn't need to be less than that. What I want to do is match the timing with my thread.

So when Windows Task Manager polls, my thread polls.


Some notes:

I didn't want to use Performance Counters or .NET built in methods. In fact, I believe - from what I have read, .NET doesn't have methods for calculating the CPU usage on a machine, that Performance counters are required for this otherwise.

Performance counters have overhead and in addition make the GC grow, not to mention the delay in calling the next result. While my software does not need to be real-time performance I do need it to be as responsive and use as little CPU time as possible. The above code can be called and returned in less than a millisecond. In fact on my development machine, the time-span difference shows 0ms. I don't believe Performance Counters are as responsive.

In case you are curious, my software is gathering a number of items, CPU, Memory, Event Log items etc. of which these all need to be gathered and stored, in SQL CE, before the next poll, 1 second away. Each task, item, however is on its own thread to facilitate this.

Also, the code above is not optimized in anyway and you will notice I have yet to comment it also. The reason being is I want to make sure it is correct before optimization etc.

Update 1

As per a coment I made down the way, I removed the extra "System" timespan as it is not required and modified the line that retrieves the "CPU Usage" and cast it appropriately.

int cpuUsage = (int)(((sysKernelDifferenceTs.Add(sysUserDifferenceTs).Subtract(sysIdleDifferenceTs).TotalMilliseconds) * 100.00) / sysKernelDifferenceTs.Add(sysUserDifferenceTs).TotalMilliseconds);

Though I am still unsure of the formula. While it seems to be highly accurate it does on occasion return a minus figure which is why I multiply it by -1 if that is the case. After all, there is no such thing a -2% CPU usage etc.

Update 2

So I did a simple test using "System.Diagnostics.PerformanceCounter". While incredibly handy and does exactly what it is intended to do it does create overhead.

Here are my observations:

  • It took the Performance Counter that much longer to initialize. In the order of roughly three seconds longer on my i7 2.6 Ghz.
  • The performance counter also seemed to add on another approx 5MB of RAM usage simply by using it. What I mean by this is: With the code above ,my app maxes out at 7.5MB ram. With the performance counter it "starts" at 12.5MB.
  • Over the space of 5 seconds, where my thread ran 5 times - once per second, the memory of my app had grown by 1 MB and this increase is consistent with time, although it does level out, in my case anyway, 3-4MB above starting. So where my app is usually 7.5MB ram with the code above, the PC code leveled out at 16.5 MB ram - an increase of 9MB over the code above. Note: The code above does not cause this increase.

So, if your application was built in a manner where resource usage and timing is key I would suggest against using Performance counters because of these reasons. Otherwise go ahead as it works without all the mess.

As for my app, performance counters will be detrimental to my software's purpose.

like image 778
Anthony Avatar asked Nov 14 '22 18:11

Anthony


1 Answers

I think you have a bug in your formula. You want to basically compute CPU usage as this:

CPU Usage = KernelTimeDiff + UserTimeDiff
            --------------------------------------------
            KernelTimeDiff + UserTimeDiff + IdleTimeDiff

Thus, a quick mod to your code as follows:

        //  TimeSpan system = sysKernelDiffenceTs.Add(sysUserDiffenceTs);
        //Double cpuUsage = (((system.Subtract(sysIdleDiffenceTs).TotalMilliseconds) * 100) / system.TotalMilliseconds); 

        TimeSpan totaltime = sysKernelDiffenceTs.Add(sysUserDiffenceTs);
        totaltime = totaltime.Add(sysIdleDifferenceTs);
        int cpuUsage = 100 - (sysIdleDifferenceTs.TotalMilliseconds * 100) / totaltime.TotalMilliseconds;
       Console.WriteLine("CPU: " + cpuUsage + "%");

You originally declared cpuUsage as "Double". I'm not sure if you wanted floating point precision, but in your code, you definitely weren't getting anything other than integer precision because the assignment statement was just doing integer math. If you need higher precision from the computation, you could easily get it by mixing in some floating point:

Double cpuUsage = 100.0 - (sysIdleDifferenceTs.TotalMilliseconds * 100.0) /totaltime.TotalMilliseconds;

Also, in regards to being in sync with Task Manager. Task Manager, as I understand it, uses perf counters. (And I would suspect that GetSystemTimes is making perf counter calls under the hood, but perhaps not). And I'm not sure why you wouldn't use perf counters either. The "% Process Time" counter is an instant sample counter that doesn't require computing a diff with a previous result. (There's one per logical cpu). Use the PDH helper functions instead of the legacy registry key apis to get at it. You can do this from an unmanaged C/C++ DLL that exports a "GetCpuUsage" function back to your C# code. But I don't know why you couldn't just PInvoke the PDH functions from C# either. I don't know about this overhead that you speak of. I'm not sure I understand your reference to " the delay in calling the next result" either.

like image 160
selbie Avatar answered Dec 17 '22 23:12

selbie