热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

windows下使用性能计数器遇到的坑

性能计数器简介MicrosoftWindwosNT2000提供了一个强大的API集来访问系统事件和性能数据的众多计数器。我们既可以实时地得到计数器的值,也可以从一个

性能计数器简介

Microsoft Windwos NT/2000 提供了一个强大的API集来访问系统事件和性能数据的众多计数器。我们既可以实时地得到计数器的值,也可以从一个日志文件中读取计数器数据。功能可为强大,而且使用简单。

可以用来做什么

可以用来监控记录当前 CPU 的使用率、memory 使用率、CPU 占有率 、memory 占有率,获取系统和进程的物理内存,虚拟内存,线程数,句柄数,CPU,网络利用率,磁盘读写速率等。总之,很强大。windows下可以通过perfmon.msc调起系统的性能监视器,通过它来查看相关的监控信息。


PDH 函数获取计数器数据

使用 PDH 函数收集性能数据。 PDH 函数比 注册表函数 更易于使用,可用于访问 V1 和 V2 提供程序的计数器数据。 PDH 提供用于收集当前性能数据的 API、将性能数据保存到日志文件以及从日志文件中读取数据。

PDH 是一个高级 API,可简化收集性能计数器数据。 它有助于查询分析、元数据缓存、在示例之间匹配实例、从原始值计算格式化值、从日志文件读取数据以及将数据保存到日志文件。 PDH 在从 V1 提供程序收集数据时自动使用注册表函数,在从 V2 提供程序收集数据时,它使用 V2 使用者函数。

若要使用 PDH 函数收集性能数据,请执行以下步骤。

  1. 创建查询
  2. 向查询添加计数器
  3. 收集性能数据
  4. 显示性能数据
  5. 关闭查询

可以从实时源或日志文件收集性能数据。 有关如何将性能数据写入日志文件的详细信息,可参阅 使用日志文件。


简单使用

// 要使用性能计数器的基本步骤是:
// 1.打开计数器PdhOpenQuery;
// 2.为计数器句柄分配空间;
// 3.把感兴趣的计数器添加进来PdhAddCounter;
// 4.收集数据PdhCollectQueryData
// 4.得到计数器的数值PdhGetFormattedCounterValue;
// 5.关闭计数器PdhCloseQuery。// 下面是用代码实现的步骤
// 第一步:
// 在头文件中
#include
// 在实现文件中
#pragma comment ( lib , "Pdh.lib" )int ExistSameName(TCHAR *ProcessName) {//TODO:判断当前进程列表中有没有同名进程,如果有返回第几个同名进程return 0;
}
char *RenameEx(char *tmp,int num) {//TODO:在tmp中的字符串末尾添加#numreturn tmp;
}
int GetCPUUsage(TCHAR *ProcessName) {HQUERY hQuery;HCOUNTER *pCounterHandle;PDH_STATUS pdhStatus;PDH_FMT_COUNTERVALUE fmtValue;DWORD ctrType;CHAR szPathBuffer[MAX_PATH];int RetVal = 0;pdhStatus = PdhOpenQuery(NULL, 0, &hQuery);//打开查询对象pCounterHandle = (HCOUNTER *)GlobalAlloc(GPTR, sizeof(HCOUNTER));//合成查询字符串char *process = NULL; //处理之后的进程计数器名if (strstr(ProcessName,".exe") || strstr(ProcessName,".EXE")) {//如果是以exe结尾,去掉后缀名int len = strlen(ProcessName) - 4;char * tmp =new char [len + 6];//为后面#num留出空间memcpy(tmp , ProcessName, len);tmp[len] = 0;process = tmp;int num = ExistSameName(ProcessName); //需要计数if (num) {//重名//如果有同名进程,当前计数器名字改为 "ProcessName#num"//如 MSDEV.EXE,则有 MSDEV,MSDEV#1,MSDEV#2process = RenameEx(tmp, num);}} else if (strcmpi(ProcessName, "System Idle Process")) {//如果是系统空闲进程,可指定计数器名字为Idleint len = strlen("Idle");char * tmp = new char[len + 1];strncpy(tmp, "Idle", len);tmp[len] = 0;process = tmp;} else {//名为System的进程process = ProcessName;}sprintf(szPathBuffer,"//Process(%s)//%% Processor Time", process);pdhStatus = PdhAddCounter(hQuery, szPathBuffer , 0 , pCounterHandle);pdhStatus = PdhCollectQueryData(hQuery);pdhStatus = PdhGetFormattedCounterValue (//获取计数器当前值*pCounterHandle,//计数器句柄PDH_FMT_LONG | PDH_FMT_NOSCALE, //format格式&ctrType, //控制类型&fmtValue); //返回值if (pdhStatus == ERROR_SUCCESS) {//fmtValue.doubleValue为所要的结果RetVal = fmtValue.longValue;// [type: double,long,string,large]} else {RetVal = 0;}pdhStatus = PdhCloseQuery(hQuery);//关闭查询句柄return RetVal;
}
int main() {setlocale(LC_ALL,"chs");pdhdump();return 0;
}

遇到的坑

坑一:字符编码问题

PdhAddCounter error,code=c0000bc0 //
PdhCollectQueryData error,code=800007d5
PdhGetFormattedCounterValue error,code=c0000bbc

第一步的PdhAddCounter就失败了,返回c0000bc0。

错误嘛含义参见:Performance Data Helper Error Codes - Win32 apps | Microsoft Docs

常见错误:

0xC0000BC0 (PDH_CSTATUS_BAD_COUNTERNAME)Unable to parse the counter path. Check the format and syntax of the specified path.

0xC0000BBC (PDH_INVALID_HANDLE)The handle is not a valid PDH object.

0xC0000BB9 (PDH_CSTATUS_NO_COUNTER)The specified counter could not be found.

0xC0000BC6 (PDH_INVALID_DATA)The data is not valid.

这里面最大的坑就是字符集问题,需要使用宽字符集wchar_t。或者字符串前面加个大写字母L。

L使用说明

TCHAR *szError = L"Error";
字符串(literal string)前面的大写字母L,用来告诉编译器该字符串应该作为Unicode来编译。它用来将ASNI转换为Unicode,Unicode字符串中每个字符占16位(两个字节),而在ASNI中每个字符占用一个字节。
例如:

strlen(“asd”) = 3;
strlen(L”asd”) = 6;

_TEXT、TEXT使用说明
其实,_T、_TEXT、TEXT 三者效果相同

tchar.h是运行时的头文件,_T、_TEXT 根据_UNICODE来确定宏
winnt.h是Win的头文件根据,TEXT 根据UNICODE 来确定宏

如果需要同时使用这3个宏,则需同时定义 UNICODE 和 _UNICODE。VS2010 设置:项目–属性–配置属性–常规–字符集–使用Unicode字符集。

如下写法才是正确的:

const wchar_t str1[] = L"\\Process(QQ)\\% Processor Time";

注意这里面的写法,不能有一点儿错。比如%和后面的 Processor Time之前有个空格必不能少。

关于TCHAR

因为C++支持两种字符串,即常规的ANSI编码(使用""包裹)和Unicode编码(使用L""包裹),这样对应的就有了两套字符串处理函数,比如:strlen和wcslen,分别用于处理两种字符串。

微软将这两套字符集及其操作进行了统一,通过条件编译(通过_UNICODE和UNICODE宏)控制实际使用的字符集,这样就有了_T("")这样的字符串,对应的就有了_tcslen这样的函数

为了存储这样的通用字符,就有了TCHAR:

当没有定义_UNICODE宏时,TCHAR = char,_tcslen =strlen

当定义了_UNICODE宏时,TCHAR = wchar_t , _tcslen = wcslen

当我们定义了UNICODE宏,就相当于告诉了编译器:我准备采用UNICODE版本。这个时候,TCHAR就会摇身一变,变成了wchar_t。而未定义UNICODE宏时,TCHAR摇身一变,变成了unsignedchar。这样就可以很好的切换宽窄字符集。

tchar可用于双字节字符串,使程序可以用于中日韩等国 语言文字处理、显示。使编程方法简化。

TCHAR 就是当你的字符设置为什么就是什么。
例如:程序编译为 ANSI, TCHAR 就是相当于 CHAR
当程序编译为 UNICODE, TCHAR 就相当于 WCHAR

char :单字节变量类型,最多表示256个字符,wchar_t :宽字节变量类型,用于表示Unicode字符。

如果在程序中既包括ANSI又包括Unicode编码,需要包括头文件tchar.h。TCHAR是定义在该头文件中的宏,它视你是否定义了_UNICODE宏而定义成: 
定义了_UNICODE:    typedef wchar_t TCHAR ; 
没有定义_UNICODE: typedef char TCHAR ;

#ifdef UNICODE 
typedef char TCHAR; 
#else 
typede wchar_t TCHAR; 
#endif 
_T( )也是定义在该头文件中的宏,视是否定义了_UNICODE宏而定义成: 
定义了_UNICODE:    #define _T(x) L##x 
没有定义_UNICODE: #define _T(x) x 
注意:如果在程序中使用了TCHAR,那么就不应该使用ANSI的strXXX函数或者Unicode的wcsXXX函数了,而必须使用tchar.h中定义的_tcsXXX函数。

常规的ANSI编码("字符串")和Unicode编码(L"字符串"),相应的就有两套字符串处理函数,比如:strlen和wcslen,分别用于处理两种字符串。
微软将这两套字符集及其操作进行了统一,通过条件编译(_UNICODE&_MBCS)来控制实际使用的字符集。

  1. 当没有定义_UNICODE & _MBCS宏时TCHAR = char,_tcslen = strlen,_tcsstr = strstr,_tcsncmp = strncmp
  2. 当定义了_MBCS宏时TCHAR = char,_tcslen = strlen,_tcsstr = _ mbsstr,_tcsncmp = _mbsnbcmp
  3. 当定义了_UNICODE宏时,TCHAR = wchar_t , _tcslen = wcslen,_tcsstr = wcsstr,_tcsncmp = wcsncmp

用_ttoi 替换掉atoi

具体查看tchar.h头文件定义:

/* String functions */#define _tcscat wcscat
#define _tcscat_s wcscat_s
#define _tcschr wcschr
#define _tcscpy wcscpy
#define _tcscpy_s wcscpy_s
#define _tcscspn wcscspn
#define _tcslen wcslen
#define _tcsnlen wcsnlen
#define _tcsncat wcsncat
#define _tcsncat_s wcsncat_s
#define _tcsncat_l _wcsncat_l
#define _tcsncat_s_l _wcsncat_s_l
#define _tcsncpy wcsncpy
#define _tcsncpy_s wcsncpy_s
#define _tcsncpy_l _wcsncpy_l
#define _tcsncpy_s_l _wcsncpy_s_l
#define _tcspbrk wcspbrk
#define _tcsrchr wcsrchr
#define _tcsspn wcsspn
#define _tcsstr wcsstr

坑二:关于PdhGetFormattedCounterValue的使用

这个使用真坑,无论怎么试,都返回个错误码:0xC0000BC6 (PDH_INVALID_DATA)。

无奈先使用PdhGetRawCounterValue这个吧, 等获取到实际值再研究下具体类型。

其实这可能的原因是,有些计数器需要获取两次才可以哦。调用一次query是不行的。

比如:

L"\\Processor Information(_Total)\\% Processor Time"

这个,如果只调用一次 PdhCollectQueryData(hQuery)是不行的。会返回失败。当然,这也不能怪别谁,因为人家英文文档上写的有。只不过有点儿隐蔽,关键时候还得看官方文档啊,网上所有的示例都没告诉你这个。这算是个坑吧。

PdhGetFormattedCounterValue function (pdh.h) - Win32 apps | Microsoft Docs

If the function succeeds, it returns ERROR_SUCCESS.

If the function fails, the return value is a system error code or a PDH error code. The following are possible values.

Return codeDescription

PDH_INVALID_ARGUMENT

A parameter is not valid or is incorrectly formatted.

PDH_INVALID_DATA

The specified counter does not contain valid data or a successful status code.

PDH_INVALID_HANDLE

The counter handle is not valid.

Some counters, such as rate counters, require two counter values in order to compute a displayable value. In this case you must call PdhCollectQueryData twice before calling PdhGetFormattedCounterValue. For more information, see Collecting Performance Data

以下的调用才是正确的:

pdhStatus = PdhAddCounter(hQuery, (LPCWSTR)L"\\Processor Information(_Total)\\% Processor Time", NULL, pCounterHandle);if (pdhStatus != ERROR_SUCCESS) {printf("PdhAddCounter error,code=%x\n", pdhStatus);}pdhStatus = PdhCollectQueryData(hQuery);if (pdhStatus != ERROR_SUCCESS) {printf("PdhCollectQueryData error,code=%x\n", pdhStatus);}Sleep(500);pdhStatus = PdhCollectQueryData(hQuery);if (pdhStatus != ERROR_SUCCESS) {printf("PdhCollectQueryData error,code=%x\n", pdhStatus);}pdhStatus = PdhGetFormattedCounterValue(//获取计数器当前值*pCounterHandle,//计数器句柄PDH_FMT_DOUBLE, //format格式&ctrType, //控制类型&fmtValue); //返回值if (pdhStatus == ERROR_SUCCESS) {//fmtValue.doubleValue为所要的结果printf("SUCCESS\n");RetVal = fmtValue.doubleValue;// [type: double,long,string,large]}else {printf("PdhGetFormattedCounterValue error,code=%x\n", pdhStatus);RetVal = 0;}pdhStatus = PdhCloseQuery(hQuery);//关闭查询句柄return RetVal;

引用

Windows性能计数器相关基础(一)_六月心悸的博客-CSDN博客

求高手指点,C或C++获取系统和进程的磁盘读写速率,网络速率等信息。-CSDN论坛

使用 PDH 函数使用计数器数据 - Win32 apps | Microsoft Docs

Windows 下使用PDH 获取CPU 使用率_风为裳のCode的博客-CSDN博客

vc下使用windows的性能计数器简介 - 至尊王者 - 博客园

c语言中宽字符,C语言:宽字符集操作函数(示例代码)_weixin_39783857的博客-CSDN博客

windows编程中L,_T() ,TEXT和_TEXT的使用及其区别_碧海凌云的博客-CSDN博客_text头文件

PdhAddCounter的使用问题-CSDN论坛

TCHAR用法_bestone0213的博客-CSDN博客_tchar

PDH性能测试之五--待续_我有梦之翼的博客-CSDN博客

Windows下使用PDH获取性能计数器(CPU、内存、网络流量等)_alwaysrun的博客-CSDN博客

关于PDH函数 (性能计数器设计)

使用PDH性能计数器获取CPU使用率网卡信息等_zhizhuode的博客-CSDN博客

PdhGetFormattedCounterValue function (pdh.h) - Win32 apps | Microsoft Docs


推荐阅读
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文介绍了一种划分和计数油田地块的方法。根据给定的条件,通过遍历和DFS算法,将符合条件的地块标记为不符合条件的地块,并进行计数。同时,还介绍了如何判断点是否在给定范围内的方法。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • 本文介绍了C函数ispunct()的用法及示例代码。ispunct()函数用于检查传递的字符是否是标点符号,如果是标点符号则返回非零值,否则返回零。示例代码演示了如何使用ispunct()函数来判断字符是否为标点符号。 ... [详细]
  • 本文介绍了PE文件结构中的导出表的解析方法,包括获取区段头表、遍历查找所在的区段等步骤。通过该方法可以准确地解析PE文件中的导出表信息。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 本文介绍了指针的概念以及在函数调用时使用指针作为参数的情况。指针存放的是变量的地址,通过指针可以修改指针所指的变量的值。然而,如果想要修改指针的指向,就需要使用指针的引用。文章还通过一个简单的示例代码解释了指针的引用的使用方法,并思考了在修改指针的指向后,取指针的输出结果。 ... [详细]
author-avatar
手机用户2502886335
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有