很多应用需要监控系统资源的使用率等信息,之前零散写过很多。
近日需要读取硬盘的IO使用率,就是任务管理器中的硬盘相关信息。
读写速度很好搞定,但是这个百分比的使用率(活动时间)恶心了,最后搜索到的技术点都指向了Pdh(performance data helper)库。
上手一个技术点确实有点懵逼,花了一天学习才搞明白是什么意思。
这玩意是windows系统底层封装好的一个统计器,并提供了一套API可以让使用者自行调用来统计。
网上写的越来越复杂,越想描述越说不清楚,整的一脸懵逼,其实整理出来流程就5步。
直接说下最简单扼要的流程:
//1.创建监控器
PdhOpenQueryA(NULL, NULL, &m_hQuery);
//2.添加要监控的Counter,这个Counter是一个类似path的字符串,这个后续补充说明怎么查看,返回监控的目标的句柄
PdhAddCounterA(m_hQuery, strCounterPath.c_str(), 0, &hCounter);
//3.每隔一段时间要刷新一下监控器,注意两次调用之间一定要隔开足够长的时间,官网的示例是sleep(1000),1秒
PdhCollectQueryData(m_hQuery);
//4.读取监控的Counter的值
PdhGetFormattedCounterValue(hCounter,nFmt,&CounterType,&mRet);
//5.不用了要关闭监控器
PdhCloseQuery(m_hQuery);
实际原理就是先建立一个监控器,然后往这个监控器里面添加要监控的条目(Counter),允许监控的信息特别详细特别多什么CPU内存硬盘网络等等都有。然后每隔一段时间调用一下刷新监控器(PdhCollectQueryData),然后就再读取数值就得到了两次刷新之间监控的目标的数据了。
这里在强调一遍,两次调用刷新一定要间隔大一些,如果顺序连续两次调用,等于间隔时间无限小,会造成数据极为不准确。可以简单理解为计算下载速度一样,先前后两次读取下载的字节数,然后差值除以间隔时间就是速度,如果间隔时间太小就会造成不精确。
最后是关键信息,监控的Counter怎么查看,怎么得到路径。
win+R,打开perfmon.msc(性能监视器)
在中间空白的地方点击右键,添加计数器,会弹出来“可用计数器”的页面让你选择,这里就是系统支持的所有类型。
比如按照上述的需求要统计硬盘的使用率,那么就是下图所示:(坑爹的玩意硬盘使用率叫DiskTime,开始一直以为是DiskUsage所以没搜索到)
选择好后确定,就能看到底部多了一条监控Counter。
底部Counter的条目点击右键,属性,就能查看到具体的Counter的Path了,调用PdhAddCounterA传入的那个Path就是这个。
当然很多名字起得非常相似,可以自己先添加进去看看,实际确认一下。
最后非常简单的封装了一个C++的类
头文件 PDHCounterHandler.h #pragma once #include <map> #include <string> #include <pdh.h> #include <pdhmsg.h> #pragma comment(lib, "pdh.lib") /* 使用方法 CPDHCounterHandler mPDHCounter; //1.初始化 mPDHCounter.Init(); //2.添加counter mPDHCounter.AddCounter("\\LogicalDisk(F:)\\% Disk Time", "DiskTimeF"); //3.循环读取counter的数值 while (true) { //刷新counter数值,两次调用间隔不能太短 官方写的是1秒 可自由发挥 Sleep(1000); mPDHCounter.CollectData(); //读取数值 printf("F: %d \n", mPDHCounter.GetCounterValue("DiskTimeF", PDH_FMT_LONG).longValue); } 如何查看各种counter的路径 win+R,打开perfmon.msc(性能监视器) 右键添加计数器即可查看 */ class CPDHCounterHandler { private: HQUERY m_hQuery; std::map<std::string, HCOUNTER> m_mapCounter; public: CPDHCounterHandler(); ~CPDHCounterHandler(); bool Init(); bool UnInit(); bool AddCounter(const std::string &strCounterPath, const std::string &strAlias = ""); /* 刷新数据 GetCounterValue前一定要先调用此方法刷新数据 两次调用此函数间隔不要太短 */ bool CollectData(); bool HasCounter(const std::string &strCounterName); PDH_FMT_COUNTERVALUE GetCounterValue(const std::string &strCounterName, int nFmt); //void ShowCounterBrowser(); }; Cpp PDHCounterHandler.cpp #include "stdafx.h" #include "PDHCounterHandler.h" CPDHCounterHandler::CPDHCounterHandler() { m_hQuery = NULL; } CPDHCounterHandler::~CPDHCounterHandler() { UnInit(); } bool CPDHCounterHandler::Init() { PDH_STATUS Status; Status = PdhOpenQueryA(NULL, NULL, &m_hQuery); if (Status != ERROR_SUCCESS) { printf("PdhOpenQuery failed with status 0x%x.\n", Status); return false; } return true; } bool CPDHCounterHandler::UnInit() { if (m_hQuery) { PdhCloseQuery(m_hQuery); m_hQuery = NULL; } return true; } bool CPDHCounterHandler::AddCounter(const std::string &strCounterPath, const std::string &strAlias/* = ""*/) { PDH_STATUS Status; HCOUNTER hCounter; Status = PdhAddCounterA(m_hQuery, strCounterPath.c_str(), 0, &hCounter); if (Status != ERROR_SUCCESS) { printf("PdhAddCounter %s failed with status 0x%x.\n", strCounterPath.c_str(), Status); return false; } // // Most counters require two sample values to display a formatted value. // PDH stores the current sample value and the previously collected // sample value. This call retrieves the first value that will be used // by PdhGetFormattedCounterValue in the first iteration of the loop // Note that this value is lost if the counter does not require two // values to compute a displayable value. // //创建后就刷新一下数据 if (!CollectData()) return false; if (strAlias.empty()) m_mapCounter.insert(std::make_pair(strCounterPath, hCounter)); else m_mapCounter.insert(std::make_pair(strAlias, hCounter)); return true; } //刷新数据 bool CPDHCounterHandler::CollectData() { PDH_STATUS Status; Status = PdhCollectQueryData(m_hQuery); if (Status != ERROR_SUCCESS) { printf("PdhCollectQueryData failed with 0x%x.\n", Status); return false; } return true; } bool CPDHCounterHandler::HasCounter(const std::string &strCounterName) { auto iter = m_mapCounter.find(strCounterName); if (iter == m_mapCounter.end()) return false; return true; } /* fmt:PDH_FMT_DOUBLE PDH_FMT_LONG ... */ PDH_FMT_COUNTERVALUE CPDHCounterHandler::GetCounterValue(const std::string &strCounterName, int nFmt) { PDH_FMT_COUNTERVALUE mRet = { 0 }; auto iter = m_mapCounter.find(strCounterName); if (iter == m_mapCounter.end()) return mRet; PDH_STATUS Status; DWORD CounterType; Status = PdhGetFormattedCounterValue(iter->second, nFmt, &CounterType, &mRet); if (Status != ERROR_SUCCESS) { printf("PdhGetFormattedCounterValue failed with status 0x%x.", Status); } return mRet; } // // void CPDHCounterHandler::ShowCounterBrowser() // { // // // // Initialize the browser dialog window settings. // // // PDH_STATUS Status; // PDH_BROWSE_DLG_CONFIG_A BrowseDlgData; // char CounterPathBuffer[PDH_MAX_COUNTER_PATH]; // // ZeroMemory(&CounterPathBuffer, sizeof(CounterPathBuffer)); // ZeroMemory(&BrowseDlgData, sizeof(PDH_BROWSE_DLG_CONFIG)); // // BrowseDlgData.bIncludeInstanceIndex = FALSE; // BrowseDlgData.bSingleCounterPerAdd = TRUE; // BrowseDlgData.bSingleCounterPerDialog = TRUE; // BrowseDlgData.bLocalCountersOnly = FALSE; // BrowseDlgData.bWildCardInstances = TRUE; // BrowseDlgData.bHideDetailBox = TRUE; // BrowseDlgData.bInitializePath = FALSE; // BrowseDlgData.bDisableMachineSelection = FALSE; // BrowseDlgData.bIncludeCostlyObjects = FALSE; // BrowseDlgData.bShowObjectBrowser = FALSE; // BrowseDlgData.hWndOwner = NULL; // BrowseDlgData.szReturnPathBuffer = CounterPathBuffer; // BrowseDlgData.cchReturnPathLength = PDH_MAX_COUNTER_PATH; // BrowseDlgData.pCallBack = NULL; // BrowseDlgData.dwCallBackArg = 0; // BrowseDlgData.CallBackStatus = ERROR_SUCCESS; // BrowseDlgData.dwDefaultDetailLevel = PERF_DETAIL_WIZARD; // BrowseDlgData.szDialogBoxCaption = "Select a counter to monitor."; // // // // // Display the counter browser window. The dialog is configured // // to return a single selection from the counter list. // // // // Status = PdhBrowseCountersA(&BrowseDlgData); // // if (Status != ERROR_SUCCESS) // { // if (Status == PDH_DIALOG_CANCELLED) // { // printf("Dialog canceled by user.\n"); // } // else // { // printf("PdhBrowseCounters failed with status 0x%x.\n", Status); // } // } // else if (strlen(CounterPathBuffer) == 0) // { // printf("User did not select any counter. \n"); // } // else // { // printf("Counter selected: %ls \n", CounterPathBuffer); // } //}
最后贴一个示例:
CPDHCounterHandler mPDHCounter; mPDHCounter.Init(); mPDHCounter.AddCounter("\\LogicalDisk(F:)\\% Disk Time", "DiskTimeF"); while (true) { //刷新监控器 Sleep(1000); mPDHCounter.CollectData(); printf("F: %d \n", mPDHCounter.GetCounterValue("DiskTimeF", PDH_FMT_LONG).longValue); //只要init之后,可以随时添加Counter进去 if (!mPDHCounter.HasCounter("DiskTimeE")) { mPDHCounter.AddCounter("\\LogicalDisk(E:)\\% Disk Time", "DiskTimeE"); } else { printf("E: %d \n", mPDHCounter.GetCounterValue("DiskTimeE", PDH_FMT_LONG).longValue); if (!mPDHCounter.HasCounter("DiskTimeD")) { mPDHCounter.AddCounter("\\LogicalDisk(D:)\\% Disk Time", "DiskTimeD"); } else { printf("D: %d \n", mPDHCounter.GetCounterValue("DiskTimeD", PDH_FMT_LONG).longValue); } } }