如何以编程方式获取 TaskManager 进程 CPU 使用率(不是 PerfMon API)
Posted
技术标签:
【中文标题】如何以编程方式获取 TaskManager 进程 CPU 使用率(不是 PerfMon API)【英文标题】:How to get TaskManager process CPU usage programmatically (not PerfMon API) 【发布时间】:2019-12-08 13:12:58 【问题描述】:最近我有机会意识到Performance Counter 的\\Process\\% Processor Time
用于单个进程,实际上与任务管理器中显示的CPU usage
不同。性能计数器通过 PerfMon API 访问,头文件 Pdh.h,而没有直接的方法来获取 TaskManager 的 CPU 使用率。
这是Microsoft TechNet 的一个帖子,讨论差异,很遗憾没有解决我的问题。
当我意识到它们提供的值之间存在巨大差异时,问题出现了。 在我的应用程序中,我需要测量 % CPU 使用率,准确或至少非常接近任务管理器提供的值。但是,PerfMon API 提供的值可能低 20-30%,为什么甚至差异是不同的并且不成比例,是另一个问题。例如,如果根据任务管理器测试进程有效负载 50-55%,PerfMon API 报告 35-40%
这是我的课程,用于使用 PerfMon API 计算总进程 CPU 使用率(已跳过返回代码检查和无关紧要的部分代码)
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;
计数器的使用:
// 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();
我使用 % Processor Time
计数器作为总 CPU 时间,据我所知,它应该等于 UserTime+KernelTime
(根据 PerfMon 命名约定,分别为 % User Time
和 % Privileged Time
)。顺便说一句,出于好奇,我检查了(UserTime+KernelTime == ProcessorTime)
,它不是,但这是另一个问题。
正如我从 TechNet thread 中发现的那样,Microsoft 使用 NtQuerySystemInformation
来实现任务管理器(除了官方文档之外,该来源的可信度当然低于 100%)
然而,调用
NtQuerySystemInformation(SystemProcessInformation, processInfo, processInfoSize, NULL);
填写结构的值
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;
其中也不包含 CPU 利用率的直接值!是的,它包括CreateTime
、UserTime
和KernelTime
,但是,目前还不清楚如何计算给定时刻的 CPU 利用率,就像 TaskManager 所做的那样
编辑总结一下,我需要一个实现,它可以让我的 CPU 使用率等于或至少足够接近 TaskManager 提供的 CPU 使用率,最好解释两个计数器来源(PerfMon 和 TaskManager)的内部结构
【问题讨论】:
您的实际具体问题是什么? 任务管理器肯定是从性能计数器获取信息 大卫赫弗南 不,不是。阅读 TechNet 的链接 问题已解决。实际上,除了SO之外,到处都得到了帮助。仍然记得它是专业人士社区的日子。 RIP ***:( 【参考方案1】:目前还不清楚如何计算给定时间内的 CPU 利用率,就像 TaskManager 所做的那样
Task managed 也不这样做。它每秒显示一次 CPU 利用率,是该秒的平均值。
您应该根据用户/内核时间计算这些值。定期重新查询该数据,例如每秒一次。从新值中减去以前的值以获得在最后一个时间间隔内发生的变化。除以经过的时间,还除以系统中的硬件线程数(SYSTEM_INFO
structure 的dwNumberOfProcessors
字段)
添加更多注释。
要精确测量样本之间经过的时间,您可以使用 QueryPerformanceCounter,或者它是标准库中的 C++ 包装器 std::chrono::high_resolution_clock
。在这两种情况下,不要忘记转换单位,处理时间以 100 纳秒的刻度来衡量。如果你使用 QPC,你可能需要MFllMulDiv,它使用 128 位中间结果缩放 64 位整数,这样你可以将 QPC 的刻度转换为 FILETIME
使用的 100ns 刻度。
如果您只对单个进程的数据感兴趣,而不是 NtQuerySystemInformation 使用GetProcessTimes WinAPI,将更快地获得相同的时间值,因为它只返回单个进程的数据。工作流程是一样的,每秒调用一次 GetProcessTimes,计算用户和系统时间的变化,缩放 delta 以获得你想要的百分比。
附:如果您只想输出单个值,请不要忘记添加用户时间 + 内核时间。
【讨论】:
@YuriS.Cherkasov 确实,您可以以任意频率更新,除非它太小,否则在 Windows 中调度程序时间片约为 20 毫秒。如果您想要平滑图表并愿意花费一些额外的 CPU 时间,您甚至可以计算滚动平均值:每 20 毫秒更新一次,使用最后 1 秒的数据维护一个循环缓冲区(包含 50 个最新读数 + 它们的时间戳),以及在整个缓冲区上使用增量值。以上是关于如何以编程方式获取 TaskManager 进程 CPU 使用率(不是 PerfMon API)的主要内容,如果未能解决你的问题,请参考以下文章