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) :
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
- Create a performance query using PdhOpenQuery.
- 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.
- 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
- Example source code (5 KB)
This is a Delphi 2009 project. To compile it, you will also need to install the Jedi API library that contains PDH API headers for Delphi. - Compiled example application (815 KB)
P.S.
Rumor has it that you may need administrative rights to use the performance counter API.
DoesnΒ΄t work for me @ the first ‘PdhExpandWildCardPath()’ ;(
Maybe not PWideChar? o.O
or may its my d2007…
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.
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?
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.
Ok, but thanks for your efforts and the Information about pdh.dll π
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
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.
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!
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).
Okay π Thank you very much for your time and help π
Michael
Duriel, run perfmon.exe to discover the translation used by MS
I am using Delphi 7 on Windows Vista. I have made minor tweaks to your code to get it to compile under Delphi 7, but the program fails to initialize. It fails the status = PDH_MORE_DATA check. I have also downloaded the compiled example application, but I still get the same ‘Initialization was unsuccessful’ error from your compiled version.
Any ideas?
Thanks,
Welton
Are you using a localized version of Windows? The performance counters can have different names on non-english systems, which would cause the program to fail in this manner.
If that’s not it, it’s probably some Vista thing instead. And I can’t help with that as I don’t have Vista.
OK, thanks. It is the english version of Vista. I’ll have to keep hunting for the performance counter paths. You’d think they’d be listed on MSDN or something, but I can’t seem to find a list anywhere.
Welton
Hi White Shadow,
Thanks for this program, how i can i tweak it to run in delphi 7. I get the incompatibility errors π i manage to do it all, but 2. string to char π Can you release a delphi 7 version with progress bar that are on the form, instead of creating it at the runtime. Say you put 4 progress bars, if the cpu is quadcore, you get all 4 bars working. if the processor is tricore, then 3 bars get their width adjusted and 4th is removed. Same for dual core. If you can please do that above in delphi 7. I can donate some $ to your blog. Thanks
Rob
Btw, im doing a widget for the site that is listed. Thanks a lot!
ok, i made the tweaks to run this on delphi 7 and it works to an extent. It only fetches total cpu usage. Then i used breakpoints to study where the problem was. Apparently it has to be in the InstanceId value. It did not show me ‘_total’ it showed me ‘0’. So i made the if command to check is InstanceId = ‘0’. Now it throws the “Initialization was unsuccessful” Error, the same one Welton got. But im on Xp. So that was not a vista thing. If the InstanceID = ‘_total’ remains, then only total cpu shows, because it skips the individual thing altogether π I am digging this further, i got to get it done finally, for my free widget that im doing for 5 days now. And oh yeah, i only started learning delphi since 5 days, so i got delphi 7 SE :S i should paid for 2009 instead π LOL Anything thanks for the code, the debugger shows me that it scrapped the thread correctly, now i need to write my own munging code :S
Rob, Esh and Sam at optimize-your-pc.org
Here’s a Delphi 7 version of the app. It compiles & works fine on my D7 / WinXP machine.
Note: You will need the Jedi API library. The TGauge component is defined in the Gauges.pas file located in the /Source/Samples/ subdirectory of your D7 install.
Hi White Shadow,
You are amazing, i was struggling for 4 hrs now. Now i see new code and it has a whole new tweaks. It will really help me to learn delphi much better.
Thanks again.
All this effort is to get my site up with some widget marketing. I will donate $12 for now, but will PROMISE to donate atleast $100 after i get this months payment. You really saved 4 days of work, and i should really pay you back!
Rob
function PdhExpandWildCardPath(
szDataSource: PAnsiChar;
szWildCardPath: PAnsiChar;
out mszExpandedPathList: PWideChar;
var pcchPathListLength: DWORD;
dwFlags: DWORD
): PDH_STATUS; stdcall external ‘pdh.dll’ name ‘PdhExpandWildCardPathA’;
Is this correct?
I’m getting error: “The specified object is not found on the system.”.
I’m using:
http://www.ez-delphi.com/sources/PDH.htm