一直以来只在linux下用过好用的top命令,可以显示出排名最前的应用cpu和内存占用情况。windows下虽然有进程管理器,性能监视器perfmon.msc,resmon资源监视器,processhacker神器等众多性能指标监控工具可以用,但就是没法看top10那样的直观。
Process Hacker介绍
Process Hacker:windows下的一款用于调试和排除软件故障的强大工具 。它可以帮助研究人员检测和解决软件或进程在特定操作系统环境下遇到的问题。除此之外,它还可以检测恶意进程,并告知我们这些恶意进程想要实现的功能。
Process Hacker, A free, powerful, multi-purpose tool that helps you monitor system resources, debug software and detect malware.
Top10的实现
可以用来做一款监控报警服务方便定制,对系统和应用的运行情况进行监控,记录异常报警日志方便运维和问题分析。
先来看张效果图:
实现原理
使用windows计数器和windows性能监视器pdh库。pdh库可以很简单的获取windows下CPU占用率、网络占用率、内存占用率、网络上下行速度等。
PDH库的使用
#ifndef UNICODE #define UNICODE #endif // !UNICODE #include <pdh.h> #include <pdhmsg.h> #include <tchar.h> #include <windows.h> //! 链接使用pdh库 #pragma comment(lib,"pdh")
简单的五步:
1、打开pdh查询 PdhOpenQuery
HQUERY query; PDH_STATUS status = PdhOpenQuery(NULL,NULL,&query); if( status !=ERROR_SUCCESS )
2、添加计数器
HCOUNTER cpuCounter; status = PdhAddCounter(query,TEXT("\\Processor Information(_Total)\\% Processor Time"),NULL,&cpuCounter);
3、收集性能数据
PdhCollectQueryData(query); Sleep(1000); // 此处时间可更换,但必须有延时至少1秒,否则结果不准确 PdhCollectQueryData(query);
4、获取统计的结果值
PDH_FMT_COUNTERVALUE pdhValue; DWORD dwValue; PDH_STATUS status =PdhGetFormattedCounterValue(cpuCounter,PDH_FMT_DOUBLE,&dwValue,&pdhValue); if(status == ERROR_SUCCESS)
5、移除计数器
PdhRemoveCounter(cpuCounter);
6、关闭pdh查询
PdhCloseQuery(query);
Top10的具体实现思路
通过编写程序来访问Windows性能计数器。获取所有进程的cpu占用率和内存使用信息,然后存入map排序。
Windows中的注册表是访问性能计数器的一种机制。性能信息并不实际存在于注册表中,在注册表编辑器RegEdit.exe中是无法查看的,但可以通过注册表函数来访问,利用注册表键来获得从性能数据提供者那里提供的数据。打开名为HKEY_PERFORMANCE_DATA的特殊键,利用RegQueryValueEx函数查询键下面的值,就可以直接访问注册表性能计数器信息。
注册表中计时器的位置是在:
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib\\009"
获取后存储进map结构,按cpu和内存占用大小排序。需要清楚数据在注册表中的存储格式。
HKEY_PERFORMANCE_DATA数据组织
性能数据的头部是一个PERF_DATA_BLOCK结构(如图1所示),它描述系统和性能数据总体信息,可从Global键值处查询得到该结构数据。PERF_DATA_BLOCK之后,定义了系统中的全部性能对象类型(PERF_OBJECT_TYPE),其中每个对象类型头部中描述了下一个性能对象类型的偏移量Offset。
图一
性能对象有两种:一种是单实例对象,另一种是多实例对象。
图2和图3分别描述了这两种性能对象的数据组织方式。每个对象数据块包含了一个PERF_OBJECT_TYPE结构,描述对象的性能数据。紧随其后是PERF_COUNTER_DEFINITION结构列表,描述了性能对象的全部计数器定义。对于单实例对象,计数器定义列表后是一个PERF_COUNTER_BLOCK结构,计数器数据紧随其后。
每个PERF_COUNTER_DEFINITION结构中定义了计数器数据相对于PERF_COUNTER_BLOCK的偏移量,因此可以非常方便地获得全部计数器的值。对支持多实例性能对象来说,PERF_COUNTER_DEFINITION结构列表之后是一组实例信息数据块,每个表示代表一个对象实例。每个实例信息数据块由一个PERF_INSTANCE_DEFINITION结构体、实例名和一个PERF_COUNTER_BLOCK结构体组成。后面是计数器值数据,与单实例对象相同。
图二
图三
以下为相关代码实现:
//根据[Process]的ID,获得与进程有关的数据块 TCHAR szTempProcessId[256] = {0}; wsprintf(szTempProcessId, L"%d", nProcess009Id); DWORD dwSize = 204800; PPERF_DATA_BLOCK pPerfDataBlock = NULL; pPerfDataBlock = (PPERF_DATA_BLOCK)malloc(dwSize); if (pPerfDataBlock == NULL){ RegCloseKey(hPerfKey); sprintf((char*)strErrorInfo.c_str(), "分配内存失败"); printf("malloc Memory to pPerfDataBlock Error, Id: %d\n", GetLastError()); return FALSE; } while (true){ LResult = RegQueryValueEx(hPerfKey, (szTempProcessId), NULL, NULL, (LPBYTE)pPerfDataBlock, &dwSize); if (LResult == ERROR_SUCCESS){ break; } else if (LResult == ERROR_MORE_DATA){ dwSize += 102400; pPerfDataBlock = (PPERF_DATA_BLOCK)realloc((void*)pPerfDataBlock, dwSize); if (pPerfDataBlock == NULL){ RegCloseKey(hPerfKey); sprintf((char*)strErrorInfo.c_str(), "分配内存失败"); printf("realloc Memory Error, Id: %d\n", GetLastError()); return FALSE; } }else{ RegCloseKey(hPerfKey); free((void*)pPerfDataBlock); sprintf((char*)strErrorInfo.c_str(), "未知错误"); printf("RegQueryValueEx [Process] Error, Id: %d\n", GetLastError()); return FALSE; } }
// 获取全部进程的cpuMem信息,按内存占用从大到小排序 BOOL CCpuTop::GetCpuMemMap() { LONGLONG tm = tm2 - tm1; int i = 1; for (itTempMap = mapCpuTemp.begin(); itTempMap != mapCpuTemp.end(); itTempMap++) { if ((itTempMap->second.strProcessName != "Idle") && (itTempMap->second.strProcessName != "_Total")) { // cpu使用率 double lfUsage = itTempMap->second.FirstData * 100.0 / tm; // 内存占用,单位字节 unsigned __int64 memSize = itTempMap->second.SecondData; int nCpuNumber = GetCpuNumber(); // 除以cpu核数才是对的 lfUsage = lfUsage / nCpuNumber; CCpuInfo aCpuInfo(itTempMap->first, itTempMap->second.strProcessName, lfUsage, memSize); aInfoNode = std::make_pair(aCpuInfo, ""); // 插入数据,自动按规则排序 InfoCheckPair = mapCpuMem.insert(aInfoNode); if (!InfoCheckPair.second) { sprintf((char*)strErrorInfo.c_str(), "获取数据失败"); printf("第%d次: Insert a node to mapCpuInfo Error\n", i); return FALSE; } i++; } } return TRUE; }
class CCpuTop { public: CCpuTop(void); ~CCpuTop(void); public: std::map<int, CCpuTemp> mapCpuTemp; std::map<int, CCpuTemp>::iterator itTempMap; std::pair<int, CCpuTemp> aTempNode; std::pair<std::map<int, CCpuTemp>::iterator, bool> TempCheckPair; // 带排序规则的map cpu占用率排序 std::map<CCpuInfo, std::string, CCpuCompare> mapCpuInfo; // 带排序规则的map 内存占用大小排序 std::map<CCpuInfo, std::string, CCpuMemCompare> mapCpuMem; std::pair<CCpuInfo, std::string> aInfoNode; std::pair<std::map<CCpuInfo, std::string, CCpuCompare>::iterator, bool> InfoCheckPair; //...... }
// cpu信息 进程id,进程名,使用率,内存占用 class CCpuInfo { public: CCpuInfo(void); CCpuInfo(int _nProcessId, std::string _strProcessName, double _fValue, unsigned __int64 _memSize); ~CCpuInfo(void); public: int nProcessId; // 进程名字 std::string strProcessName; // cpu占用率 double fValue; // 内存使用 unsigned __int64 memSize; }; // 排序规则定义类 cpu占用率大小比较 class CCpuCompare { public: CCpuCompare(void) = default; ~CCpuCompare(void) = default; public: bool operator()(const CCpuInfo& aCpuInfo, const CCpuInfo& bCpuInfo) const; }; // 排序规则定义类 内存占用大小比较 class CCpuMemCompare { public: CCpuMemCompare(void) = default; ~CCpuMemCompare(void) = default; public: bool operator()(const CCpuInfo& aCpuInfo, const CCpuInfo& bCpuInfo) const; };
参考文献
[1] 深入解析Windows操作系统(第四版), Mark E.Russinovich & David A.Solomon.
[2] Using Performance Counters, MSDN 开发人员工具、技术文档和代码示例 | Microsoft Docs
[3] 纵谈进程枚举, 陆其明. http://jasonye.bokee.com/432054.html.
[4] 如何编程获取Windows NT的性能数据, 周京生. http://www.comprg.com.cn/detail.asp?hw_id=2643.
引用
Process Hacker 简单介绍_一杯咖啡的时间的博客-CSDN博客_processhacker使用