Daily Archives: January 3, 2009

Determine CPU usage of current process (C++ and C#)

Like this? Please check out my latest book, Writing High-Performance .NET Code.

Updated 2/4/2009: I changed the implementation of these classes from the original:

  • Instead of a critical section, InterlockedIncrement/Decrement is used.
  • The sample driver program now demos using multiple threads using the CpuUsage class to show thread safety.

Download the C++ and C# projects that accompany this article.

Just to make it clear, there is no API called GetProcessCpuPercentage(). To find out the percentage, we can use some other, real APIs and do some calculations. Before getting to the equation and code, let’s discuss the different types of time available.

There are four types of time:

  1. Wall time – The actual, real-world progression of time as measured by you on your watch.
  2. Kernel time – The amount of time spent in kernel mode (protected, high-order mode of operation)
  3. User time – the amount of time spent in user-mode (often by the process itself)
  4. Idle time – nothing going on at all

Kernel, User, and Idle sum to total time, which is approximately wall-time.

Each process spends some time in kernel mode and some time in user mode. We just need to compare the time spent by a process to the time spent by all processes on the computer, since the last time we made such a measurement. It is important to note that we do NOT take into account the idle time.

Thus, the equation is

CpuPercentageEquation

There are two APIs that are useful:

The times reported are absolute, so what we are actually interested is in the difference between the current times and those from a previous run.

Armed with this information, we can calculate the CPU usage for the current process (or any arbitrary process, for that matter).

Let’s do it first in C++ to demonstrate usage of the APIs.

CPU Usage Percentage in C++

Here’s the header file:

   1: #pragma once
   2: #include <windows.h>
   3:
   4: class CpuUsage
   5: {
   6: public:
   7:     CpuUsage(void);
   8:
   9:     short  GetUsage();
  10: private:
  11:     ULONGLONG SubtractTimes(const FILETIME& ftA, const FILETIME& ftB);
  12:     bool EnoughTimePassed();
  13:     inline bool IsFirstRun() const { return (m_dwLastRun == 0); }
  14:
  15:     //system total times
  16:     FILETIME m_ftPrevSysKernel;
  17:     FILETIME m_ftPrevSysUser;
  18:
  19:     //process times
  20:     FILETIME m_ftPrevProcKernel;
  21:     FILETIME m_ftPrevProcUser;
  22:
  23:     short m_nCpuUsage;
  24:     ULONGLONG m_dwLastRun;
  25:
  26:     volatile LONG m_lRunCount;
  27: };

The GetUsage() method is where the work occurs. The other methods are to help in the calculations. The critical section run count enables the code to be called in a multi-threaded environment without problems. I also prevent the code from being called more often than every 250ms. Here is the complete implementation:

   1: #include "StdAfx.h"
   2: #include <windows.h>
   3: #include "CpuUsage.h"
   4:
   5: CpuUsage::CpuUsage(void)
   6: :m_nCpuUsage(-1)
   7: ,m_dwLastRun(0)
   8: ,m_lRunCount(0)
   9: {
  10:     ZeroMemory(&m_ftPrevSysKernel, sizeof(FILETIME));
  11:     ZeroMemory(&m_ftPrevSysUser, sizeof(FILETIME));
  12:
  13:     ZeroMemory(&m_ftPrevProcKernel, sizeof(FILETIME));
  14:     ZeroMemory(&m_ftPrevProcUser, sizeof(FILETIME));
  15:
  16: }
  17:
  18:
  19: /**********************************************
  20: * CpuUsage::GetUsage
  21: * returns the percent of the CPU that this process
  22: * has used since the last time the method was called.
  23: * If there is not enough information, -1 is returned.
  24: * If the method is recalled to quickly, the previous value
  25: * is returned.
  26: ***********************************************/
  27: short CpuUsage::GetUsage()
  28: {
  29:     //create a local copy to protect against race conditions in setting the 
  30:     //member variable
  31:     short nCpuCopy = m_nCpuUsage;
  32:     if (::InterlockedIncrement(&m_lRunCount) == 1)
  33:     {
  34:         /*
  35:         If this is called too often, the measurement itself will greatly 
  36:         affect the results.
  37:         */
  38:
  39:         if (!EnoughTimePassed())
  40:         {
  41:             ::InterlockedDecrement(&m_lRunCount);
  42:             return nCpuCopy;
  43:         }
  44:
  45:         FILETIME ftSysIdle, ftSysKernel, ftSysUser;
  46:         FILETIME ftProcCreation, ftProcExit, ftProcKernel, ftProcUser;
  47:
  48:         if (!GetSystemTimes(&ftSysIdle, &ftSysKernel, &ftSysUser) ||
  49:             !GetProcessTimes(GetCurrentProcess(), &ftProcCreation,
  50:                 &ftProcExit, &ftProcKernel, &ftProcUser))
  51:         {
  52:             ::InterlockedDecrement(&m_lRunCount);
  53:             return nCpuCopy;
  54:         }
  55:
  56:         if (!IsFirstRun())
  57:         {
  58:             /*
  59:             CPU usage is calculated by getting the total amount of time 
  60:             the system has operated since the last measurement 
  61:             (made up of kernel + user) and the total
  62:             amount of time the process has run (kernel + user).
  63:             */
  64:             ULONGLONG ftSysKernelDiff =
  65:                 SubtractTimes(ftSysKernel, m_ftPrevSysKernel);
  66:             ULONGLONG ftSysUserDiff =
  67:                 SubtractTimes(ftSysUser, m_ftPrevSysUser);
  68:
  69:             ULONGLONG ftProcKernelDiff =
  70:                 SubtractTimes(ftProcKernel, m_ftPrevProcKernel);
  71:             ULONGLONG ftProcUserDiff =
  72:             SubtractTimes(ftProcUser, m_ftPrevProcUser);
  73:
  74:             ULONGLONG nTotalSys =  ftSysKernelDiff + ftSysUserDiff;
  75:             ULONGLONG nTotalProc = ftProcKernelDiff + ftProcUserDiff;
  76:
  77:             if (nTotalSys > 0)
  78:             {
  79:                 m_nCpuUsage = (short)((100.0 * nTotalProc) / nTotalSys);
  80:             }
  81:         }
  82:
  83:         m_ftPrevSysKernel = ftSysKernel;
  84:         m_ftPrevSysUser = ftSysUser;
  85:         m_ftPrevProcKernel = ftProcKernel;
  86:         m_ftPrevProcUser = ftProcUser;
  87:
  88:         m_dwLastRun = GetTickCount64();
  89:
  90:         nCpuCopy = m_nCpuUsage;
  91:     }
  92:
  93:     ::InterlockedDecrement(&m_lRunCount);
  94:
  95:     return nCpuCopy;
  96: }
  97:
  98: ULONGLONG CpuUsage::SubtractTimes(const FILETIME& ftA, const FILETIME& ftB)
  99: {
 100:     LARGE_INTEGER a, b;
 101:     a.LowPart = ftA.dwLowDateTime;
 102:     a.HighPart = ftA.dwHighDateTime;
 103:
 104:     b.LowPart = ftB.dwLowDateTime;
 105:     b.HighPart = ftB.dwHighDateTime;
 106:
 107:     return a.QuadPart - b.QuadPart;
 108: }
 109:
 110: bool CpuUsage::EnoughTimePassed()
 111: {
 112:     const int minElapsedMS = 250;//milliseconds
 113:
 114:     ULONGLONG dwCurrentTickCount = GetTickCount64();
 115:     return (dwCurrentTickCount - m_dwLastRun) > minElapsedMS;
 116: }

In order to test this, here is a simple program that starts two threads that run an infinite loop, eating the processor. On a dual-core system, this process will take roughly 85-95% of the CPU. I also start two threads to access the usage object and poll the CPU usage in order to demonstrate the thread safety of the object.

   1: // CpuUsageCpp.cpp : Defines the entry point for the console application.
   2: //
   3:
   4: #include "stdafx.h"
   5: #include <windows.h>
   6: #include "CpuUsage.h"
   7:
   8: DWORD WINAPI EatItThreadProc(LPVOID lpParam);
   9: DWORD WINAPI WatchItThreadProc(LPVOID lpParam);
  10:
  11: CpuUsage usage;
  12:
  13: int _tmain(int argc, _TCHAR* argv[])
  14: {
  15:     //start threads to eat the processor
  16:     CreateThread(NULL, 0, EatItThreadProc, NULL, 0, NULL);
  17:     CreateThread(NULL, 0, EatItThreadProc, NULL, 0, NULL);
  18:
  19:     //start threads to watch the processor (to test thread-safety)
  20:     CreateThread(NULL, 0, WatchItThreadProc, NULL, 0, NULL);
  21:     CreateThread(NULL, 0, WatchItThreadProc, NULL, 0, NULL);
  22:
  23:     while (true)
  24:     {
  25:         Sleep(1000);
  26:     }
  27:
  28:     return 0;
  29: }
  30:
  31:
  32: DWORD WINAPI WatchItThreadProc(LPVOID lpParam)
  33: {
  34:     while (true)
  35:     {
  36:         short cpuUsage = usage.GetUsage();
  37:
  38:         printf("Thread id %d: %d%% cpu usage\n", ::GetCurrentThreadId(), cpuUsage);
  39:         Sleep(1000);
  40:     }
  41: }
  42:
  43: DWORD WINAPI EatItThreadProc(LPVOID lpParam)
  44: {
  45:     ULONGLONG accum = 0;
  46:     while (true)
  47:     {
  48:         accum++;
  49:     }
  50:
  51:     printf("%64d\n", accum);
  52: }

C# Version

In C#, The System.Diagnostics.Process can give us the time information for a specific process. However, we still need the Win32 API call for getting the total system times (GetSystemTimes). The Process class reports times in TimeSpans, not FILETIME, so our class is modified accordingly.

   1: using System;using System.Collections.Generic;
   2: using System.Linq;
   3: using System.Text;
   4: using System.Runtime.InteropServices;
   5: using ComTypes = System.Runtime.InteropServices.ComTypes;
   6: using System.Threading;
   7: using System.Diagnostics;
   8:
   9: namespace CpuUsageCs
  10: {
  11:     class CpuUsage
  12:     {
  13:         [DllImport("kernel32.dll", SetLastError = true)]
  14:         static extern bool GetSystemTimes(
  15:                     out ComTypes.FILETIME lpIdleTime,
  16:                     out ComTypes.FILETIME lpKernelTime,
  17:                     out ComTypes.FILETIME lpUserTime
  18:                     );
  19:
  20:         ComTypes.FILETIME _prevSysKernel;
  21:         ComTypes.FILETIME _prevSysUser;
  22:
  23:         TimeSpan _prevProcTotal;
  24:
  25:         Int16 _cpuUsage;
  26:         DateTime _lastRun;
  27:         long _runCount;
  28:
  29:         public CpuUsage()
  30:         {
  31:             _cpuUsage = -1;
  32:             _lastRun = DateTime.MinValue;
  33:             _prevSysUser.dwHighDateTime = _prevSysUser.dwLowDateTime = 0;
  34:             _prevSysKernel.dwHighDateTime = _prevSysKernel.dwLowDateTime = 0;
  35:             _prevProcTotal = TimeSpan.MinValue;
  36:             _runCount = 0;
  37:         }
  38:
  39:         public short GetUsage()
  40:         {
  41:             short cpuCopy = _cpuUsage;
  42:             if (Interlocked.Increment(ref _runCount) == 1)
  43:             {
  44:                 if (!EnoughTimePassed)
  45:                 {
  46:                     Interlocked.Decrement(ref _runCount);
  47:                     return cpuCopy;
  48:                 }
  49:
  50:                 ComTypes.FILETIME sysIdle, sysKernel, sysUser;
  51:                 TimeSpan procTime;
  52:
  53:                 Process process = Process.GetCurrentProcess();
  54:                 procTime = process.TotalProcessorTime;
  55:
  56:                 if (!GetSystemTimes(out sysIdle, out sysKernel, out sysUser))
  57:                 {
  58:                     Interlocked.Decrement(ref _runCount);
  59:                     return cpuCopy;
  60:                 }
  61:
  62:                 if (!IsFirstRun)
  63:                 {
  64:                     UInt64 sysKernelDiff =
  65:                         SubtractTimes(sysKernel, _prevSysKernel);
  66:                     UInt64 sysUserDiff =
  67:                         SubtractTimes(sysUser, _prevSysUser);
  68:
  69:                     UInt64 sysTotal = sysKernelDiff + sysUserDiff;
  70:
  71:                     Int64 procTotal = procTime.Ticks - _prevProcTotal.Ticks;
  72:
  73:                     if (sysTotal > 0)
  74:                     {
  75:                         _cpuUsage = (short)((100.0 * procTotal) / sysTotal);
  76:                     }
  77:                 }
  78:
  79:                 _prevProcTotal = procTime;
  80:                 _prevSysKernel = sysKernel;
  81:                 _prevSysUser = sysUser;
  82:
  83:                 _lastRun = DateTime.Now;
  84:
  85:                 cpuCopy = _cpuUsage;
  86:             }
  87:             Interlocked.Decrement(ref _runCount);
  88:
  89:             return cpuCopy;
  90:
  91:         }
  92:
  93:         private UInt64 SubtractTimes(ComTypes.FILETIME a, ComTypes.FILETIME b)
  94:         {
  95:             UInt64 aInt =
  96:             ((UInt64)(a.dwHighDateTime << 32)) | (UInt64)a.dwLowDateTime;
  97:             UInt64 bInt =
  98:             ((UInt64)(b.dwHighDateTime << 32)) | (UInt64)b.dwLowDateTime;
  99:
 100:             return aInt - bInt;
 101:         }
 102:
 103:         private bool EnoughTimePassed
 104:         {
 105:             get
 106:             {
 107:                 const int minimumElapsedMS = 250;
 108:                 TimeSpan sinceLast = DateTime.Now - _lastRun;
 109:                 return sinceLast.TotalMilliseconds > minimumElapsedMS;
 110:             }
 111:         }
 112:
 113:         private bool IsFirstRun
 114:         {
 115:             get
 116:             {
 117:                 return (_lastRun == DateTime.MinValue);
 118:             }
 119:         }
 120:     }
 121: }

These classes can now be used wherever you need to monitor the CPU usage of a process.

Notice any improvements to be made? Leave a comment.

Download C++ and C# projects