Lo primero que tenemos que considerar al intentar obtener los tiempos de uso de CPU de los procesos de Windows y de sus tareas es el hecho de que windows no está, mágicamente, calculando dicho porcentaje.
En lugar de mantener un porcentaje que no le serviría para nada, Windows, para cada proceso, guarda el tiempo de CPU que este ha estado en ejecución en modo usuario, el tiempo que ha estado en ejecución en modo sistema y el instante de creación del proceso.
Dados estos datos no es posible obtener directamente el consumo de CPU instantaneo de un proceso. Podremos obtener el consumo medio de uso de CPU por parte del proceso haciendo (TiempoMedio = TiempoUsuario + TiempoSistema / TiempoActual - TiempoCreación). Podríamos decir que la medida del consumo instantaneo de CPU es como el potencial eléctrico, no existe XD.
Vale, no existe, ¿dejo de leer?. ¡No!, ahora que he conseguido que leas hasta aquí no te vayas...
Obviamente no podemos conseguir la medida instantanea de carga para un proceso en un instante dado pero si podemos calcular la carga media de la CPU entre dos instantes de tiempo, si dichos instantes de tiempo son relativamente cercanos la medición efectuada es similar a una medición instantanea, de hecho, el monitor de rendimiento de windows calcula la carga media en periodos de un segundo (y esa medida debería ser suficiente para cualquier aplicación ya que si reducimos mucho más el intervalo podemos conseguir que la llamada de consulta incremente en exceso la propia carga de la CPU, ¡una indeterminación de Heisenberg en programación!).
Vale, puesto que ya tenemos claros los datos que necesitamos vamos a necesitar y como calcular nuestra carga de CPU vamos a crear una clase sencilla que nos permita calcularla. Para ello vamos a crear una clase que encapsulará la información de un proceso, en concreto y fundamentalmente necesitamos un sitio (que será un miembro privado de la clase) donde almacenar el momento en que se produjo la última llamada, de forma que podamos calcular el tiempo que ha pasado desde entonces.
{ TProcessInformation }
constructor TProcessInformation.Create(Pid: Cardinal);
begin
FPid := Pid;
FThreadList := TThreadList.Create;
// Abrir y obtener el handle del proceso
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
// Obtener los tiempos del proceso
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
// Obtener los tiempos del proceso
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;
Aunque el código anterior es bastante claro vamos a darle un repaso rápido. Estamos usando varias llamadas a la API de windows, a funciones situadas en el kernel.dll. La primera de ellas es GetProcessesTimes que devuelve diversa información sobre el proceso (momento de creación, de finalización así como el tiempo de uso de CPU en modo usuario y el tiempo de uso en modo kernel), por otro lado utilizamos la llamada a la funcion GetSystemTime para obtener el tiempo actual del sistema. De esta forma tenemos el tiempo de uso de CPU del proceso (tiempo de usuario + tiempo de sistema) así como el periodo en que queremos calcular la media (que será el tiempo de vida del proceso para la media de uso de cpu o el intervalo desde la última llamada para el calculo). Además hay que convertir todas las llamadas a Int64 para poder realizar los cálculos correctamente.
Hay otra llamada a la API de windows llamada GetThreadTimes que obtiene los mismos parametros pero para los distintos hilos de un proceso. El código sería practicamente el mismo pero cambiando la llamada.
En .Net es mucho más sencillo obtener la carga de CPU ya que el framework nos provee de una clase Process que, entre otras cosas, nos va a servir para calcular la carga de CPU produciad por el proceso.
void GetAverageCPULoad()
{
// Para el proceso actual
Process proceso = Process.GetCurrentProcess();
// O si lo quisieramos para cualqueir otro proceso
// Process proceso = Process.GetProcessById(345);
/* Obtener el intervalo de tiempo desde que
se inicio el proceso */
System.TimeSpan lifeInterval = (DateTime.Now - proceso.StartTime);
// Calcular el uso de CPU
float CPULoad = (proceso.TotalProcessorTime.TotalMilliseconds /
lifeInterval.TotalMilliseconds) * 100;
}
Hay dos apuntes finales que son importantes:
Nota: La estructura TSystemInfo se que está predefinida en Delphi 2006, no he podido comprobarlo en Delphi 7, si no lo estuviera habría que definirla para que coincidiera con la estructura de windows SYSTEM_INFO