windows下cpu和性能指标监控Top10的实现

简介: windows下cpu和性能指标监控Top10的实现

一直以来只在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使用


Overview - Process Hacker


Process Hacker:一款用于调试和排除软件故障的强大工具 - FreeBuf网络安全行业门户

相关文章
|
29天前
|
监控 Python
【python】实现cpu/内存监控的功能(非常简单)
【python】实现cpu/内存监控的功能(非常简单)
|
4月前
|
Arthas 监控 Java
Arthas 可以用于监控和诊断在 Windows 系统下部署的 Tomcat 服务
Arthas 可以用于监控和诊断在 Windows 系统下部署的 Tomcat 服务
191 2
|
5月前
|
JSON TensorFlow 算法框架/工具
Windows下安装Anaconda5.3.1+Python3.8+TensorFlow2.13.0-CPU版本总结
Windows下安装Anaconda5.3.1+Python3.8+TensorFlow2.13.0-CPU版本总结
118 0
|
1天前
|
存储 弹性计算 监控
监控CPU
【4月更文挑战第30天】
6 0
|
8天前
|
监控 Shell
Shell脚本监控CPU、内存和硬盘利用率
Shell脚本监控CPU、内存和硬盘利用率
|
9月前
|
监控 小程序
利用PowerShell写的一个CPU监控小程序
业务部门需要,所以写的一个CPU监控小程序,有窗口显示,同时会在当前目录生成日志,有需要的自取,复制代码,TXT另存为xx.ps1即可。 仅供学习交流。
125 0
|
5月前
|
传感器 监控 Linux
Linux|奇怪的知识---CPU温度监控
Linux|奇怪的知识---CPU温度监控
102 0
|
5月前
|
监控 安全 API
7.2 Windows驱动开发:内核注册并监控对象回调
在笔者上一篇文章`《内核枚举进程与线程ObCall回调》`简单介绍了如何枚举系统中已经存在的`进程与线程`回调,本章`LyShark`将通过对象回调实现对进程线程的`句柄`监控,在内核中提供了`ObRegisterCallbacks`回调,使用这个内核`回调`函数,可注册一个`对象`回调,不过目前该函数`只能`监控进程与线程句柄操作,通过监控进程或线程句柄,可实现保护指定进程线程不被终止的目的。
31 0
7.2 Windows驱动开发:内核注册并监控对象回调
|
5月前
|
监控 安全 API
7.6 Windows驱动开发:内核监控FileObject文件回调
本篇文章与上一篇文章`《内核注册并监控对象回调》`所使用的方式是一样的都是使用`ObRegisterCallbacks`注册回调事件,只不过上一篇博文中`LyShark`将回调结构体`OB_OPERATION_REGISTRATION`中的`ObjectType`填充为了`PsProcessType`和`PsThreadType`格式从而实现监控进程与线程,本章我们需要将该结构填充为`IoFileObjectType`以此来实现对文件的监控,文件过滤驱动不仅仅可以用来监控文件的打开,还可以用它实现对文件的保护,一旦驱动加载则文件是不可被删除和改动的。
30 1
7.6 Windows驱动开发:内核监控FileObject文件回调
|
5月前
|
监控 安全 API
7.5 Windows驱动开发:监控Register注册表回调
在笔者前一篇文章`《内核枚举Registry注册表回调》`中实现了对注册表的枚举,本章将实现对注册表的监控,不同于32位系统在64位系统中,微软为我们提供了两个针对注册表的专用内核监控函数,通过这两个函数可以在不劫持内核API的前提下实现对注册表增加,删除,创建等事件的有效监控,注册表监视通常会通过`CmRegisterCallback`创建监控事件并传入自己的回调函数,与该创建对应的是`CmUnRegisterCallback`当注册表监控结束后可用于注销回调。
45 0
7.5 Windows驱动开发:监控Register注册表回调

热门文章

最新文章