如何以编程方式获取 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 利用率的直接值!是的,它包括CreateTimeUserTimeKernelTime,但是,目前还不清楚如何计算给定时刻的 CPU 利用率,就像 TaskManager 所做的那样

编辑总结一下,我需要一个实现,它可以让我的 CPU 使用率等于或至少足够接近 TaskManager 提供的 CPU 使用率,最好解释两个计数器来源(PerfMon 和 TaskManager)的内部结构

【问题讨论】:

您的实际具体问题是什么? 任务管理器肯定是从性能计数器获取信息 大卫赫弗南 不,不是。阅读 TechNet 的链接 问题已解决。实际上,除了SO之外,到处都得到了帮助。仍然记得它是专业人士社区的日子。 RIP ***:( 【参考方案1】:

目前还不清楚如何计算给定时间内的 CPU 利用率,就像 TaskManager 所做的那样

Task managed 也不这样做。它每秒显示一次 CPU 利用率,是该秒的平均值。

您应该根据用户/内核时间计算这些值。定期重新查询该数据,例如每秒一次。从新值中减去以前的值以获得在最后一个时间间隔内发生的变化。除以经过的时间,还除以系统中的硬件线程数(SYSTEM_INFOstructure 的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)的主要内容,如果未能解决你的问题,请参考以下文章

如何以编程方式获取正在侦听某个端口的进程的 pid?

如何在同一台机器上以编程方式获取通过 AF_INET 套接字连接到我的代理的进程的 PID?

应用程序的单个实例

从 Windows 8 TaskManager 隐藏进程

以编程方式获取另一个进程的父 pid?

在 Windows 上以编程方式获取每个进程的磁盘 io 统计信息?