Performance & System Testing 
by Thomas H. Bodenheimer 


Example 1:

(a) 
\\Machine\PerfObject(ParentInstance/ObjectInstance#InstanceIndex)\Counter

(b)
\\MACHIN1\Processor(0)\% Processor Time
\\MACHIN1\Processor(1)\% Processor Time
\\MACHIN1\Processor(_Total)\% Processor Time
\\MACHIN1\PhysicalDisk(0 C:)\% Disk Time


Listing One

#include "stdafx.h"
#include <pdh.h>
#include <pdhmsg.h>

#define INITIALPATHSIZE 2048
class PerfLogger{
    char        logFile[512];
    int         intervalBetweenMeasurements;//in milliseconds
    HQUERY      hQuery;
    HLOG        phLog;
    DWORD       logType;
    BOOL        logging;
public:
    PerfLogger();
    PerfLogger(char* logFileName, int interval);
    int findAndActivatePerfMetrics();
    void startPerfLog();
    void stopPerfLog();

private:
    PDH_STATUS getAllMetricsFor(char *wildCardPath);
};



Listing Two

#include "PerfLogger.h"
PerfLogger::PerfLogger(){
}
PerfLogger::PerfLogger(char* logFileName,int interval)
{
    strcpy(logFile,logFileName);
    /*  The logType defines what type of log will be used for output.
        Since I use Perl to often summarize data, a comma separated value
        file is what I wanted.  CSV log files are one of the options, so 
        I was in business.
    */
    logType = PDH_LOG_TYPE_CSV;
    /*  We open a PDH query in the constructor - we'll add counters to it
        later.  By having all our counters in one query, whenever a snapshot
        of the counters is taken, all the counters are sampled at that same 
        time.
    */
    PdhOpenQuery(0,0, &hQuery);
    
    /*  I needed to sample the counters for some integral number of seconds.
        The actual argument is in milliseconds, so we multiply by 1000.
    */
   intervalBetweenMeasurements=interval * 1000;
    
    /*  A boolean value to help keep track of when logging should be ongoing
        or not.
    */
    logging = FALSE;
}
/*  A member to allow us to find the subset of individual machine counters I'm
    interested in logging.  For me, I wanted the
        % Processor Time for all processors.
        % Disk Time for all disks
        % Disk Read Time for all disks
        % Disk Write Time for all disks
        Available Mbytes of memory during the monitoring period.
        Bytes Received/Sec for all network interfaces
        Bytes Sent/Sec for all network interfaces
Althought it's bad practice, I don't check the return code status when
wildcarding through the counters.  So far, it hasn't caused me any problems.
*/
PerfLogger::findAndActivatePerfMetrics(){
    char wildCardPath[256];
    PDH_STATUS pdhStatus;
// Use the counter path format without specifying the computer.
//    \object(parent/instance#index)\counter

    strcpy(wildCardPath,"\\Processor(*/*#*)\\%% Processor Time");
    pdhStatus=getAllMetricsFor(wildCardPath);

    strcpy(wildCardPath,"\\PhysicalDisk(*/*#*)\\%% Disk Time");
    pdhStatus=getAllMetricsFor(wildCardPath);
    strcpy(wildCardPath,"\\PhysicalDisk(*/*#*)\\%% Disk Read Time");
    pdhStatus=getAllMetricsFor(wildCardPath);
    strcpy(wildCardPath,"\\PhysicalDisk(*/*#*)\\%% Disk Write Time");
    pdhStatus=getAllMetricsFor(wildCardPath);
        
    strcpy(wildCardPath,"\\Memory(*/*#*)\\Available MBytes");
    pdhStatus=getAllMetricsFor(wildCardPath);

    strcpy(wildCardPath,"\\Network Interface(*/*#*)\\Bytes Received/sec");
    pdhStatus=getAllMetricsFor(wildCardPath);
    strcpy(wildCardPath,"\\Network Interface(*/*#*)\\Bytes Sent/sec");
    pdhStatus=getAllMetricsFor(wildCardPath);
    return 0;
}
/*  This member actually takes a counter path with wildcards and uses the
    PdhExpandWildCardPath to get all the matching counter paths. It then 
    adds these expanded paths to the query we created in the constructor.
*/
PDH_STATUS PerfLogger::getAllMetricsFor(char *WildCardPath){
   LPSTR  szCtrPath = NULL;
    char   szWildCardPath[256] = "\000";
    DWORD  dwCtrPathSize = 0;

    HCOUNTER phcounter;
    PDH_STATUS  pdhStatus;
    sprintf(szWildCardPath, WildCardPath);//works
// First try with an initial buffer size.
    szCtrPath = (LPSTR) GlobalAlloc(GPTR, INITIALPATHSIZE);
    dwCtrPathSize = INITIALPATHSIZE;
    pdhStatus = PdhExpandWildCardPath(NULL,szWildCardPath, szCtrPath, 
                    &dwCtrPathSize,NULL);
// Check for a too small buffer.
    if (pdhStatus == PDH_MORE_DATA)
    {
        dwCtrPathSize++;
        GlobalFree(szCtrPath);
        szCtrPath =  (LPSTR) GlobalAlloc(GPTR, dwCtrPathSize);;
        pdhStatus = PdhExpandWildCardPath(NULL,szWildCardPath, szCtrPath, 
                    &dwCtrPathSize,NULL);
    }
// Add the paths to the query
    if (pdhStatus == PDH_CSTATUS_VALID_DATA)
    {
        LPTSTR ptr;
        ptr = szCtrPath;
        while (*ptr)
        {
            pdhStatus = PdhAddCounter(hQuery,ptr,0,&phcounter);
            ptr += strlen(ptr);
            ptr++;
        }
    }
    else printf("PdhExpandCounterPath failed: %d\n", pdhStatus);
    return pdhStatus;
}
/*  Since eventually the PerfLogger class will be used as a 
    service, I just start logging in an infinite loop - I'll
    count on the Windows Service API to allow me to stop
    logging by updating the value of the boolean.
*/
void PerfLogger::startPerfLog()
{
    logging = TRUE;
    PDH_STATUS pdhStatus;
    // Open the log file for write access.
    pdhStatus = PdhOpenLog (logFile, PDH_LOG_WRITE_ACCESS | 
             PDH_LOG_CREATE_ALWAYS, &logType, hQuery, 0, NULL, &phLog);
   // Capture samples and write them to the log.
   while(logging) {
       pdhStatus = PdhUpdateLog (phLog, TEXT("Some Text."));
       Sleep(intervalBetweenMeasurements); // Sleep between samples
   }
// Close the log and the Query
   pdhStatus = PdhCloseLog (phLog, PDH_FLAGS_CLOSE_QUERY);
}
//  Just to stop the logging loop
void PerfLogger::stopPerfLog(){
    logging = FALSE;
}




3


