How To Get Per-Core CPU Usage

It’s pretty easy to get the average CPU usage, but how about calculating the per-code load on multicore systems? Turns out it’s also simple enough if you use performance counters.

Windows API includes a subset of functions that provide various performance-related information, which includes data on how busy individual processors or cores are. In this post I’ll show you how to retrieve and use this information. I’ve also included a simple example application that demonstrates the functionality (the download link is at the end of the post) :

Screenshot of the example application

Screenshot of the example application

The example app was written in Delphi, but the salient parts are almost purely Windows API so they should be easy to translate to other programming languages.

Algorithm Overview

Here’s a general overview of how to use the performance counter API to get the per-core usage numbers.

Initialization

  1. Create a performance query using PdhOpenQuery.
  2. Generate a list of performance counter paths (one for each CPU or core) by feeding a wildcard path to PdhExpandWildCardPath. In this case, this is the path that we need :
    \Processor(*/*#*)\% Processor Time
    You can read more about counter path syntax here.

    Note : Unfortunately, the API makes no distinction between multiple physical processors and multiple cores, so you will get a “…\Processor(X)\…” entry for each core.

  3. Add the generated counter paths to the query using PdhAddCounter and save the returned handle(s) for later.

Collecting & Displaying Data
CPU usage is typically measured over an interval of time, so you will need to collect at least two data samples at different times to get a meaningful result. Depending on your needs, you could either :

  • Collect one sample, sleep() for a second, collect another sample and calculate the result; OR
  • Set up a timer or a loop to calculate & displays the CPU usage periodically. That’s what I’ll do in this tutorial.

Regardless of whether you only need a one-time measurement or periodic updates, the process is very similar API-wise. First, call PdhCollectQueryData to update all counters associated with the query. Then call PdhGetFormattedCounterValue with each counter handle to get the actual data. If you set the output format flag to PDH_FMT_DOUBLE the function will give you the CPU/core load percentage as a floating-point value in the range of [0..100].

If you need periodic updates just call these two functions repeatedly.

Cleanup
When you’re done, call PdhCloseQuery to close counters associated with the query and free up allocated system resources. You don’t need to explicitly remove individual counters.

Source

This is the source code of a simple Delphi 2009 application that displays the load percentage of each processor core in a progress bar. The output is updated every second by using a timer component.

unit CoreUsage;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, JwaWindows, ExtCtrls, ComCtrls, Gauges;
 
type
  TForm1 = class(TForm)
    Timer1: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
 
  TCounter = record
   Path : string;
   Handle : cardinal;
   mLabel : TLabel;
   mGauge : TGauge;
  end;
 
var
  Form1: TForm1;
  Counters : array of TCounter;
  Query : Cardinal = 0;
 
implementation
 
{$R *.dfm}
 
procedure TForm1.FormCreate(Sender: TObject);
var
 dwSize, h : cardinal;
 cnt, i : Integer;
 pPaths:PWideChar;
 pIterator:PwideChar;
 InstanceId, CounterPath : string;
 status : PDH_STATUS;
begin
  //Create a performance query
  if  PdhOpenQuery(nil, 0, Query) = ERROR_SUCCESS then begin
 
    //Get all valid processor/core usage counter paths by expanding
    //a wildcard path. To do this, we must first call PdhExpandWildCardPath
    //with buffer size set to zero to get the actual required buffer size.
    dwSize := 0;
    pPaths := nil;
 
    status := PdhExpandWildCardPath(
      nil,                                  //search the local computer
      '\Processor(*/*#*)\% Processor Time', //we want CPU usage counters for all CPUs/cores
      pPaths,                               //user-allocated buffer; currently null
      dwSize,                               //buffer size
      0) ;                                  //no flags
 
    if status = PDH_MORE_DATA then begin
      dwSize := dwSize + 1; //+1 byte required in XP and below.
      pPaths := GetMemory(dwSize); //Allocate an output buffer.
 
      //Really get the counter paths.
      status := PdhExpandWildCardPath(
        nil,
        '\Processor(*/*#*)\% Processor Time',
        pPaths,
        dwSize,
        0) ;
 
      if status = ERROR_SUCCESS then begin
 
        cnt := 0;
        SetLength(Counters, 32);
 
        //PdhExpandWildCardPath returns the counter list (pPaths) as an array
        //of null-terminated strings where the last item is a zero-length
        //string (i.e. just #0). We'll now iterate over this list with some
        //simple pointer math.
        pIterator := pPaths;
        while (strlen(pIterator)>0) do begin
 
          CounterPath := pIterator;
          pIterator := pIterator + Length(pIterator) + 1;
 
          //Find the counter instance ID (the part in parentheses)
          i := Pos('(', CounterPath);
          InstanceId := Copy(CounterPath, i+1, Pos(')', CounterPath)-i-1);
          //Skip the counter if it indicates the overall CPU usage,
          //we only want the per-core values.
          if InstanceId = '_Total' then continue;
 
          //Add the counter to the query
          status := PdhAddCounter(Query, PWideChar(CounterPath), 0, h);
 
          if status = ERROR_SUCCESS then begin
 
            //Expand the internal counter array if necessary
            if cnt > Length(Counters)-1 then
              SetLength(Counters, Length(Counters)+16);
 
            //Save the counter data to the array
            with Counters[cnt] do begin
              Path := CounterPath;
              Handle := h;
 
              //Create a label for this core/CPU
              mLabel := TLabel.Create(Self);
              mLabel.Parent := Self;
              mLabel.Caption := 'Core '+InstanceId;
              mLabel.Top := (mLabel.Height + 8) * cnt + 10;
              mLabel.Left := 10;
              mLabel.AutoSize := false;
              mLabel.Width := 80;
 
              //Create a "progress bar" to show the core/CPU usage
              mGauge := TGauge.Create(Self);
              mGauge.Parent := Self;
              mGauge.Top := mLabel.Top;
              mGauge.Left := mLabel.Width + 20;
              mGauge.Height := mLabel.Height+2;
              mGauge.Width := Self.ClientWidth - mGauge.Left - 10;
              mGauge.ForeColor := $0031D329;
              mGauge.Anchors := [akRight, akTop];
 
            end;
 
            inc(cnt);
          end;
        end;//while
 
        //Truncate the array to the actual number of discovered cores  
        SetLength(Counters, cnt);
 
        //Collect the first data sample
        PdhCollectQueryData(Query);
 
      end;
    end;
 
  end;
 
  //Did we get any valid counters?
  if Length(Counters) = 0 then begin
 
    //Nope. Show an unhelpful error message...
    MessageBox(Handle, 'Initialization was unsuccessful. The application will now exit.',
      'Error', MB_OK or MB_ICONEXCLAMATION);
 
    //...and quit.
    Application.Terminate;
 
  end else begin
    //Everything went well.
    //Resize the form to make sure all progress bars are visible.
    Self.ClientHeight :=
      Counters[Length(Counters)-1].mLabel.Top +
      Counters[Length(Counters)-1].mLabel.Height +
      Counters[0].mLabel.Top;
  end;
 
end;
 
procedure TForm1.Timer1Timer(Sender: TObject);
var
 i:integer;
 counterType: PDword;
 pValue:_PDH_FMT_COUNTERVALUE;
 status : cardinal;
begin
  if Query = 0 then exit;
 
  //Collect a data sample.
  PdhCollectQueryData(Query);
 
  //Iterate over all counters and update progress bars.
  for i := 0 to Length(Counters) - 1 do begin
    counterType := nil;
 
    //Get the current core/CPU usage
    status := PdhGetFormattedCounterValue(
      Counters[i].Handle, //Counter handle as returned by PdhAddCounter.
      PDH_FMT_DOUBLE,     //Get the counter value as a double-precision float.
      CounterType,        //Counter type; unused.
      pValue);            //Output buffer for the counter value.
 
    //Update the progress bar.
    if status = ERROR_SUCCESS then
      Counters[i].mGauge.Progress := Round(pValue.doubleValue);
  end;
end;
 
procedure TForm1.FormDestroy(Sender: TObject);
begin
 //Close the query when quitting.
 if Query <> 0 then PdhCloseQuery(Query);
end;
 
end.

Download

P.S.
Rumor has it that you may need administrative rights to use the performance counter API.

Share :
  • Reddit
  • del.icio.us
  • Digg
  • StumbleUpon
  • DZone
  • Ping.fm
  • Sphinn
Related posts :

11 Responses to “How To Get Per-Core CPU Usage”

  1. 1
    duriel says:

    Doesn´t work for me @ the first ‘PdhExpandWildCardPath()’ ;(
    Maybe not PWideChar? o.O
    or may its my d2007…

  2. 2
    White Shadow says:

    Your Delphi version is probably the cause – I’d guess the default string type doesn’t support Unicode, right? Try using the ANSI/ASCII version – PdhExpandWildCardPathA – instead.

  3. 3
    duriel says:

    I found the bug :) , i needed the german translation of:
    ‘\Processor(*/*#*)\% Processor Time’
    -> ‘\Prozessor(0)\Prozessorzeit (%)’

    Do you know how i can autodetect this string for each language?

  4. 4
    White Shadow says:

    Unfortunately not. I tried to find some kind of language-independent algorithm for constructing the pattern when I wrote the above application, but (if I recall correctly) nothing useful came up.

  5. 5
    duriel says:

    Ok, but thanks for your efforts and the Information about pdh.dll :)

  6. 6
    Michael says:

    Hi

    Thank you so much for this article! It is seriously the best Delphi article about performance counters on the Internet!

    However, when my delphi (2005) tries to compile JwaWindows, it says that I do not have msxml.dcu:
    [Fatal Error] JwaWindows.pas(213): F1026 File not found: ‘msxml.dcu’

    I have tried searching for it online, but I can’t seem to find it.

    Can you help me? :)

    Best regards,
    Michael

  7. 7
    White Shadow says:

    That file seems to be a part of the core install in D2009 and I don’t know if you can download it anywhere. Maybe you need to enable it separately when installing D2005?

    You could also try using just the necessary sub-units (e.g. JwaPdh in this case) instead of the entire JwaWindows library.

  8. 8
    Michael says:

    Hi,

    Thank you so much! I just needed to add JwaPdh and JwaPdhMsg, and it worked beautifully!

    This topic (performance counters in Delphi) is really really small on the Internet – the only Delphi code example available, that does not use the HKEY_PERFORMANCE_DATA, is yours! And your piece of code absolutely rocks!

    If I want to add another performance counter to the query (for example disk activity or similar), how should I go about adding it?
    Should I make the wildcard search broader or how should I approach adding more counters to the code?

    Best regards,
    Michael

    Your website rocks!

  9. 9
    White Shadow says:

    I’m not all that experienced with this API, so you’d better read the performance counter documentation for things like that ;) Most of the C/C++ examples and docs apply equally well to Delphi (with minor differences in syntax).

  10. 10
    Michael says:

    Okay :) Thank you very much for your time and help :D

    Michael

  11. 11
    Cleber says:

    Duriel, run perfmon.exe to discover the translation used by MS

Leave a Reply