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:
- Wall time – The actual, real-world progression of time as measured by you on your watch.
- Kernel time – The amount of time spent in kernel mode (protected, high-order mode of operation)
- User time – the amount of time spent in user-mode (often by the process itself)
- 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
There are two APIs that are useful:
- GetProcessTimes – Get times for a specific process
- GetSystemTimes – Get total times for the entire system (all CPUs)
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