如何编程获取Windows NT的性能数据

来源:互联网 发布:通晓知行 佟瑞 编辑:程序博客网 时间:2024/05/16 10:31


 
摘  要:本文较详细的介绍了如何编程获取以及计算NT的性能数据的方法,以处理器%ProcessorTime为例,给出了关键部分的实现源代码。
关键字:性能监视、对象
 


一、概述
    我们知道,NT管理工具中包括的性能监视器(Performance Monitor )是分析Windows NT 系统性能最重要的一个工具。它是一个多用途的功能强大的监视程序,你可以用它来监视和测量本地以及网络上NT系统,发现系统的瓶颈。通过性能监视器,你可以了解到各种系统对象的表现情况,例如,处理器、内存、高速缓存、线程和进程等。
    但在的实际工程应用中,往往需要由应用程序来自动地监视系统的运行情况,以便作出相应的处理。例如,在某些分布式的网络环境中,服务器如何自动地将需要处理的任务合理地分配到若干台配置相同的工作站上?这就需要服务器了解各台工作站运行的饱和程度。对于处理器密集型的任务,工作站的处理器开销情况(Processor time)就是很重要的参数。
    Windows NT提供了两种编程获取性能数据的方法:
    (1)使用注册表函数访问NT的性能蜂巢(Performance hive)HKEY_PERFORMANCE_DATA获取性能数据。对于采集大量的性能数据以及更全面的控制采集过程的应用来说,这种方法的效率较高。Windows NT的性能监视器就是采用这种方法实现的。
    (2)使用PDH.DLL动态链接库中的API函数获取性能数据。其实,PDH.DLL中提供的API函数是通过实现前一种中方法来获取性能数据的,它只不过是隐藏了前种方法中很多复杂的实现细节,使其编程界面更简单、易用。对于只采集少量的性能数据时,其效率也是很高的。
二、术语
   (1) 性能对象 :使性能数据通过Registry可用的任何对象,包括 CPU、内存、高速缓存、进程等。
   (2) 性能计数器 :性能对象的数据采用计数器的形式表示,代表了各种测量值。例如,内存对象包含了多个计数器,分别代表自由内存量、当前正在使用的内存量等。Windows NT共定义了两种类型的计数器:比率计数器 、原始计数器。
   ·比率计数器:在这类计数器的名称中都包含每秒或百分号的字样,例如 %Processor Time 、Interrupts/sec等。它们的检测都是针对一段时间进行的,至少需要两次的测量值才能计算出结果。
   · 原始计数器:主要显示最新的测量值。例如,TCP对象的计数器Connections Established、Connections Active等。
   (3) 对象实例 :性能对象可能会有多个实例。例如,某个系统有几个CPU或磁盘驱动器。为了区分相同对象的不同成员,把这些对象表示为该对象类型的不同实例。
三、访问注册表性能蜂巢获取性能数据
1、注册表性能蜂巢
    在NT的注册表中有一类特殊的蜂巢,它就是性能蜂巢HKEY_PERFORMANCE_DATA。说它特殊是因为,此蜂巢不同于其它的蜂巢如HKEY_LOCAL_MACHINE、HKEY_CURRENT_USER等,用户使用注册表编辑器无法看到此蜂巢,只能够通过Registry API函数来访问它。虽然HKEY_PERFORMANCE_DATA是一个与性能数据相关的蜂巢,但NT的性能数据实际上不是以硬件或软件参数的方式存储在注册表中的,程序通过使用带HKEY_PERFORMANCE_DATA键的 Win32 Registry API函数使系统收集合适的系统对象管理器中的数据。
2、收集性能数据方法
    如果是收集本地系统的性能数据,可以使用带有参数HKEY_PERFORMANCE_DATA 键的RegQueryValueEx函数,在完成收集工作后一定要使用RegCloseKey关闭。如要访问远程系统的性能数据,则必须使用带有远程系统机器名字和HKEY_PERFORMANCE_DATA 键的RegConnectRegistry函数。该函数返回代表远程系统性能数据的句柄,作为随后调用RegQueryValueEx函数的参数(而不是使用HKEY_PERFORMANCE_DATA 键)。
 


3、性能数据结构
    由RegQueryValueEx 获取的数据基本布局如下图所示:
 


·PERF_DATA_BLOCK : 该结构对系统和性能数据进行描述,包括RegQueryValueEx返回的数据大小、被监视对象的数量、高分辨率性能计数时的频率和数值等。
·PERF_OBJECT_TYPE : 对一种对象类型性能数据进行描述,包括结构的大小和跟在后面的特定数据等。
·PERF_COUNTER_DEFINITION : 定义了计数器的大小、类型和计数器偏移等。
·PERF_COUNTER_BLOCK : 所有原始计数器的标头,定义了后跟的性能计数器的总字节长度。
·PERF_INSTANCE_DEFINITION : 如果对象类型有多个实例,那么在计数器定义列表后就要跟该结构,包含了实例相关的信息。
例如:处理器对象是个多实例对象,它有10个计数器,包括 % Processor Time、%User Time … APC Bypasses/sec。在单处理器的系统中起性能数据的机构如下图所示:
4、程序实现
在此以获取处理器时间(%Processor Time)开销为例,演示如何通过注册表函数从注册表性能蜂巢中获取以及计算性能数据。采用这种方法主要需要完成下面三个步骤:
步骤一:判断是连接本地的注册表还是远程系统的注册表,如果是连接远程系统注册表要用RegConnectRegistry建立与其的连接。
 


        #define IsLocalComputer(a , LocalComputerName) (!lstrcmp(a,LocalComputerName))
         . . .
         CString MachineName; //要获取性能参数的机器名
         . . .
    DWORD dwLength = MAX_COMPUTERNAME_LENGTH ;
    ::GetComputerName(LocalComputerName,&dwLength);
 


    LPCTSTR p = MachineName.GetBuffer(MachineName.GetLength());
    if(IsLocalComputer(LocalComputerName,p))
    { // 本地计算机
        hKey = HKEY_PERFORMANCE_DATA;
    }
    else
    { // 远程计算机
        MachineName = "\\"+MachineName;
 


        LPTSTR p = MachineName.GetBuffer(MachineName.GetLength());
        LONG lRet =::RegConnectRegistry (
                         p,
                         HKEY_PERFORMANCE_DATA,
                         &hKey);
        …
     }
 


步骤二:获取处理器对象的索引值。Windows NT 为每个性能对象及其计数器定义了一个索引值,存储在注册表的HKEY_LOCAL_MACHINESoftwareMicrosoftWindows NTCurrentVersionPerlib09的Counter中。获取处理器对象的索引值,在后面调用RegQueryValueEx获取性能数据时使用这个索引值标明要获取的内容,就可以只得到处理器对象计数器信息数据块。本人在编程时过程中发现,通过HKEY_PERFORMANCE_DATA也可以获取性能对象索引值。
 


   char  szIndex[256] = "";
   XYGetIndexByName( "Processor", szIndex ,hKey);
// 获取Processor对象的索引值,hKey步骤一中的值
 


        //获取性能对象索引值
        void XYGetIndexByName( char *pszCounter, char *szIndex ,HKEY hkey)
        {
            char*  pszBuffer;
            char*  pszTemp;
            char   szObject[256] = "";
            DWORD  dwBytes;
            HKEY   hKeyIndex;
            int    i = 0,j = 0;
 


            // 得到Counter中值的字节长度
            RegQueryValueEx( hkey , "Counters", NULL, NULL, NULL, &dwBytes );
            pszBuffer = (char *) HeapAlloc( GetProcessHeap(),
                                           HEAP_ZERO_MEMORY,
                                           dwBytes );
            // 获取Counter键中的值(索引、对象名表)
            RegQueryValueEx( hkey, "Counters", NULL, NULL, (LPBYTE)pszBuffer, &dwBytes );
            // 查找Processor对象的索引值
            pszTemp = pszBuffer;
            while( i != (int)dwBytes )
            {
                //得到索引值
                while (*(pszTemp+i) != '')
                {
                    szIndex[j] = *(pszTemp+i);
                    i++;
                    j++;
                }
                szIndex[j] = '';
                i++;
 


                //得到对象名
                j = 0;
                while (*(pszTemp+i) != '')
                {
                    szObject[j] = *(pszTemp+i);
                    i++;
                    j++;
                }
                szObject[j] = '';
                i++;
 
               //进行比较
               if( strcmp(szObject, pszCounter) == 0 )
                   break;
               //准备下一个循环
               j = 0;
               if( *(pszTemp+i) == '' )
               i++;
            }
            
            HeapFree( GetProcessHeap(), 0, (LPVOID)pszBuffer );
        }
 


步骤三:根据对象的索引值获取处理器对象的%Processor Time计数器信息,通过两次采样得到的计数器值计算处理器的开销。每次采样得到的计数器原始值并不是我们所需要的结果,要得到最终结果还需要根据计数器的类型对采样得到的原始计数器值进行计算。%Processor Time计数器表示的是处理器执行非空闲线程所花费的时间百分比,而我们得到的计数器原始值是当前处理器执行空闲线程所花费时间的累计值(100纳秒为单位),由这个数据我们可以得到两次采样间隔之间,空闲线程执行的时间百分比。用100%减去空闲线程执行的时间百分比值,也就得到了我们所需要的结果。在PERF_COUNTER_DEFINITION结构中的CounterType字段中给出了每个计数器的类型,在winperf.h文件以及MSDN的帮助有具体说明。
 


    COMPUTE_DATA   PreCounter; //前一次采样值
    COMPUTE_DATA   CurrentCounter; //当前采样值  
    FLOAT PerfData;
 


    // 第一次采样收集数据
    XYGetRawData(&PreCounter,szIndex,hKey);
    Sleep(1000); //采样间隔
    // 进入采样收集循环
    while(bStop)
    {
        XYGetRawData(&CurrentCounter,szIndex,hKey);
        PerfData=XYGetPerfData(PreCounter,CurrentCounter);//计算结果
        …
        PreCounter.llRawData = CurrentCounter.llRawData;
        PreCounter.llData100NS =CurrentCounter.llData100NS;
        Sleep(1000);//采样间隔
    }   
 


        typedef struct _COMPUTE_DATA
        {
             LONGLONG llRawData;     // raw data
             LONGLONG llData100NS;   // time
        }COMPUTE_DATA;
 


        // 获取计数器的原始值函数
        void XYGetRawData(COMPUTE_DATA * pComputeData ,LPTSTR  szIndex ,HKEY hkey)
        {
             PPERF_DATA_BLOCK  PerfDataBlock=NULL;
             PPERF_OBJECT_TYPE  PerfObjectType;
             PPERF_INSTANCE_DEFINITION   PerfInstanceDefinition;
             PPERF_COUNTER_DEFINITION    PerfCounterDefinition;
             PPERF_COUNTER_BLOCK        PerfCounterBlock;
 


             DWORD BufferSize=BUFFERSIZE;
             PerfDataBlock=(PPERF_DATA_BLOCK)malloc(BufferSize);
 


             while( RegQueryValueEx( hkey,
                                     (LPTSTR)szIndex,
                                     NULL,
                                     NULL,
                                     (LPBYTE)PerfDataBlock,      
                                     &BufferSize)==ERROR_MORE_DATA)
             {
                 BufferSize+=INCREMENT;
                 PerfDataBlock=(PPERF_DATA_BLOCK)realloc(PerfDataBlock,BufferSize);
             }
             pComputeData->llData100NS =*(LONGLONG UNALIGNED *)(&PerfDataBlock->PerfTime100nSec);
 


             // 得到性能对象结构指针
             PerfObjectType=(PPERF_OBJECT_TYPE)
                        ((PBYTE)PerfDataBlock+PerfDataBlock->HeaderLength);
             if(PerfObjectType->NumInstances)
                PerfCounterDefinition= (PPERF_COUNTER_DEFINITION)    
                       ((PBYTE)PerfObjectType+PerfObjectType->HeaderLength);
             //得到处理器对象实例CPU0结构指针
             PerfInstanceDefinition=(PPERF_INSTANCE_DEFINITION)
                       ((PBYTE)PerfObjectType+PerfObjectType->DefinitionLength);
             
             PerfCounterBlock = (PPERF_COUNTER_BLOCK)
                       ((PBYTE)PerfInstanceDefinition+PerfInstanceDefinition->ByteLength);
             // 得到%Processor Time计数器原始值
             if (PerfCounterDefinition->CounterSize <= 4)
             {
                 pComputeData->llRawData =* ((DWORD FAR *)
                          ((PBYTE)PerfCounterBlock + PerfCounterDefinition->CounterOffset));
                 pComputeData->llRawData &= (LONGLONG) (0x0ffffffff);
              }
             else
                 pComputeData->llRawData =* ((LONGLONG UNALIGNED*)
                         ((PBYTE)PerfCounterBlock + PerfCounterDefinition->CounterOffset));
             free(PerfDataBlock);
        }
 


      // 计算最终结果函数
      FLOAT  XYGetPerfData(IN COMPUTE_DATA Pre_CoumputeData,
                             IN COMPUTE_DATA Cur_CoumputeData)
      {                                          
          FLOAT   eTimeInterval; //精确的采样间隔时间
          FLOAT   eDifference;  //两采样获取的时间差
          FLOAT   eCount ;
 


          // 两次采样的时间间隔(100纳秒为单位)
          eTimeInterval = (FLOAT)(Cur_CoumputeData.llData100NS - Pre_CoumputeData.llData100NS);
          if (eTimeInterval <= 0.0f)  return (FLOAT) 0.0f;
          // 采样值差(100纳秒为单位)
          eDifference = (FLOAT)(Cur_CoumputeData.llRawData - Pre_CoumputeData.llRawData);
          // 空闲线程执行所占时间百分比
          eCount = eDifference / eTimeInterval ;
          // 非空闲线程执行所占时间百分比
          eCount = (FLOAT) 1.0 - eCount ;
          // 去掉百分号
          eCount *= 100.0f ;
 


          if (eCount < 0.0f) eCount = 0.0f ;
          if (eCount > 100.0f ) eCount = 100.0f;
          return(eCount) ;
     }
四、使用PDH.DLL动态链接库中的API函数获取性能数据
PDH(Performance Data Helper)库的实现也是通过访问注册表获取性能参数,只不过它的API函数对用户屏蔽了很多的实现细节,使得编程的界面更简单、易于操作。在VC++5.0中只包括有pdh.h和pdh.lib,而pdh.dll在Win32 SDK中有,其函数都以Pdh开头。使用PDH库获取性能数据的步骤如下:
(1)    创建一个计数器队列,并添加要采样计数器
(2)    收集性能数据
(3)    计算、显示最终结果
(4)    结束性能数据收集
下面给出用PDH库中API实现上面的方法完成的获取处理器时间%Processor Time的代码。
 


   static HQUERY   hQuery = NULL;
    CString  name; //  机器名 
    HCOUNTER hCounter;
    PDH_STATUS  pdhStatus;
    PDH_COUNTER_PATH_ELEMENTS  pdh_Path;
    DWORD    dwType;
    PDH_FMT_COUNTERVALUE    pValue;
    CHAR  szCounterBuffer[MAX_PATH];
    DWORD  length = MAX_PATH;
 


    name = "\\"+name;
    pdh_Path.szMachineName = name.GetBuffer(name.GetLength());
    pdh_Path.szObjectName = "Processor";
    pdh_Path.szInstanceName = "0";
    pdh_Path.szParentInstance = NULL;
    pdh_Path.dwInstanceIndex = 0;
    pdh_Path.szCounterName = "% Processor Time";
    // 产生完整的路径
    PdhMakeCounterPath ( &pdh_Path, szCounterBuffer, &length, 0);
    // 初始化打开 (1 )
    pdhStatus = PdhOpenQuery (NULL, 0, &hQuery);
    // 添加要采样的计数器
    pdhStatus = PdhAddCounter (hQuery, szCounterBuffer, 0, &hCounter);
    …
    // 第一次采样数据(2)
    pdhStatus = PdhCollectQueryData (hQuery);
    Sleep(1000); // 采样间隔
    // 采样数据循环
    while(bStop)
    {
        // 采样
        pdhStatus = PdhCollectQueryData (hQuery);
        …
        // 计算最终结果(3)
        // PdhGetFormattedCounterValue函数针对最近的采样结果进行计算
        pdhStatus = PdhGetFormattedCounterValue( hCounter,
                                                  PDH_FMT_DOUBLE,
                                                  &dwType,                                     
                                                  &pValue);
        if(pValue.doubleValue<0)  pValue.doubleValue = (FLOAT)0.0f;
        …
        Sleep(1000); // 采样间隔
    }   
    // 结束收集 (4)
    PdhCloseQuery(hQuery);
五、结束语
    本人用上面两种方法,在Windows NT Server 4.0上用VC++5.0实现了一个可以监视本地和网络上NT计算机%Processor Time的程序,如下图所示,两个仪表可以同时监视两台计算机。在具体实现过程中,还需要注意性能数据的采样工作最好在单独的线程中完成,并且整个进程及采样线程要有较的优先级(进程设为HIGH_PRIORITY_CLASS、线程为THREAD_PRIORITY_HIGHEST)。在监视网络上的计算机时,要先建立与该机器的连接,即具有访问该机器共享资源的权利。
 http://klyj.edu-chn.com/simple/index.php?t23083.html
原创粉丝点击