Recently I had a chance to realize that Performance Counter's \\Process\\% Processor Time
for the single process, is actually different counter than CPU usage
, displayed in the Task Manager. Performance Counters are accessed through PerfMon API, header file Pdh.h, whereas there's no direct way to get TaskManager's CPU Usage.
Here’s a thread from Microsoft TechNet, discussing differences, unfortunately does not solve my problem.
Problem emerged when I realized, how huge discrepancies between values they provide. In my application I need to measure % CPU usage, exactly or at least very close to values, provided by the Task Manager. However, values provided by PerfMon API could be 20-30% lower, why even discrepancies are different and not proportional, is an another question. For example, if tested process payload 50-55% according to Task Manager, PerfMon API reports 35-40%
Here’s my class, implemented for calculating total process CPU usage using PerfMon API (return codes check and insignificant parts of the code have been skipped)
pdh_cpu_counter.h
class PdhCPUCounter {
public:
// @param counter_name: "\\Process(*ProcessName*)\\% Processor Time"
explicit PdhCPUCounter(const std::string& counter_name);
// Release handles here
virtual ~PdhCPUCounter();
// Provide actual CPU usage value in range [0.0, 1.0]
double getCPUUtilization() const;
private:
// Low-level query
PDH_FMT_COUNTERVALUE getFormattedCounterValue() const;
// Needed for calculation
size_t m_threads;
// Counter format: "\\Process(*ProcessName*)\\% Processor Time"
std::string m_counter_name;
// CPU counter handle
PDH_HCOUNTER m_counter = INVALID_HANDLE_VALUE;
// Query to PerfMon handle
PDH_HQUERY m_query = INVALID_HANDLE_VALUE;
};
pdh_cpu_counter.cpp
PdhCPUCounter::PdhCPUCounter(const std::string& counter_name) :
m_counter_name(counter_name),
m_threads(std::thread::hardware_concurrency())
{
PdhOpenQuery(nullptr, 0, &m_query);
PdhAddEnglishCounter(m_query, m_counter_name.c_str(), 0, &m_counter);
PdhCollectQueryData(m_query);
}
PDH_FMT_COUNTERVALUE PdhCPUCounter::getFormattedCounterValue() const
{
PdhCollectQueryData(m_query);
PDH_FMT_COUNTERVALUE val;
PdhGetFormattedCounterValue(m_counter, PDH_FMT_DOUBLE | PDH_FMT_NOCAP100, nullptr, &val);
return val;
}
double PdhCPUCounter::getCPUUtilization() const
{
const auto &val = getFormattedCounterValue();
return val.doubleValue / m_threads;
}
Usage of the counter:
// Monitor.exe is a test process here, which gives about 50-55% CPU usage in TaskManager
processor_time = std::make_unique<PdhCPUCounter>("\\Process(Monitor)\\% Processor Time");
// Here we're reported about 0.35 +/- 0.05
double cpu_utilization = processor_time->getCPUUtilization();
I used % Processor Time
counter for the total CPU time, which to my knowledge should be equal UserTime+KernelTime
(% User Time
and % Privileged Time
respectively, according to PerfMon naming convention). By the way, out of curiosity I checked if (UserTime+KernelTime == ProcessorTime)
, and it is not, but it’s another question.
As I found out from the TechNet thread, Microsoft utilizes NtQuerySystemInformation
for the Task Manager implementation (credibility of that source other than official docs less than 100% of course)
However, the call
NtQuerySystemInformation(SystemProcessInformation, processInfo, processInfoSize, NULL);
fills out values of a structure
typedef struct _SYSTEM_PROCESS_INFO
{
ULONG NextEntryOffset;
ULONG NumberOfThreads;
LARGE_INTEGER Reserved[3];
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ImageName;
ULONG BasePriority;
HANDLE ProcessId;
HANDLE InheritedFromProcessId;
}SYSTEM_PROCESS_INFO,*PSYSTEM_PROCESS_INFO;
which also does not contain direct value for CPU utilization! Yes, it includes CreateTime
, UserTime
and KernelTime
, however, it’s unclear how to calculate CPU utilization in a given moment of time, like TaskManager does
EDIT Summarizing, I need an implementation, which would give me CPU Usage equal or at least close enough to one provided by the TaskManager, preferrably with the explanation of internals of the both counters origin (PerfMon and TaskManager)