IntelliTask – DEVELOPPARADISE
17/07/2018

IntelliTask

IntelliTask

Introduction

Task Manager shows you the programs, processes, and services that are currently running on your computer. You can use Task Manager to monitor your computer’s performance or to close a program that is not responding.

Background

The easiest way to check the current running processes is to create a snapshot of memory. To accomplish this, we use CreateToolhelp32Snapshot, Process32First, and Process32Next functions, which has the following syntax:

HANDLE WINAPI CreateToolhelp32Snapshot(
	DWORD dwFlags,
	DWORD th32ProcessID
);

Parameters

  • dwFlags: The portions of the system to be included in the snapshot. This parameter can be one or more of the following values: TH32CS_INHERIT (indicates that the snapshot handle is to be inheritable), TH32CS_SNAPALL (includes all processes and threads in the system, plus the heaps and modules of the process specified in th32ProcessID.), TH32CS_SNAPHEAPLIST (includes all heaps of the process specified in th32ProcessID in the snapshot.), TH32CS_SNAPMODULE (includes all modules of the process specified in th32ProcessID in the snapshot.), TH32CS_SNAPMODULE32 (includes all 32-bit modules of the process specified in th32ProcessID in the snapshot when called from a 64-bit process.), TH32CS_SNAPPROCESS (includes all processes in the system in the snapshot.), and TH32CS_SNAPTHREAD (includes all threads in the system in the snapshot).
  • th32ProcessID: The process identifier of the process to be included in the snapshot. This parameter can be zero to indicate the current process. This parameter is used when the TH32CS_SNAPHEAPLIST, TH32CS_SNAPMODULE, TH32CS_SNAPMODULE32, or TH32CS_SNAPALL value is specified. Otherwise, it is ignored and all processes are included in the snapshot.

Return Value

If the function succeeds, it returns an open handle to the specified snapshot.

If the function fails, it returns INVALID_HANDLE_VALUE. To get extended error information, call GetLastError. Possible error codes include ERROR_BAD_LENGTH.

BOOL WINAPI Process32First(
	HANDLE hSnapshot,
	LPPROCESSENTRY32 lppe
);
BOOL WINAPI Process32Next(
	HANDLE hSnapshot,
	LPPROCESSENTRY32 lppe
);

Parameters

  • hSnapshot: A handle to the snapshot returned from a previous call to the CreateToolhelp32Snapshot function.
  • lppe: A pointer to a PROCESSENTRY32 structure. It contains process information such as the name of the executable file, the process identifier, and the process identifier of the parent process.

Return Value

Returns TRUE if the first entry of the process list has been copied to the buffer or FALSE otherwise. The ERROR_NO_MORE_FILES error value is returned by the GetLastError function if no processes exist or the snapshot does not contain process information.

So, our implementation using the above functions will be:

BOOL CSystemSnapshot::Refresh()
{
   HANDLE hSnapshot = NULL;
   PROCESSENTRY32 pe32 = { 0 };

   VERIFY(RemoveAll());
   if ((hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)) != INVALID_HANDLE_VALUE)
   {
      pe32.dwSize = sizeof(PROCESSENTRY32);
      if (!Process32First(hSnapshot, &pe32))
      {
         return FALSE;
      }

      do
      {
         VERIFY(InsertProcess(pe32));
      } while (Process32Next(hSnapshot, &pe32));

      VERIFY(CloseHandle(hSnapshot));
   }
   else
   {
      return FALSE;
   }
   return TRUE;
}

BOOL CSystemSnapshot::InsertProcess(PROCESSENTRY32& pe32)
{
   HANDLE hProcess = NULL;
   PROCESS_MEMORY_COUNTERS pmc = { 0 };
   pmc.cb = sizeof(PROCESS_MEMORY_COUNTERS);

   CProcessData* pProcessData = new CProcessData();
   m_arrProcessList.Add(pProcessData);
   pProcessData->SetProcessID(pe32.th32ProcessID);
   pProcessData->SetParentProcessID(pe32.th32ParentProcessID);
   pProcessData->SetFileName(pe32.szExeFile);

   if ((hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | 
   			PROCESS_VM_READ, FALSE, pe32.th32ProcessID)) != NULL)
   {
      pProcessData->m_pCpuUsage.SetProcessID(pe32.th32ProcessID);
      pProcessData->SetProcessorUsage(pProcessData->m_pCpuUsage.GetUsage());
      if (GetProcessMemoryInfo(hProcess, &pmc, pmc.cb))
      {
         pProcessData->SetMemoryUsage(pmc.WorkingSetSize);
      }

      TCHAR lpszFullPath[MAX_PATH] = { 0 };
      if (GetModuleFileNameEx(hProcess, NULL, lpszFullPath, MAX_PATH))
      {
         CVersionInfo pVersionInfo;
         if (pVersionInfo.Load(lpszFullPath))
         {
            pProcessData->SetDescription(pVersionInfo.GetFileDescription());
            pProcessData->SetCompany(pVersionInfo.GetCompanyName());
            pProcessData->SetVersion(pVersionInfo.GetFileVersionAsString());
         }
      }
      VERIFY(CloseHandle(hProcess));
   }
   return TRUE;
}

CProcessData* CSystemSnapshot::UpdateProcess(DWORD dwProcessID)
{
   HANDLE hProcess = NULL;
   PROCESS_MEMORY_COUNTERS pmc = { 0 };
   pmc.cb = sizeof(PROCESS_MEMORY_COUNTERS);

   CProcessData* pProcessData = GetProcessID(dwProcessID);
   if (pProcessData != NULL)
   {
      if ((hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | 
                 PROCESS_VM_READ, FALSE, dwProcessID)) != NULL)
      {
         pProcessData->SetProcessorUsage(pProcessData->m_pCpuUsage.GetUsage());
         if (GetProcessMemoryInfo(hProcess, &pmc, pmc.cb))
         {
            pProcessData->SetMemoryUsage(pmc.WorkingSetSize);
         }
         VERIFY(CloseHandle(hProcess));
      }
      return pProcessData;
   }
   return NULL;
}

IntelliTask

The Architecture

Each process definition is contained in a CProcessData class, with the following interface:

  • DWORD GetProcessID(); – Gets ID of the current process definition
  • SetProcessID(DWORD dwProcessID); – Sets ID for the current process definition
  • DWORD GetParentProcessID(); – Gets parent’s process ID for current process definition
  • void SetParentProcessID(DWORD dwParentProcessID); – Sets parent’s process ID for current process definition
  • DWORD GetPriority(); – Gets priority for current process definition
  • void SetPriority(DWORD dwPriority); – Sets priority for current process definition
  • DOUBLE GetProcessorUsage(); – Gets CPU usage for current process definition
  • void SetProcessorUsage(DOUBLE cpuUsage); – Sets CPU usage for current process definition
  • DWORD GetMemoryUsage(); – Gets memory usage for current process definition
  • void SetMemoryUsage(DWORD memUsage); – Sets memory usage for current process definition
  • CString GetFileName(); – Gets file name for current process definition
  • void SetFileName(CString strFileName); – Sets file name for current process definition
  • CString GetFilePath(); – Gets file path for current process definition
  • void SetFilePath(CString strFilePath); – Sets file path for current process definition
  • CString GetDescription(); – Gets description for current process definition
  • void SetDescription(CString strDescription); – Sets description for current process definition
  • CString GetCompany(); – Gets company for current process definition
  • void SetCompany(CString strCompany); – Sets company for current process definition
  • CString GetVersion(); – Gets version for current process definition
  • void SetVersion(CString strVersion); – Sets version for current process definition

Then, we define CProcessList as typedef CArray<CProcessData*> CProcessList;.

This list is managed inside the CSystemSnapshot class, with the following interface:

  • BOOL RemoveAll(); – Removes all process definitions from list
  • int GetSize(); – Gets the size of process definition list
  • CProcessData* GetAt(int nIndex); – Geta an process definition from list
  • BOOL Refresh(); – Updates the status for each process definition from list
  • BOOL InsertProcess(PROCESSENTRY32& pe32); – Inserts a process definition into list
  • CProcessData* UpdateProcess(DWORD dwProcessID); – Updates a process definition from list
  • BOOL DeleteProcess(DWORD dwProcessID); – Removes a process definition from list

Points of Interest

The challenge for IntelliTask was to compute exact CPU usage for each process, as shown below:

CpuUsage::CpuUsage(void)
   : m_dwProcessID(0)
   , m_nCpuUsage(-1)
   , m_dwLastRun(0)
   , m_lRunCount(0)
{
   ZeroMemory(&m_ftPrevSysKernel, sizeof(FILETIME));
   ZeroMemory(&m_ftPrevSysUser, sizeof(FILETIME));
   ZeroMemory(&m_ftPrevProcKernel, sizeof(FILETIME));
   ZeroMemory(&m_ftPrevProcUser, sizeof(FILETIME));
}

/**********************************************
* CpuUsage::GetUsage
* returns the percent of the CPU that this process
* has used since the last time the method was called.
* If there is not enough information, -1 is returned.
* If the method is recalled to quickly, the previous value
* is returned.
***********************************************/
DOUBLE CpuUsage::GetUsage()
{
   HANDLE hProcess = NULL;
   // create a local copy to protect against race conditions in setting the member variable
   DOUBLE nCpuCopy = m_nCpuUsage;
   if (::InterlockedIncrement(&m_lRunCount) == 1)
   {
      // If this is called too often, the measurement itself will greatly affect the results.
      if (!EnoughTimePassed())
      {
         ::InterlockedDecrement(&m_lRunCount);
         return nCpuCopy;
      }

      FILETIME ftSysIdle, ftSysKernel, ftSysUser;
      FILETIME ftProcCreation, ftProcExit, ftProcKernel, ftProcUser;
      if ((hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | 
              PROCESS_VM_READ, FALSE, m_dwProcessID)) != NULL)
      {
         if (!GetSystemTimes(&ftSysIdle, &ftSysKernel, &ftSysUser) ||
            !GetProcessTimes(hProcess, &ftProcCreation,
            &ftProcExit, &ftProcKernel, &ftProcUser))
         {
            ::InterlockedDecrement(&m_lRunCount);
            CloseHandle(hProcess);
            return nCpuCopy;
         }
         CloseHandle(hProcess);
         hProcess = NULL;

         /* CPU usage is calculated by getting the total amount of time 
         the system has operated since the last measurement 
         (made up of kernel + user) and the total
         amount of time the process has run (kernel + user) */
         ULONGLONG ftSysIdleDiff = SubtractTimes(ftSysIdle, m_ftPrevSysIdle);
         ULONGLONG ftSysKernelDiff = SubtractTimes(ftSysKernel, m_ftPrevSysKernel);
         ULONGLONG ftSysUserDiff = SubtractTimes(ftSysUser, m_ftPrevSysUser);

         ULONGLONG ftProcKernelDiff = SubtractTimes(ftProcKernel, m_ftPrevProcKernel);
         ULONGLONG ftProcUserDiff = SubtractTimes(ftProcUser, m_ftPrevProcUser);

         ULONGLONG nTotalSys =  ftSysKernelDiff + ftSysUserDiff;
         ULONGLONG nTotalProc = ftProcKernelDiff + ftProcUserDiff;

         if (nTotalSys > 0)
         {
            m_nCpuUsage = ((100.0 * nTotalProc) / nTotalSys);
         }

         m_ftPrevSysIdle = ftSysIdle;
         m_ftPrevSysKernel = ftSysKernel;
         m_ftPrevSysUser = ftSysUser;
         m_ftPrevProcKernel = ftProcKernel;
         m_ftPrevProcUser = ftProcUser;

         m_dwLastRun = GetTickCount64();
         nCpuCopy = m_nCpuUsage;
      }
   }

   ::InterlockedDecrement(&m_lRunCount);
   return nCpuCopy;
}

ULONGLONG CpuUsage::SubtractTimes(const FILETIME& ftA, const FILETIME& ftB)
{
   LARGE_INTEGER a, b;
   a.LowPart = ftA.dwLowDateTime;
   a.HighPart = ftA.dwHighDateTime;
   b.LowPart = ftB.dwLowDateTime;
   b.HighPart = ftB.dwHighDateTime;
   return a.QuadPart - b.QuadPart;
}

bool CpuUsage::EnoughTimePassed()
{
   const int minElapsedMS = 250; //milliseconds
   ULONGLONG dwCurrentTickCount = GetTickCount64();
   return (dwCurrentTickCount - m_dwLastRun) > minElapsedMS;
}

Final Words

IntelliTask application uses many components that have been published on The Code Project. Many thanks to:

  • my CMFCListView form view (see source code);
  • PJ Naughter for his CVersionInfo class.

Further plans: I would like to add support for services, and additional management functions for processes as soon as possible.

History

  • Version 1.3 (January 18th, 2014) – Initial release