First thing we have to keep in mind when we try to get the CPU load for windows processes is the fact that Windows isn't magically calculating that percentage.
In fact, instead of maintaining a percentage that wouldn't be useful, Windows, keeps track, for each process, of the time that the process has been executing on user mode, the time it has been executing in kernel mode and the instant the process was created.
With all that data we can't just obtain the instant CPU load for a process. We can get the average CPU load for the process by a simple arithmetic calculation (AvgTime = User Time + Kernel Time / Actual Time - Creation Time). We could say that instantaneous CPU load value doesn't exist, just like Electric Potential :D
Ok, it doesn't exists, Should I leave now? No!, now that I've managed to get you here just keep reading!
We obviously cannot obtain the instant CPU load but, as I've already told you, we can get the average between two given times. If that "given times" are close enough the average load we'll be near the instant load, the closer they are, the nearer you'll get. In fact the Windows resource meter does just that. It obtains the CPU load for each process in one second intervals. I would consider that as a reasonable interval, if we reduce the interval further the measure itself could affect the CPU load.
Ok, so now that we know the data we will need and the method to calculate the CPU load let's create a simple class to do it. In order to do so we are going to create a class which will encapsulates the information about a given process and we will need a place to store the information about the last call that was made so when we call again we will be able to get the actual CPU time and the last CPU time.
{ TProcessInformation }
constructor TProcessInformation.Create(Pid: Cardinal);
begin
FPid := Pid;
FThreadList := TThreadList.Create;
// Open the process and get its handle
FHandle := OpenProcess(PROCESS_QUERY_INFORMATION,false,Pid);
if FHandle = 0 then
FValid := false
else
FValid := true;
Update;
end;
destructor TProcessInformation.Destroy;
var list : TList;
begin
CloseHandle(FHandle);
inherited;
end;
function TProcessInformation.GetAverageCpuLoad: Cardinal;
var
lpCreationTime, lpExitTime,
lpKernelTime, lpUserTime : TFileTime;
workingTime,lifeTime : Int64;
currentSystemTime : TSystemTime;
currentFileTime : TFileTime;
res : Double;
begin
result := 0;
if FValid then
begin
// Get the process times
if GetProcessTimes(FHandle,lpCreationTime,lpExitTime,lpKernelTime,lpUserTime) then
begin
GetSystemTime(currentSystemTime);
SystemTimeToFileTime(currentSystemTime,currentFileTime);
lifeTime := Int64(currentFileTime) - Int64(lpCreationTime);
workingTime := Int64(lpKernelTime) + Int64(lpUserTime);
res := (workingTime / lifeTime);
result := Round((res / SystemStats.SystemInfo.NumberOfProcessors) * 100);
end
else
FValid := false;
end;
end;
function TProcessInformation.GetInstantCpuLoad: Cardinal;
var
lpCreationTime, lpExitTime,
lpKernelTime, lpUserTime : TFileTime;
workingTime, workingInterval,lifeInterval : Int64;
currentSystemTime : TSystemTime;
currentFileTime : TFileTime;
res : Double;
begin
result := 0;
if FValid then
begin
// Get the process times
if GetProcessTimes(FHandle,lpCreationTime,lpExitTime,lpKernelTime,lpUserTime) then
begin
GetSystemTime(currentSystemTime);
SystemTimeToFileTime(currentSystemTime,currentFileTime);
lifeInterval := Int64(currentFileTime) - Int64(FLastUpdateTime);
workingTime := Int64(lpKernelTime) + Int64(lpUserTime);
workingInterval := workingTime - FLastWorkingTime;
FLastWorkingTime := workingTime;
FLastUpdateTime := currentFileTime;
res := (workingInterval / lifeInterval);
result := Round((res * 100);
end
else
FValid := false;
end;
end;
Let's review the above code. We're using several calls to the Windows API, to functions located into the kernel.dll library. The first call is GetProcessesTimes which return a wide range of information about the process (as creation time, finalization time as well as the user CPU time and the kernel CPU time). On the other hand we're using a call to the function GetSystemTime to get the actual time of the system. This way we have the time the process has been executing (user time + kernel time) as well as the interval in which we are measuring (which will be the lifespan of the process for the average CPU load or the time from the last call for the instant CPU load). We must also convert all the results to Int64 to be able to correctly make the calculations.
There's another API function called GetThreadTimes which gets exactly the same parameters but regarding the process threads. The code would be the same but exchanging the call to GetProcessTimes for this one.
Using the .Net Framework, getting the CPU load is mucho more simpler as it provides a single Process class which, among other things, will allow us to obtain the CPU load for the process.
void GetAverageCPULoad()
{
// For the current process
Process proceso = Process.GetCurrentProcess();
// Or for any other process given its pid
// Process proceso = Process.GetProcessById(345);
/* Get the timespan since the process started executing */
System.TimeSpan lifeInterval = (DateTime.Now - proceso.StartTime);
// Get the CPU load
float CPULoad = (proceso.TotalProcessorTime.TotalMilliseconds /
lifeInterval.TotalMilliseconds) * 100;
}
There are to side notes to this:
Note:The TSystemInfo struct is predefined in Delphi 2006. I haven't been able to check if that is so also in Delphi 7. If it isn't then we should define it so that it matches the windows struct SYSTEM_INFO