Sunius
Sunius

Reputation: 2907

c++/c# system and programs resource monitoring - Windows

I was thinking of making an improved version of Windows resource monitor, which not only included resource usage, but also hardware status, such as temperatures and fan speeds.

I've started programming it yesterday using c++, and I've managed to make it list and refresh memory and CPU utilization of every process on my computer.

However, here comes the issue: it's terribly slow. It uses 5% of my i5 [email protected] GHz when polling the usage once every second. Which is unacceptable, because I want not only CPU and memory usage, but also disk usage for every hard drive, internet usage, GPU usage (all of those for every process), then I also want to monitor all the temperatures too.

Here's my code:

const double MB = 1024*1024;
const double KB = 1024;

struct WinProcess
{
    wstring Name;
    unsigned int ID;
    wstring UserName;
    double cpuUsage;
    long long int ramUsage;

    WinProcess(wstring name, unsigned int id) : Name(name), ID(id), UserName(L"Not set"), cpuUsage(0), ramUsage(0) {}
};

class ResourceMonitor
{
private:
    void** hquery;
    void** hcountercpu;
    void** hcountermem;

    int cpuCores;

    vector<WinProcess> Processes;
public:
    ResourceMonitor();
    ~ResourceMonitor();

    void StartResourceMonitor();
    void MeasureResourceUsage();
    void PrintUsage();
};

ResourceMonitor::ResourceMonitor() : 
    Processes(GetProcessList())    // Function GetProcessList() returns vector with all processes, their IDs and username, from which it was launched
{
    hquery = new void*[Processes.size()];
    hcountercpu = new void*[Processes.size()];
    hcountermem = new void*[Processes.size()];

    for (unsigned int i = 2; i < Processes.size(); i++)
    {
        Processes[i].Name = Processes[i].Name.substr(0, Processes[i].Name.size() - 4);
    }

    for (unsigned int i = 2; i < Processes.size(); i++)
    {
        wstring CounterPathCPU = L"\\Process(" + Processes[i].Name + L")\\% Processor Time";
        wstring CounterPathMEM = L"\\Process(" + Processes[i].Name + L")\\Private Bytes";

        if ((PdhOpenQuery(NULL, 0, &hquery[i])) != ERROR_SUCCESS
            || PdhAddCounter(hquery[i], CounterPathCPU.c_str(), 0, &hcountercpu[i]) != ERROR_SUCCESS
            || PdhAddCounter(hquery[i], CounterPathMEM.c_str(), 0, &hcountermem[i]) != ERROR_SUCCESS
            || PdhCollectQueryData(hquery[i]) != ERROR_SUCCESS)
        {
            continue;
        }
    }

    SYSTEM_INFO sysinfo;
    GetSystemInfo(&sysinfo);
    cpuCores = sysinfo.dwNumberOfProcessors;

    wcout.setf(ios::fixed);
}

ResourceMonitor::~ResourceMonitor()
{    
    for (unsigned int i = 2; i < Processes.size(); i++)
    {
        PdhCloseQuery(hquery[i]);
        PdhRemoveCounter(hcountercpu[i]);
        PdhRemoveCounter(hcountermem[i]);
    }

    delete[] hquery;
    delete[] hcountercpu;
}

void ResourceMonitor::StartResourceMonitor()
{
    system("mode CON: COLS=150");

    while (1)
    {
        MeasureResourceUsage();
        PrintUsage();
        Sleep(1000);
    }
}

void ResourceMonitor::MeasureResourceUsage()
{
    PDH_FMT_COUNTERVALUE countervalcpu;
    PDH_FMT_COUNTERVALUE countervalmem;

    for (unsigned int i = 2; i < Processes.size(); i++)
    {
        if ((PdhCollectQueryData(hquery[i])) != ERROR_SUCCESS)
        {   
            printError(L"Error on collecting query data: ");
            continue;
        }        
        if ((PdhGetFormattedCounterValue(hcountercpu[i], PDH_FMT_LONG | PDH_FMT_NOCAP100, 0, &countervalcpu)) != ERROR_SUCCESS)
        {
            printError(L"Error on CPU usage retrieval: ");
            continue;
        }
        if ((PdhGetFormattedCounterValue(hcountermem[i], PDH_FMT_LONG, 0, &countervalmem)) != ERROR_SUCCESS)
        {
            printError(L"Error on Memory usage retrieval: ");
            continue;
        }

        Processes[i].cpuUsage = countervalcpu.longValue / (double)cpuCores;
        Processes[i].ramUsage = countervalmem.longValue;
    }
}

void ResourceMonitor::PrintUsage()
{
    system("cls");
    long long int RAMSUM = 0;
    for (unsigned int i = 2; i < Processes.size(); i++)
    {
        wcout << left << setw(40) << Processes[i].Name + L".exe: ";
        wcout << setw(6) << left << L" | ID " << setw(4) << right << Processes[i].ID;
        wcout << setw(8) << left << L" | User " << setw(15) << Processes[i].UserName;
        wcout << setw(8) << left << L" | CPU usage " << setw(10) << right << setprecision(2) << Processes[i].cpuUsage << setw(1) << L"%";
        wcout << setw(8) << left << L" | RAM usage " << setw(15) << right << setprecision(3) << Processes[i].ramUsage / MB << setw(3) << left << L" MB" << endl;

        RAMSUM += Processes[i].ramUsage;
    }
    wcout << L"Total RAM usage: " << setw(7) << right << RAMSUM / MB
        << setw(3) << left << L" MB" << endl;
}

I'm well aware issues surrounding this code: the process list never gets updated, and there's no GUI, but those are fixable, as getting process list function is pretty fast.

Is there a way to check CPU and memory usage task, as windows task manager does? I can use c++ or c#, as I know both of the languages. Lastly, I have no idea how to monitor network traffic, disk usage and GPU usage for each process. Any ideas? Thanks for the help!

Upvotes: 1

Views: 5605

Answers (1)

I was using Delphi when I was last working with cpu and mem stats, but the calls are pretty much right into windows APIs. I'm including the Delphi/pascal source at the end of this answer so you can see the actual code I used. This covers CPU and memory usage, but I don't have an answer for your last question about network traffic, disk usage and GPU usage.

These links cover the techniques I used, but for c/c++. For c#, this is all nicely wrapped up in the System.Diagnostics.Process class, and very easy to access.

Memory usage:

http://msdn.microsoft.com/en-us/library/windows/desktop/ms683219%28v=vs.85%29.aspx

http://msdn.microsoft.com/en-us/library/windows/desktop/ms684877%28v=vs.85%29.aspx

How to use GetProcessMemoryInfo in C++?

How to get memory usage under Windows in C++

CPU time:

http://msdn.microsoft.com/en-us/library/windows/desktop/ms683223%28v=vs.85%29.aspx

CPU Process time using GetProcessTimes and FileTimeToSystemTime do not work in 64 bit win

// this member var is referenced within the function
mLastMemInfo: TProcessMemoryCounters;

procedure TProcess.UpdateStats(handle: Cardinal);
var
  creation, exit, kernel, user: FILETIME;
  sysTime: TSystemTime;
  iKernel: Int64;
  iUser: Int64;
  totalTime: Int64;
  tickCount: Cardinal;
  tDelta: Cardinal;
begin
  if (handle <> INVALID_HANDLE) then begin
    tickCount := GetTickCount;
    tDelta := tickCount - mLastStatsTick;
    // only update stats if its been 500 ms or more since last check
    // or if we haven't checked at all yet
    if (tDelta >= 500) then begin
      // get cpu stats
      if GetProcessTimes(handle, creation, exit, kernel, user) then begin
        if (mCreationTime = 0) then begin
          if (FileTimeToSystemTime(creation, sysTime)) then begin
            mCreationTime := SystemTimeToDateTime(sysTime);
          end;
        end;
        iKernel := kernel.dwHighDateTime;
        iKernel := (iKernel shl 32) or kernel.dwLowDateTime;
        iUser := user.dwHighDateTime;
        iUser := (iUser shl 32) or user.dwLowDateTime;
        iKernel := iKernel div 10; // convert 100nanos to microseconds
        iUser := iUser div 10; // convert 100nanos to microseconds
        mLastKernelDelta := iKernel - mLastKernelTime;
        mLastUserDelta := iUser - mLastUserTime;
        totalTime := mLastKernelDelta + mLastUserDelta;
        if (totalTime <= 0) and (mLastStatsTick = 0) then begin
          mLastCpuUsage := 0;
        end else begin
          mLastCpuUsage := (totalTime / NumberOfProcessors) / (tDelta * 1000);
          if (mLastCpuUsage > 1.0) then mLastCpuUsage := 1.0;
        end;
        mLastKernelTime := iKernel;
        mLastUserTime := iUser;
      end else begin
        mLastCpuUsage := 0;
      end;
      // get memory stats
      mLastMemInfo.cb := SizeOf(mLastMemInfo);
      if not GetProcessMemoryInfo(handle, @mLastMemInfo,
                    SizeOf(mLastMemInfo)) then begin
        mLastMemInfo.cb := 0; // flag data as no good
      end;
      // set the time we got the stats
      mLastStatsTick := tickCount;
    end;
  end;
end;

Upvotes: 3

Related Questions