升级至Windows 11后PDH计数器返回错误值的技术求助
Windows 11下PDH计数器CPU使用率异常问题
将系统从Windows 10升级到Windows 11后,原有的C++ CPU监控程序(基于PDH计数器)返回错误值,在三台不同设备上测试结果差异极大:一台返回0%0.2%,一台返回300%400%,还有一台返回7%~15%,无法和Windows 11任务管理器显示的0%~100%利用率匹配。原代码如下:
//Declare variables double cpuUsage; PDH_HQUERY cpuQuery; PDH_HCOUNTER cpuTotal; //Initialize at the start PdhOpenQuery(NULL, NULL, &cpuQuery); PdhAddEnglishCounter(cpuQuery, L"\\Processor(_Total)\\% Processor Time", NULL, &cpuTotal); PdhCollectQueryData(cpuQuery); //Code to be called every second PDH_FMT_COUNTERVALUE counterVal; PdhCollectQueryData(cpuQuery); PdhGetFormattedCounterValue(cpuTotal, PDH_FMT_DOUBLE, NULL, &counterVal); cpuUsage = counterVal.doubleValue;
同时微软官方的PDH示例代码也存在此类不一致问题。
一、Windows 11对PDH计数器的改动
Windows 11调整了\Processor(_Total)\% Processor Time计数器的计算逻辑:该计数器返回的是所有CPU核心的总使用率,而非单核心归一化后的数值。比如4核CPU满负载时,计数器会返回400%,而任务管理器会自动将其除以核心数,显示为100%。另外,原代码本身存在逻辑缺陷(缺少两次采样的间隔等待),在Windows 10中可能因系统调度特性未暴露,升级后被放大。
二、修复现有PDH代码的方法
针对原代码的问题,需要做两处修改:
- 第一次采集数据后,必须等待至少1秒再进行第二次采集(PDH计数器依赖两次采样的差值计算使用率)
- 将计数器返回值除以CPU核心数,归一化到0%~100%范围
修改后的代码:
// 声明变量 double cpuUsage = 0.0; PDH_HQUERY cpuQuery; PDH_HCOUNTER cpuTotal; SYSTEM_INFO sysInfo; // 初始化步骤 GetSystemInfo(&sysInfo); DWORD coreCount = sysInfo.dwNumberOfProcessors; PDH_STATUS queryStatus = PdhOpenQuery(NULL, NULL, &cpuQuery); if (queryStatus == ERROR_SUCCESS) { queryStatus = PdhAddEnglishCounter(cpuQuery, L"\\Processor(_Total)\\% Processor Time", NULL, &cpuTotal); if (queryStatus == ERROR_SUCCESS) { // 第一次采集基准数据 PdhCollectQueryData(cpuQuery); // 等待1秒确保有足够的时间差 Sleep(1000); } } // 每秒调用的监控代码 PDH_FMT_COUNTERVALUE counterVal; if (queryStatus == ERROR_SUCCESS) { PdhCollectQueryData(cpuQuery); PDH_STATUS valueStatus = PdhGetFormattedCounterValue(cpuTotal, PDH_FMT_DOUBLE, NULL, &counterVal); if (valueStatus == ERROR_SUCCESS) { // 归一化到0-100% cpuUsage = counterVal.doubleValue / coreCount; // 修正边界值 cpuUsage = max(0.0, min(100.0, cpuUsage)); } }
三、更简便的CPU使用率监控方案
1. 使用GetSystemTimes API(轻量高效)
无需依赖PDH,直接通过系统API获取CPU时间计算使用率,逻辑更简单,结果和任务管理器完全一致:
#include <windows.h> #include <algorithm> // 用于max/min double GetCPUUsage() { static FILETIME idlePrev, kernelPrev, userPrev; FILETIME idleCurr, kernelCurr, userCurr; if (!GetSystemTimes(&idleCurr, &kernelCurr, &userCurr)) { return 0.0; } // 将FILETIME转换为ULONGLONG进行差值计算 auto FileTimeToULL = [](const FILETIME& ft) { return ((ULONGLONG)ft.dwHighDateTime << 32) | ft.dwLowDateTime; }; ULONGLONG idleDiff = FileTimeToULL(idleCurr) - FileTimeToULL(idlePrev); ULONGLONG totalDiff = (FileTimeToULL(kernelCurr) - FileTimeToULL(kernelPrev)) + (FileTimeToULL(userCurr) - FileTimeToULL(userPrev)); double usage = 0.0; if (totalDiff > 0) { usage = 100.0 * (1.0 - static_cast<double>(idleDiff) / totalDiff); } // 更新上次采集的时间 idlePrev = idleCurr; kernelPrev = kernelCurr; userPrev = userCurr; // 确保数值在合理范围 return std::max(0.0, std::min(100.0, usage)); }
使用说明:第一次调用该函数会返回0.0,之后每隔1秒调用一次,即可得到正确的CPU使用率。
2. WMI性能计数器(适合多指标监控)
如果需要同时监控内存、磁盘等更多系统指标,可以使用WMI的Win32_PerfFormattedData_PerfOS_Processor类获取归一化后的CPU使用率,但实现相对复杂,适合有批量监控需求的场景。
内容的提问来源于stack exchange,提问作者sfaad
相关产品推荐
相关产品推荐

