BlackFeather'S Blog 我的技术小博 -- C/C++,Python,Golang

性能监控之PDH系列方法C++封装


很多应用需要监控系统资源的使用率等信息,之前零散写过很多。

近日需要读取硬盘的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);
			}
		}
	}


2021年6月25日 | 发布:blackfeather | 分类:C/C++代码 | 评论:0

发表留言: