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

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: windows下使用性能计数器遇到的坑

性能计数器简介


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. 创建查询


  1. 向查询添加计数器


  1. 收集性能数据


  1. 显示性能数据


  1. 关闭查询


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


简单使用


// 要使用性能计数器的基本步骤是:
// 1.打开计数器PdhOpenQuery;
// 2.为计数器句柄分配空间;
// 3.把感兴趣的计数器添加进来PdhAddCounter;
// 4.收集数据PdhCollectQueryData
// 4.得到计数器的数值PdhGetFormattedCounterValue;
// 5.关闭计数器PdhCloseQuery。
// 下面是用代码实现的步骤
// 第一步:
// 在头文件中 
#include <Pdh.h>
// 在实现文件中 
#pragma comment ( lib , "Pdh.lib" )
int ExistSameName(TCHAR *ProcessName) {
  //TODO:判断当前进程列表中有没有同名进程,如果有返回第几个同名进程
  return 0;
}
char *RenameEx(char *tmp,int num) {
  //TODO:在tmp中的字符串末尾添加#num
  return 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#2
            process = RenameEx(tmp, num);
        }
    } else if (strcmpi(ProcessName, "System Idle Process")) {
        //如果是系统空闲进程,可指定计数器名字为Idle
        int 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


  1. 当定义了_MBCS宏时TCHAR = char,_tcslen = strlen,_tcsstr = _ mbsstr,_tcsncmp = _mbsnbcmp


  1. 当定义了_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 code Description

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

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
监控 安全 测试技术
排错-Loadrunner添加Windows Resource计数器提示“找不到网络路径”解决方法
排错-Loadrunner添加Windows Resource计数器提示“找不到网络路径”解决方法
133 0
排错-Loadrunner添加Windows Resource计数器提示“找不到网络路径”解决方法
|
监控 测试技术 Windows
loadrunner 场景设计-添加Windows Resources计数器
loadrunner 场景设计-添加Windows Resources计数器
157 0
|
存储 监控 数据库
利用windows性能计数器进行服务器性能监控
using System; using System.Collections.Generic; using System.Text; using System.Diagnostics; using System.
994 0
|
SQL Windows
WINDOWS 性能计数器
转自 http://www.360doc.com/content/08/0630/17/51673_1384252.shtml http://emzwh.blog.163.com/blog/static/293030882010415111630505/ 与sqlserver相关的性能计数器 Process:Working Set Working Set 计数器表示的是一个进程所占用的内存数量。
876 0
|
1月前
|
网络安全 Windows
Windows server 2012R2系统安装远程桌面服务后无法多用户同时登录是什么原因?
【11月更文挑战第15天】本文介绍了在Windows Server 2012 R2中遇到的多用户无法同时登录远程桌面的问题及其解决方法,包括许可模式限制、组策略配置问题、远程桌面服务配置错误以及网络和防火墙问题四个方面的原因分析及对应的解决方案。