很多应用需要监控系统资源的使用率等信息,之前零散写过很多。
近日需要读取硬盘的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);
}
}
}