In this short article I will describe how to obtain the CPU usage of a single process (like the “CPU” column in Task Manager). I have also created a small unit that implements this functionality –
uCpuUsage
(7 Kb,
RAR
archive).
One way, that works only in NT-based operation systems (NT/2000/XP and so on) is to use the
GetProcessTimes()
API function (
Windows
unit).
function
GetProcessTimes(
// process handle
hProcess:cardinal;
// when the process was created
var
lpCreationTime:_FILETIME;
// when the process exited var
lpExitTime:_FILETIME;
// time the process has spent in kernel mode
var
lpKernelTime:_FILETIME;
// time the process has spent in user mode
var
lpUserTime :_FILETIME
):LongBool;
As you can see, this function returns the total amount of time the process has been using the CPU (lpKernelTime+lpUserTime) (we can ignore lpCreationTime and lpExitTime). This amount is expressed in units of 100 nanoseconds (divide by 10000 to get miliseconds). The _FILETIME structure is essentially a 64-bit integer and can be converted to Delphi version of Int64 quite easily :
TotalTime:Int64;
{…..}
TotalTime:=int64(mKernelTime.dwLowDateTime
or
(mKernelTime.dwHighDateTime
shr
32));
To get the CPU usage we must get the process times twice (say, TotalTime1 and TotalTime2), and calculate the CPU usage as ((TotalTime2-TotalTime1) / DeltaTime), where DeltaTime is time elapsed between the two calls of GetProcessTimes (TotalTime’s and DeltaTime must be expressed in the same units, see the example below).
Method 1
A simple (though not very flexible) way of getting the CPU usage is then such :
(Add Windows to your Uses clause)
{A function that returns CPU usage (in percent) for a given process id}
function
GetCpuUsage(PID:cardinal):single;
const
cWaitTime=750;
h : Cardinal;
mCreationTime,mExitTime,mKernelTime, mUserTime:_FILETIME;
TotalTime1,TotalTime2:int64;
begin
{We need to get a handle of the process with PROCESS_QUERY_INFORMATION privileges.}
h:=OpenProcess(PROCESS_QUERY_INFORMATION,false,PID);
{We can use the GetProcessTimes() function to get the amount of time the process has spent in kernel mode and user mode.}
GetProcessTimes(h,mCreationTime,mExitTime,mKernelTime,mUserTime);
TotalTime1:=int64(mKernelTime.dwLowDateTime
or
(mKernelTime.dwHighDateTime
shr
32)) + int64(mUserTime.dwLowDateTime
or
(mUserTime.dwHighDateTime
shr
32));
{Wait a little}
Sleep(cWaitTime);
GetProcessTimes(h,mCreationTime,mExitTime,mKernelTime,mUserTime);
TotalTime2:=int64(mKernelTime.dwLowDateTime
or
(mKernelTime.dwHighDateTime
shr
32))+
int64(mUserTime.dwLowDateTime
or
(mUserTime.dwHighDateTime
shr
32));
{This should work out nicely, as there were approx. 250 ms between the calls
and the result will be a percentage between 0 and 100}
Result:=((TotalTime2-TotalTime1)/cWaitTime)/100;
CloseHandle(h);
end
;
Method 2
The previous method has some obvious shortcomings – it pauses the program for the specified time every time it is run and is a bit inefficient when used repeatedly (e.g. to create a CPU usage graph for a process). That’s why I wrote a simple unit that is more flexible and still easy to use. You can
download
it or copy & paste the code below.
Using the unit
When starting to monitor a process, call cnt:=wsCreateUsageCounter(Process_id) to initialize a usage counter. When you need to get the current CPU usage of that process, use usage:=wsGetCpuUsage(cnt). When you have finished monitoring the process, call wsDestroyUsageCounter(cnt) to free memory used by usage counter and close open handles.
The uCpuUsage unit
unit
uCpuUsage;
interface
const
wsMinMeasurementInterval=250;
{minimum amount of time that must have elapsed to calculate CPU usage, miliseconds. If time elapsed is less than this, previous result is returned, or zero, if there is no previous result.}
TCPUUsageData=record
PID,Handle:cardinal;
oldUser,oldKernel:Int64;
LastUpdateTime:cardinal;
LastUsage:single;
//Last result of wsGetCpuUsage is saved here
Tag:cardinal;
//Use it for anythin you like, not modified by this unit
end
;
PCPUUsageData=^TCPUUsageData;
function
wsCreateUsageCounter(PID:cardinal):PCPUUsageData;
function
wsGetCpuUsage(aCounter:PCPUUsageData):single;
procedure
wsDestroyUsageCounter(aCounter:PCPUUsageData);
implementation
uses
Windows;
function
wsCreateUsageCounter(PID:cardinal):PCPUUsageData;
p:PCPUUsageData;
mCreationTime,mExitTime,mKernelTime, mUserTime:_FILETIME;
h:cardinal;
begin
result:=nil;
//We need a handle with PROCESS_QUERY_INFORMATION privileges
h:=OpenProcess(PROCESS_QUERY_INFORMATION,false,PID);
if
h=0
then
exit;
new(p);
p.PID:=PID;
p.Handle:=h;
p.LastUpdateTime:=GetTickCount;
p.LastUsage:=0;
if
GetProcessTimes(p.Handle, mCreationTime, mExitTime, mKernelTime, mUserTime)
then
begin
//convert _FILETIME to Int64
p.oldKernel:=int64(mKernelTime.dwLowDateTime
or
(mKernelTime.dwHighDateTime
shr
32));
p.oldUser:=int64(mUserTime.dwLowDateTime
or
(mUserTime.dwHighDateTime
shr
32));
Result:=p;
end
else
begin
dispose(p);
end
;
end
;
procedure
wsDestroyUsageCounter(aCounter:PCPUUsageData);
begin
CloseHandle(aCounter.Handle);
dispose(aCounter);
end
;
function
wsGetCpuUsage(aCounter:PCPUUsageData):single;
mCreationTime,mExitTime,mKernelTime, mUserTime:_FILETIME;
DeltaMs,ThisTime:cardinal;
mKernel,mUser,mDelta:int64;
begin
result:=aCounter.LastUsage;
ThisTime:=GetTickCount;
//Get the time elapsed since last query
DeltaMs:=ThisTime-aCounter.LastUpdateTime;
if
DeltaMs < wsMinMeasurementInterval
then
exit;
aCounter.LastUpdateTime:=ThisTime;
GetProcessTimes(aCounter.Handle,mCreationTime, mExitTime, mKernelTime, mUserTime);
//convert _FILETIME to Int64.
mKernel:=int64(mKernelTime.dwLowDateTime
or
(mKernelTime.dwHighDateTime
shr
32));
mUser:=int64(mUserTime.dwLowDateTime
or
(mUserTime.dwHighDateTime
shr
32));
//get the delta
mDelta:=mUser+mKernel-aCounter.oldUser-aCounter.oldKernel;
aCounter.oldUser:=mUser;
aCounter.oldKernel:=mKernel;
Result:=(mDelta/DeltaMs)/100;
//mDelta is in units of 100 nanoseconds, so…
aCounter.LastUsage:=Result;
//just in case you want to use it later, too
end
;
end
.
Related posts :
Process memory usage
How To Get Per-Core CPU Usage
Manipulating Taskbar Buttons
Listing All Open Handles
Hiding from NT TaskManager
This entry was posted on Sunday, August 27th, 2006 at 20:07 and is filed under
Miscellany
.
You can follow any responses to this entry through the
RSS 2.0
feed.
You can
leave a response
, or
trackback
from your own site.
In the following code:
TotalTime:=int64(mKernelTime.dwLowDateTime or (mKernelTime.dwHighDateTime shr 32));
‘shr’ should be ‘shl’ ?
The correct syntax for converting FILETIME to int64 is this:
mKernel := mKernelTime.dwLowDateTime or int64(mKernelTime.dwHighDateTime) shl 32;
If you do not case the dwHighTime (which is a double word, or unsigned 32 bit) you will simply wrap the value back on itself. Go ahead and try it in a watch window. 1 shl 32 is 1. int64(1) shl 32 gets you the right value. I discovered this with an application that used a lot of CPU and every couple minutes it would return a negative usage because the value wrapped the 32 bit boundary.
Privacy & Cookies: This site uses cookies. By continuing to use this website, you agree to their use.
To find out more, including how to control cookies, see here:
Privacy Policy