0
votes

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)

1
What is your actual specific question?Jesper Juhl
Surely task manager gets its information from the performance countersDavid Heffernan
David Heffernan No it is not. Read the link to TechNetuser707779
Problem solved. Actually, got help everywhere but SO. Still remember days when it was community of professionals. RIP StackOverflow:(user707779

1 Answers

1
votes

it’s unclear how to calculate CPU utilization in a given moment of time, like TaskManager does

Task managed doesn’t do that, either. It shows you CPU utilization once per second, averaged over that second.

You should compute these values from user/kernel times. Re-query that data at regular intervals, e.g. once per second. Subtract previous values from new values to get the change which happened over the last interval. Divide the change by time passed, also divide by count of hardware threads in the system (dwNumberOfProcessors field of SYSTEM_INFO structure)

Couple more notes.

To precisely measure time passed between samples, you can use QueryPerformanceCounter, or it’s C++ wrapper from the standard library, std::chrono::high_resolution_clock. In both cases don’t forget to convert units, process times are measured in 100-nanosecond ticks. If you’ll use QPC, you might need MFllMulDiv, it scales 64-bit integers using 128-bit intermediate result, this way you can convert from QPC's ticks to 100ns ticks used by FILETIME.

If you only interested in that data for a single process, instead of NtQuerySystemInformation use GetProcessTimes WinAPI, will get you same time values much faster because it only returns data for a single process. The workflow is the same, call GetProcessTimes once per second, compute changes of user and system time, scale the delta to get the percents you want.

P.S. If you only want a single value on output, don’t forget to add user time + kernel time.