Alphasite

The programmers site

Estadísticas web

Estadisticas web

Publicidad

Languages

Inicio de sesión

Google AdSense

Encuesta

En línea

En este momento hay 0 usuarios y 9 invitados en línea.

Get process and thread cpu time/load


Introduction

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

Getting information about the process

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.

Obtaining CPU Load with Delphi

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.

type TProcessInformation = class
  private
    FLastUpdateTime : TFileTime;
    FLastWorkingTime : Int64;
    FValid : boolean;
    FHandle : THandle;
    function GetAverageCpuLoad: Cardinal;
    function GetInstantCpuLoad: Cardinal;
  protected
    FPid : Cardinal;
  public
    constructor Create(Pid : Cardinal);
    destructor Destroy; override;
    { The process id }
    property Pid : Cardinal read FPid;
    { Gets the average cpu load (all times)}
    property AverageCpuLoad : Cardinal read GetAverageCpuLoad;
    { Gets the instant cpu load }
    property InstantCpuLoad : Cardinal read GetInstantCpuLoad;
    { A boolean value indicating if the process is still in execution }
    property Valid : boolean read FValid;
end;

{ 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.

Getting the CPU load with .NET

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.

using System.Diagnostics;

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;
}

Final notes

There are to side notes to this:

  • The process explained here does only work on NT family as the GetProcessTimes function is only available from Win NT 3.5 to Win 2003/Vista but it is not in Windows 95, Windows 98 or Windows Me
  • The call to GetProcessTimes returns the elapsed time for the process in a given CPU, if we are dealing with a mono processor system that will match the total CPU load, nevertheless if the system has more than one CPU then, in order to get the real workload we should divide it between the number of CPUs

Getting the number of CPUs

function GetNumberOfProcessors : Integer;
var
  Info : TSystemInfo;
begin
  GetSystemInfo(Info);
  result := Info.dwNumberOfProcessors;
end;

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