Using the Performance Data Helper Library

Allen Denver
Microsoft Developer Support

March 11, 1997

In between “therapeutic” games of Blood, Quake and Red Alert, Allen has taken some time to write about the Windows NT PDH Library. Now there’s a simpler method to write a program like PerfMon without needing personal time for such “therapy.”

Click to open or copy the files in the PDHTest sample application for this technical article.

Abstract

The Performance Data Helper (PDH) library is an enhancement to the performance-monitoring capabilities of Windows NT®. Instead of wading through lots of performance-data structures in your own application, you can rely on the functions available in the PDH Library to manipulate performance information with ease.

If you are a programmer who has wrestled with the performance data structures, you probably are still scratching your head, wondering if you got everything right. The PDH Library can help you to focus on the reasons you want the performance information instead of writing lots of code to wade through the complicated performance data structures to get the information you seek.

If you’ve never embarked on the long path involved in writing a program that uses the performance data structures, then rest assured that your life will be much less complicated if you use the PDH Library. You should be able to write a simple performance-monitoring program in just a few hours after reading this article.

This article will discuss the PDH Library in general, what APIs are available, and how the APIs work. As much sample code as possible is provided, where appropriate. Much of this information is in the Win32® Software Development Kit (SDK) documentation. I highly recommend you use it as you read this article, since it will help to clarify much of the information I present. I will then discuss how you can use the PDH Library to profile the activity of your code without the need for an external profiler.

While I use only C/C++ in this article, the PDH Library itself contains an interface for Microsoft® Visual Basic® as well. See the Platform SDK documentation for more help with that since I don't address Visual Basic at all.

This article assumes you are familiar with the fundamentals of programming in Win32. If you are unfamiliar with general Windows-based programming practices, then you might want to learn some of the fundamentals of general Windows programming before taking on this topic.

Introduction

The  Performance Data Helper (PDH) is a companion library to the native performance-monitoring features of the Windows NT operating system. It is built on top of the standard performance-monitoring features of Windows NT and doesn't really add any new functionality to native performance monitoring.

The performance data that the Windows NT operating system provides contains information for a variable number of object types, instances per object, and counters per object type. The counters are used to measure various aspects of performance. For example, the Process object includes the Handle Count counter to measure the number of handles open by the process. An instance is a unique copy of a particular object type, though not all object types support multiple instances. For example, the System object has no instances since there is only one System. On the other hand, the Process object supports multiple instances because Windows NT supports multiple processes.

In order for a program to utilize the performance features of the Windows NT operating system, use of the Registry functions is necessary. The Registry functions retrieve blobs of data from the HKEY_PERFORMANCE_DATA key that contains the performance information. The blob of data is formatted according to specifications that are documented in the Platform SDK. See the documentation for the PERF_DATA_BLOCK, PERF_OBJECT_TYPE, PERF_COUNTER_DEFINITION, PERF_COUNER_BLOCK, and PERF_INSTANCE_DEFINITION structures for help interpreting the data. You must also be aware of how to perform the calculations on this raw data in order to get the information you would expect from a counter. There are around 30 different types of counters that can be in the performance data, so there are 30 different ways to calculate the information. (Technically there are fewer, since some of the counter types share the same calculation method.)

What the PDH Library does for you is to package this data in a form that doesn't require any traversal at all. As a matter of fact, the library provides a nice dialog box that allows the user to select counters interactively. You can use the library without the dialog box simply by specifying counters as strings. For instance, the counter for a Process object's Handle Count is specified as a string that looks like this: \Process(MyApp)\HandleCount. This simplification is at the heart of the PDH Library. You don't have to know anything about the native performance data in order to easily find the information you seek.

I've been using the terms object, counter, instance, and so on without much in explanation. The following PDH Library overview explains these and other concepts that are related to the PDH Library.

PDH Library Overview

Terminology

See the Platform SDK documentation for more complete definitions. I started with the definitions there and added my own explanations.

Objects/Object Type

An Object Type is defined as a measurable entity. The term object is also used to refer to a measurable entity. The list of objects on my system includes Browser, Cache, ICMP, IP, Logical Disk, Memory, NBT Connection, Network Interface, NWLink IPX, NWLink NetBIOS, NWLink SPX, Objects, Paging File, Physical Disk, Process, Processor, Redirector, Server, Server Work Queues, System, TCP, Telephony, Thread, and UDP.

Each of these objects is associated with a different set of counters. For instance, the Physical Disk object has counters that measure disk performance while the Memory object has counters that measure memory performance.

Counter

A counter is unit of performance. It provides data related to a single item of the system. Some examples of counters are Handle Count and Thread Count, both associated with a Process object. Another counter is the % Processor Time, which measures the amount of processor time an object utilizes. This counter is actually used in two different Object types, a Process object and a Thread object. In a Process object, the % Processor Time counter measures the entire process, while % Processor Time for a Thread object measures only a specific thread.

Instance

An instance is an instantiation of a particular object, such as a specific process or thread. All instances of a given Object have the same counters. For example, the Process object has an instance for each of the running processes. The Thread object has an instance for each thread of each process in the system. As mentioned earlier, some objects, like the Memory object don't have instances at all since there is always only one of them in the system. Some objects may have zero instances, which means that there are no current instantiations of the object. This can occur, for instance, in the Telephony object if Telephony has never been configured.

The above definitions aren't really related to the PDH Library directly since they are part of the native performance data; however, you must understand them in order to use the PDH Library properly. The following definitions, however, are specific to the PDH Library.

Counter name string

A counter name string is of special importance to the PDH Library, since this is the identifier of a counter for inclusion in gathering performance data. The counter names must be formatted a specific way in order to be properly recognized by the PDH Library. The format is:

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

The \\Machine portion is optional; if included, it specifies the name of the machine. If you do not include a machine name, the PDH Library uses the local machine.

The \PerfObject component is required; it specifies the object that contains the counter. If the object supports variable instances, then you must also specify an instance string. The format of the (ParentInstance/ObjectInstance#InstanceIndex) portion depends on the type of object specified. If the object has simple instances, then the format is just the instance name in parentheses. For example, an instance for the Process object would be the process name such as (Explorer) or (MyApp).

The \Counter portion is required; it specifies the performance counter. See the above explanation of Counter for more information on the \Counter portion.

Fortunately, the PDH Library supplies a counter browsing dialog box that will build the counter name strings automatically. This allows you to avoid having to know everything about the counter name strings before you can use the PDH Library. For more help on the format specification of the counter path string, see the Platform SDK documentation.

Query

A query is a collection of counters. The PDH Library supports multiple queries. For instance, you could have a query that contains counters related to one process, and another query that contains counters related to another process. Each of these queries can be individually updated to gather the raw data associated with each counter in the query. Additionally, you could have a query containing counters for which frequent updates are required and another query containing counters for which infrequent updates are needed. Multiple queries allows this flexibility

Your program creates queries. Once created, they can be used in PDH functions to update the counters they contain. Counters are also added to a query by your program. If you don't add any counters to a query, then nothing interesting will occur. I will explain the functions related to queries and adding counters later in this article.

Raw data

Raw data is the data that is associated with a counter as it appears in the native Windows NT performance data. There is little that can be done with the raw data, although it is important for statistical calculations. More on this topic follows.

Formatted data

Formatted data in the PDH Library is data that you expect to see from a counter. The PDH Library formats the data for you based on the calculations that are required depending on the counter type in the native Windows NT performance data. You don't have to know anything about these calculations or how they work in order to get properly formatted data from the counters.

Statistics

The PDH Library handles statistical calculations for you. The library provides statistics on average, minimum, and maximum for each counter you specify. Proper calculation of statistics requires that a collection of raw data be kept for some time period. It is up to your application to save the raw data in a queue and update this information as often as necessary.

Browse Performance Counters Dialog and Callback Function

The PDH Library provides a dialog box that allows the user to interactively select counters for monitoring:

Figure 1. PDH Browse Performance Counters dialog box

This dialog box allows the user to select an object. When an object is selected, the list of counters changes to show the counters that are relevant for the selected object. Also, instances are shown if the object has instances.

There are many ways to modify the behavior of the dialog box. For instance, one may only want to add only a single counter per dialog box or to allow counters from remote machines to be added.

A callback function is associated with the dialog that allows your program to be notified when the user chooses to add a counter. The callback function is executed and all selected counters are reported to the function. The callback function is responsible for actually doing something with the selected counters. If the callback function does nothing, then the selection has no effect. Obviously, for anything interesting to occur, the callback function must add the counter to a query.

Now that I've covered the terminology on a high level, let's dive into the functions and structures of the PDH Library that accomplish these things.

PDH Functions and Structures

The prototypes and structure definitions for the PDH functions come in two header files. The header file PDH.h must be included in order to gain access to the functions, data types, and structure definitions used in the PDH Library.

All of the PDH functions have a return type of PDH_STATUS. The actual values you can expect from the functions are defined in the PDHMsg.h header file. You must include this header file in order to use the definitions described in the documentation.

To properly link to the PDH Library, you must use the PDH.LIB import file that comes with the Platform SDK.

Queries

To create a query and start using the PDH Library, call the PdhOpenQuery function. This function takes a pointer to a HQUERY variable as one of its parameters. This HQUERY variable will contain the handle to the query created. Remember that a query is a collection of counters, so after PdhOpenQuery, the query is initially empty.

To close a query, call PdhCloseQuery, passing the HQUERY for the query you wish to close.

Counters

In the PDH Library, counters are more than just the performance data. Counters also have status and a timestamp.

To add a counter to a query, you must call the PdhAddCounter function. You supply the HQUERY associated with the counter you are adding and also supply the counter name string. You can optionally supply some user data (a 32-bit value) to associate with the counter. The function takes a pointer to a HCOUNTER variable. If the function is successful, then this HCOUNTER variable will contain the handle to the counter.

Here's some sample code that demonstrates adding a counter:

BOOL PDH_AddCounter(LPTSTR szCounterName)
{
    // Add the counter to the current query
    if (ERROR_SUCCESS != PdhAddCounter( hQuery, szCounterName, 0, &hCounter)) {
        return FALSE;
    else
        return TRUE;
}

To remove a counter from a query, call PdhRemoveCounter, passing the HCOUNTER for the counter you wish to remove.

You may be wondering where the counter name string comes from. Well, it can come from any number of sources. Perhaps the counter string is stored in a file, or hard coded in the program. You can also use the PDH Browse Performance Counters dialog box to allow the user to interactively select counters to add. In any case, once a counter name is determined, you must call PdhAddCounter in order to get the counter added to a query. The PDHTest sample application included with this article and the Platform SDK samples can retrieve counter names from a file or from the PDH Browse Performance Counters dialog box.

Browse dialog box

If you want the user to be able to interactively select counters, then you can either build your own counter selection interface, or you can use the PDH Browse Performance Counters Dialog supplied in the library.

If you want to use the PDH Browse Performance Counters dialog box, then you must first create a character buffer to contain the counter name strings. The buffer pointer should be accessible globally since it is necessary to access this buffer in the callback function. Here is an example:

#define INIT_RETURNPATH_SIZE        1024
DWORD gdwReturnPathSize;
LPTSTR gszReturnPath;

The buffer can be allocated any time during program startup. Here is an example:       

gdwReturnPathSize = INIT_RETURNPATH_SIZE;
gszReturnPath = (LPTSTR) LocalAlloc(LPTR, INIT_RETURNPATH_SIZE);
if (gszReturnPath == NULL)
    fRes = FALSE;

In addition to allocating a counter name string buffer, initialization of a PDH_BROWSE_DLG_CONFIG structure must also take place before displaying the dialog. This structure controls the behavior of the PDH Browse Performance Counters dialog box. Like the counter name string buffer, the structure should be allocated so that it is accessible in the callback function. The structure contains the members listed in the table below.:

Table 1. The PDH_BROWSE_DLG_CONFIG structure

Member Explanation
bIncludeInstanceIndex Controls whether or not the counter name string will include the instance index for the counter. See the Platform SDK Overview for more information on the format of the counter name string.
bSingleCounterPerAdd Controls whether the user can select multiple counters for a single operation.
bSingleCounterPerDialog Controls the life of the dialog box. If TRUE, then the dialog box will only allow one "Add" operation, and then disappears. If FALSE, then the dialog box stays visible until the user selects the "Close" button allowing multiple add operations.
bLocalCountersOnly Controls whether the user can select counters from a remote machine.
bWilCardInstances Controls whether the counter name string will contain a wildcard for instances, or each counter separately. See the following section on the callback function for more information on parsing the counter name string buffer.
bHideDetailBox Controls whether the dialog displays the "Detail Level" combo box. If TRUE, then the detail level is fixed at the level specified by the dwDefaultDetailLevel member of this structure.
bInitializePath Controls whether the dialog displays the default counter for the machine or the counter in the counter name string buffer.
bReserved Don't use; just initialize to zero.
hWndOwner An HWND to be the "owner" of the dialog box.
szReserved Don't use; just initialize to zero.
szReturnPathBuffer Assign this member to the pointer of the counter name string buffer. This allows the dialog box to fill in the buffer with the selected counters.
cchReturnPathLength The maximum size of the counter name string buffer.
pCallBack Pointer to the callback function in your program. More on the callback function below.
dwCallBackArg 32 bits of data that you can use to pass to the callback function. More on the callback function below.
CallBackStatus Status of the dialog. The callback function can use this member to determine what action to take. For instance, if the counter name string buffer is too small for the selected counter name, this member will be PDH_MORE_DATA. The callback function handles the condition as necessary. More on the callback function below.
dwDefaultDetailLevel The detail level used in the dialog box. If the bHideDetailBox member is TRUE, then this member acts as a filter for the displayed counters. If bHideDetailBox is FALSE, then this member acts as the initial detail level in the dialog box.
szDialogBoxCaption Pointer to a string that will serve as the caption of the PDH Browse Performance Counters dialog box. If NULL, then default is "Browse Performance Counters."

After the PDH_BROWSE_DLG_CONFIG structure is properly initialized with the items you want, call the PdhBrowseCounters function. This will cause the dialog box to display. Control is not returned to your program until the dialog box is dismissed.

Here is sample code that demonstrates how to initialize the structure and call the dialog function:

ZeroMemory(&gpdhBrowseDlgConfig, sizeof(gpdhBrowseDlgConfig));
// These flags (gbXxxXxx) are declared globally.
gpdhBrowseDlgConfig.bIncludeInstanceIndex = gbIncludeInstanceIndex;
gpdhBrowseDlgConfig.bSingleCounterPerAdd = gbSingleCounterPerAdd;
gpdhBrowseDlgConfig.bSingleCounterPerDialog = gbSingleCounterPerDialog;
gpdhBrowseDlgConfig.bLocalCountersOnly = gbLocalCountersOnly;
gpdhBrowseDlgConfig.bWildCardInstances = gbWildCardInstances;
gpdhBrowseDlgConfig.bHideDetailBox = gbHideDetailBox;
gpdhBrowseDlgConfig.bInitializePath = gbInitializePath;
gpdhBrowseDlgConfig.hWndOwner = hWnd;
gpdhBrowseDlgConfig.szReturnPathBuffer = gszReturnPath;
gpdhBrowseDlgConfig.cchReturnPathLength = gdwReturnPathSize;
gpdhBrowseDlgConfig.pCallBack = PDH_BrowseCallback;
PdhBrowseCounters(&gpdhBrowseDlgConfig);

The Browse Performance Counters dialog box Callback Function

The callback function is the link between your application and the PDH Browse Performance Counters dialog box. The callback function is called by the dialog box when the user adds a single counter or multiple counters. It is the job of the callback function to add the selected counters to a query or handle errors reported in the CallBackStatus member of the PDH_BROWSE_DLG_CONFIG structure.

The callback function should be declared as follows:

PDH_STATUS __stdcall PDH_BrowseCallback(DWORD dwParam)

The DWORD parameter passed to the function is the same value that was specified in the dwCallBackArg member of the PDH_BROWSE_DLG_CONFIG structure.

Error handling in the callback function

As stated earlier, the PDH_BROWSE_DLG_CONFIG structure used in the PdhBrowseCounters function should be accessible in the callback function. This is to allow the function to check the CallBackStatus member and do whatever error handling the dialog box needs. The current implementation documents only one case where the callback function must handle an error. If the CallBackStatus member is PDH_MORE_DATA, then the counter name string buffer is too small. The callback function should reallocate the buffer to make it larger. The cchReturnPathLength member of the PDH_BROWSE_DLG_CONFIG structure must be changed to reflect the new size of the buffer. Additionally, if the location of the buffer changes, the szReturnPathBuffer member of the PDH_BROWSE_DLG_CONFIG structure must also be changed to reflect the new location. After all of this has been done successfully, the callback function must return PDH_RETRY to inform the dialog box that a new buffer was created. The dialog function will try to call the callback function again using the larger buffer to contain the user's selections. If there is any error during the reallocation, then the callback function should return an appropriate error code from PDHMsg.h.

Here's some sample code that demonstrates this technique:

#define BUFFER_INCREMENT    512
// Buffer too small for the counters selected?
if (gpdhBrowseDlgConfig.CallBackStatus == PDH_MORE_DATA) {
        
    // Free original buffer
    if (NULL != LocalFree(gszReturnPath))
        return PDH_MEMORY_ALLOCATION_FAILURE;
        
    // Alloc new larger buffer
    gdwReturnPathSize += BUFFER_INCREMENT;
    gszReturnPath = (LPTSTR) LocalAlloc(LPTR, gdwReturnPathSize);
    if (gszReturnPath == NULL)
        return PDH_MEMORY_ALLOCATION_FAILURE;
    // Set the config structure members for the new buffer
    gpdhBrowseDlgConfig.szReturnPathBuffer = gszReturnPath;
    gpdhBrowseDlgConfig.cchReturnPathLength = gdwReturnPathSize;
    // Retry the counter browse selection.
    return PDH_RETRY;
}

Adding counters in the callback function

If the CallBackStatus member of the PDH_BROWSE_DLG_CONFIG structure is ERROR_SUCCESS, then there is no error to handle. The counter name string buffer should be examined and each selected counter should be added to the desired query.

The counter name string buffer contains counter names in a multi-SZ format. This means that each string is terminated with a NULL character and the last string is terminated with another NULL character.

Here's some sample code that demonstrates how to traverse the counter name string buffer. As each string is retrieved, it can be added to a query with PdhAddCounter, or, as in this sample code, a general-purpose function (PDH_AddCounter) is called to add the counter to a query.

    PDH_STATUS returnValue;
    LPTSTR szCurrentPath;
    int nLen;
    
    returnValue = ERROR_SUCCESS;
    szCurrentPath = gszReturnPath;
    // This string manipulation code is in a try/except block
    // to gracefully handle any problems with pointer values,
    // null termination, boundary conditions, etc.
    __try {
        while (TRUE) {
            nLen = lstrlen(szCurrentPath);
            if (nLen == 0)
                break;
            PDH_AddCounter(szCurrentPath);
            //
            // The return value for PDH_AddCounter is ignored
            // since we still want to add the other counters
            // in the buffer.
            //
            szCurrentPath += (nLen + 1);
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER) {
        returnValue = PDH_INVALID_BUFFER;
    }
    return returnValue ;

Data collection and formatting

Data collection to update the raw data for each counter in a query is simply a matter of calling the PdhCollectQueryData function, supplying the appropriate HQUERY. This function updates the raw data for each counter in the query and also updates the status code and timestamp for each counter.

The raw data, however, is not very useful for display purposes. To format the raw data into something that is useful for display, call the PdhGetFormattedCounterValue function. This function must be called for each HCOUNTER that you want to display. The first parameter of this function is the HCOUNTER for the counter being formatted. The second parameter specifies the data type your code requires of the formatted value. You can specify the PDH_FMT_DOUBLE, PDH_FMT_LARGE, or PDH_FMT_LONG flag. Optionally, you can combine the previous flag with either PDH_FMT_NOSCALE or PDH_FMT_1000. See the Platform SDK Function reference for the PdhGetFormattedCounterValue for more information on these flags. The third parameters can be specified to receive the counter type. The counter type is part of the native Windows NT performance data and allows your application to know what type of counter you are dealing with. The fourth parameter is a pointer to a PDH_FMT_COUNTERVALUE structure that receives the counter value.

This PDH_FMT_COUNTERVALUE structure is the structure with a union that allows you to get data that is appropriate for display. The PDH_FMT_COUNTERVALUE members are explained in Table 2 below.

Table 2. The PDH_FMT_COUNTERVALUE structure

Member Explanation
CStatus Status of the last collection operation for the counter. Any value other than PDH_CSTATUS_VALID_DATA ((DWORD) 0x00000000L) is an error condition. If this is the case, then the data should not be used. See the PDHMsg.h file for a list of the possible PDH_CSTATUS_XXX values that are expected. These are also more fully explained in the Platform SDK documentation.
longValue If the format specified in the call to PdhGetFormattedCounterValue is PDH_FMT_LONG, then this member is the counter data as a LONG.
doubleValue If the format is PDH_FMT_DOUBLE, then this member is the counter data as a double (double-precision floating-point real).
largeValue If the format is PDH_FMT_LARGE, then this member is the counter data as a LONGLONG.

After the data is formatted successfully, it can then be displayed in any way your application desires. The sample application PDHTest does this by using wsprintf to format a string containing the formatted data. This string is then set as a list view item's text. More on the PDHTest sample application follows in the next section.

Statistical calculation

Statistical calculation allows you derive statistics from the data being collected. The PDH Library can calculate the minimum, maximum, and average for the data collected. In order to calculate this data, the PDH Library must be supplied with a buffer containing raw data for a counter. It is left to your application to decide how often to calculate and collect the raw data. It is also up to your application to decide how many samples to take for the calculations. The buffer must be an array of PDH_RAW_COUNTER structures.

The collection of the raw data is accomplished by calling the PdhGetRawCounterValue after the query has been updated with PdhCollectQueryData. The PdhGetRawCounterValue function takes a HCOUNTER as the first parameter and an optional pointer to a DWORD to get the counter type (from the native Windows NT performance data). The third parameter is a pointer to a PDH_RAW_COUNTER structure that will receive the raw counter value.

Another thing that is a little tedious, but must be accomplished, is to manage the array to keep track of how many entries are used, what entry contains the oldest data, and what entry should contain the next data. When first starting, these are all zero. With each collection of raw data the following should take place to update these items:

  1. The number of entries should be incremented. When the number of entries reaches the size of the array, it should stay there.

  2. The next entry to be assigned should be incremented. If the end of the array is reached, then this index should return to zero to start over.

  3. The oldest entry should be updated from zero only if the number of entries (from step 1) has reached the size of the buffer. If so, then the oldest index is changed to be the same as the index for the next entry to be assigned since it contains the oldest data and will be updated at the next data collection.

These steps are standard ring buffer manipulation techniques. Here's some sample code that shows how to do this (notice the use of the try/except to guard against any exception in the buffer manipulation):

BOOL PDH_UpdateRawValue()
{
    BOOL fRes = TRUE;
    PPDH_RAW_COUNTER ppdhRawCounter;
    __try {
        // Get a pointer to the next entry to be assigned.
        ppdhRawCounter = &(a_RawValue[nNextIndex]);
        if (ERROR_SUCCESS != PdhGetRawCounterValue( 
                                    pCounterStruct->hCounter,
                                    NULL,
                                    ppdhRawCounter) ) {
            fRes = FALSE;
        }
        else {
            // Update raw counter - up to MAX_RAW_VALUES
            nRawCount = min(nRawCount + 1, MAX_RAW_VALUES);
            // Update next index - rolls to zero upon reaching top
            nNextIndex = (nNextIndex + 1) % MAX_RAW_VALUES;
            // The Oldest index remains zero until the buffer is filled.
            // It will now be the same as the "next" index 
            // since next has old data from previous collections.
            if (nRawCount >= MAX_RAW_VALUES)
                nOldestIndex = nNextIndex;
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER) {
        fRes = FALSE;
    }
    return fRes;
}

Once the buffer is properly managed using techniques described above, the counter statistics can be calculated by calling the PdhComputeCounterStatistics. The first parameter of this function is the HCOUNTER for the counter being calculated. The next parameter is the format flag (PDH_FMT_DOUBLE, PDH_FMT_LARGE, or PDH_FMT_LONG). The third parameter is the index of the first entry in the buffer (nOldestIndex). The fourth parameter is the number of entries in the array (nRawCount). The fifth parameter is the address of the beginning of the array and the last parameter is a pointer to a PDH_STATISTICS structure.

After successfully calling this function, the PDH_STATISTICS structure contains the members detailed in Table 3 below.

Table 3. The PDH_STATISTICS structure

Member Explanation
dwFormat The format of the data. This is PDH_FMT_DOUBLE, PDH_FMT_LARGE, or PDH_FMT_LONG, depending on what is passed to PdhComputeCounterStatistics.
count The number of values in the array.
min The minimum of the values. This member is a PDH_FMT_COUNTERVALUE structure and is treated just like the structure returned from the PdhGetFormattedCounterValue function.
max The maximum of the values. This member is also a PDH_FMT_COUNTERVALUE structure.
mean The average of the values. The member is also a PDH_FMT_COUNTERVALUE structure.

You must be sure to check the CStatus member of the min, max, and mean structures in order to determine if the PDH_FMT_COUNTERVALUE structure is valid. This will occur at the beginning of your calculations since there is not enough data to create the statistics. There must be at least 2 entries in the array to properly compute the statistics.

Other PDH functions

There are several other functions in the PDH Library that allow you to connect to remote machines, enumerate the performance objects, enumerate counters and instances for an object, get counter information, and set scale factors. I'm not going to cover those here, but defer instead to the Platform SDK documentation. See the section on the PDH Library for more help with those functions. Some of them are in the PDHTest sample application and I discuss them below, but the most complete descriptions can be found in the SDK documentation.

PDHTest Sample Application

The PDHTest sample application comes with this article and is also included in the Platform SDK Win32 samples. It is an application that demonstrates how to use the PDH functions and structures in a real world application. It doesn't have all of the bells and whistles that a full-featured application such as PerfMon has, but it does the job of performance monitoring and shows you how to do the same with your own code.

Table 4. PDHTest Sample files

File Description
PDHFns.c: Demonstrates the use of the PDH Library functions. This file contains all of the calls into the PDH Library. The functions in this file are called from Window.c in order to accomplish whatever the user desires.
Window.c: Window handling routines—Main Window, List View, Status Window, Window procedures, menu handling routines, list view manipulation, etc. This file doesn't call any PDH functions directly, but instead calls the functions in PDHFns.c.
PDHTEST.h: Header file containing structure definitions, constant definitions, and function prototypes. The sample utilizes its own PDHCOUNTERSTRUCT structure, which isn't part of the PDH Library. The sample uses it to contain the HCOUNTER for each counter added to a query. The structure also contains the array of PDH_RAW_COUNTER structures and the buffer manipulation indices necessary for statistical calculations on the counter. More on this structure and how it is maintained follows.
RESOURCE.h: Header file containing resource IDs.
PDHRes.rc: Resource file containing all resources.
Import.txt: Text file that contains strings (counter names) that can be imported.

If you'd like to just use the program without looking at the code, here is a simple explanation of how to operate it.

The program starts by displaying an empty list view control and initializing an empty query. To add counters to the query, you can either import them (from a text file) by choosing the Import... option from the File menu or the Browse Counters... option from the PDH menu. The latter choice displays the PDH Browse Performance Counters dialog box and allows you to interactively select counters. Once counters have been added, their names will appear in the list view control. To collect (and display) data, you must choose the Collect Query Data option from the PDH menu. This will gather raw data, format it, and display it in the list view control.

You can automate data collection by choosing the Start option from the Auto menu. To stop automatic collection, choose the Stop option from the Auto menu. While this feature is enabled, data collection will occur once each second. To display statistics (minimum, maximum, and average), choose Statistics from the Auto menu. If this option is enabled, then raw data will be collected and stored. Once each second, the program will pass this data to the PDH Library for analysis and the statistical results will be displayed.

You can export the counter list to a text file by choosing the Export... option from the File menu.

You can right-click on the list view control to more quickly locate menu options.

So, now, let me explain how this code works. First, you'll notice in PDHFns.c, that try/except blocks wrap all functions in order to enhance the stability of the code. This will help to diagnose any problems that may arise in the code and not cause the program to crash if such a problem is encountered.

First, I should explain that the sample uses only a single query. The query is opened with the PdhOpenQuery PDH function in the PDH_Inititialize function in PDHFns.c. This function is called when the main window is created. The query is destroyed in the PDH_Uninitialize function called when the main window is closed. I should also explain that the main user interface for the sample is a List View common control. The list view contains 5 columns: Counter Name, Value, Minimum, Maximum, and Average. Each line in the list view represents a counter in the query.

Adding counters

To add a counter to the query, the PDH_AddCounter function can be called. This function is implemented in the PDHFns.c source file, from which all of the PDH functions are called. This function allocates the necessary memory (PDHCOUNTERSTRUCT) for managing the counter and also calls a function to add an item to the list view control. The address of the associated PDHCOUNTERSTRUCT structure is stored in the list view item's LPARAM. When List View control events occur, the selected item is retrieved and the appropriate PDHCOUNTERSTRUCT is found. This allows access to the HCOUNTER for the counter and also allows access to the raw data array (used in statistical calculations) and its management items (number of items, oldest item, and next item). After the structure is allocated and the list view item is added, then PdhAddCounter is called to add the counter to the query.

The PDH_AddCounter is passed the address of a string buffer containing a counter name. The PDH_AddCounter is called currently from the PDH_BrowseDialogCallback function and from the function that handles importing counters from a text file.

PDH Browse dialog box

The PDH_BrowseCounters function, implemented in PDHFns.c, is responsible for calling the PdhBrowseCounters function. First, it initializes the PDH_BROWSE_DLG_CONFIG structure. When the user chooses to add counters, the PDH Browse Dialog calls the callback function PDH_BrowseCallback, also implemented in PDHFns.c. Various flags, declared in the PDHFns.c source file, are assigned to the appropriate members of the PDH_BROWSE_DLG_CONFIG structure. The Flags menu is used to control the state of these flags and subsequently the behavior of the dialog.

Figure 2. PDHTest Flags menu

The PDH Browse Performance Counters dialog box is displayed by choosing the Browse Counters… option from the PDH menu.

Figure 3. PDHTest PDH Menu

Importing counters from a file

To import counters from a file, choose the Import… option from the File menu. The FileImport function, implemented in Window.c, opens the user-selected file and then calls TraverseFileAndAddCounters. I won't bore you with the details of this function, since it is quite tedious. I suppose I could have used some C-Runtime functions to read a line at a time, but I wanted to see if I could do it with only Win32 APIs. As each counter is read from the file, the PDH_AddCounter function is called. The import file must be formatted with valid counter names; each one must be terminated with a CR/LF pair, including the last counter.

Figure 4. PDH File menu

Once counters have been added to the query, they can be easily exported by choosing the Export… option from the File menu. The FileExport function and the functions it calls, all implemented in Window.c, write each counter name to the user-specified file.

Removing counters

When a counter is removed from the query, the RemoveCounter function is called. This function, implemented in Window.c, retrieves the selected list view item and subsequently retrieves the counter name and the associated PDHCOUNTERSTRUCT pointer. The PDH_RemoveCounter function is called to have the counter removed. Removal of the list view item is completed when the PDH_RemoveCounter function returns.

The PDH_RemoveCounter function simply calls PdhRemoveCounter to remove the counter from the query and then frees the memory for the PDHCOUNTERSTRUCT structure. The PDH_RemoveCounter function is implemented in PDHFns.c.

Counter information

As you can see from the PDH menu, you can get counter information by choosing the Get Counter Info… option. This option causes the PDH_GetCounterInfo function, implemented in PDHFns.c, to fill in a PDH_COUNTER_INFO structure for the selected counter. The address of the PDH_COUNTER_INFO structure is then passed to a dialog box function that displays the following information from the structure:

Figure 5. Counter Information Dialog

Additionally, selecting the Set Counter Scale… option from the PDH menu sets the scale for the values from a counter. This option causes the PDH_SetCounterScale function to be called for the selected counter. This function, implemented in PDHFns.c, displays a dialog box that allows the user to select the scale factor. Once a scale factor is selected, the PDH_SetCounterScale function calls the PdhSetCounterScaleFactor function to change the counter's scale.

Collecting and Displaying Query Data

To actually gather and display performance data for a counter, the Collect Query Data option should be selected from the PDH menu. This causes the UpdateListView function, implemented in Window.c, to be called. The function first calls PDH_CollectQueryData, implemented in PDHFns.c, to update the raw data for each counter in the query. Then UpdateListView calls PDH_UpdateValue, implemented in PDHFns.c, for each item in the list view. The PDH_UpdateValue function formats the raw data for single counter by calling the PdhGetFormattedCounterValue function. If this is successful, then PDH_UpdateValue calls PDH_DisplayFormattedValue, also implemented in PDHFns.c, to update the Value column of the list view item.

Note   Let me take moment to note that I am not mentioning any error handling that these functions perform. It should go without saying that error handling is crucial to application performance and should always be practiced. Checking return codes for success and calling error handling functions when an error occurs should be something that is included in any code. For clarity, error handling is left out of this discussion, but you can view the source to see what measures have been taken to ensure smooth operation of the program.

Automatic Collection and Display

You can make the sample program automatically gather and display the counter data once each second. This is accomplished by selecting the Start option from the Auto menu. This causes a timer to be started that sends a WM_TIMER message to the main window once each second. When this message is received, UpdateListView is called just like above. If you so desire, statistics can also be updated at this interval.

Figure 6. PDHTest Auto menu

Statistical calculation

To enable or disable statistical calculation, select the Statistics option from the Auto menu. This simply causes a global flag to be set to either TRUE or FALSE. In the processing of the WM_TIMER message mentioned above, the program examines the global flag to determine if statistical calculation is enabled. If enabled, then the program calls the UpdateStatistics function, implemented in Window.c. UpdateStatistics calls PDH_UpdateRawValue and PDH_DisplayStatistics, both implemented in PDHFns.c, for each counter in the query.

A simplified version of the PDH_UpdateRawValue function is listed above. The difference between the listing above and the actual code is that the PDHCOUNTERSTRUCT structure is used to gain access to the counter's HCOUNTER and raw data array. The PDH_UpdateRawValue function calls PdhGetRawCounterValue to get the raw data from the last collection and places the data into the raw data array. Then PDH_UpdateRawValue updates the indices used to manage the array.

The PDH_DisplayStatistics function calls the PdhComputeCounterStatistics function to compute and format the minimum, maximum, and average values for the data in the raw data array. The PdhComputeCounterStatistics fills in a PDH_STATISTICS structure containing three PDH_FMT_COUNTERVALUE structures. The three statistical values are placed into the appropriate columns of the list view item by calling the PDH_DisplayFormattedValue for each PDH_FMT_COUNTERVALUE structure; this is similar to displaying the counter's value from a PDH_FMT_COUNTERVALUE structure after it is formatted.

Code Improvement

Wouldn't it be great if you could place code into your existing project to do your own profiling?  Well, with the PDH Library, this is not only possible, but also really easy. I have put together a short bit of code that allows you to easily watch things like handle counts, thread counts, etc.

First, I'll cover the code that interfaces with the PDH Library, and then I'll cover how to add the code to your own program. Although it is possible to use my code in your own application, I would expect you to create your own. It is pretty simple to create one of these types of libraries, so I would expect your own to add functionality like logging results to files for later analysis, or interfacing with a companion tester application to see the results graphically.

The code that I show is called the PDHProf module. Here is the PDHProf.h header file:

// File: PDH.H
// Datatype definitions and function 
// prototypes for the PDHProf module.
#include <pdh.h>
typedef struct tagPDHPROFSTRUCT {
    HQUERY hQuery;
    HCOUNTER hCounter;
    DWORD dwStartingValue;
    DWORD dwEndingValue;
} PDHPROFSTRUCT, *PPDHPROFSTRUCT;
BOOL CreatePDHProfItem(LPTSTR lpszCounterName, PPDHPROFSTRUCT pStruct);
BOOL ClosePDHProfItem(PPDHPROFSTRUCT pStruct);
BOOL PDHProfStart(PPDHPROFSTRUCT pStruct);
BOOL PDHProfUpdate(PPDHPROFSTRUCT pStruct);
BOOL PDHProfGetDifference(PPDHPROFSTRUCT pStruct, int * pnDiff);

The PDHPROFSTRUCT is a structure that contains the HQUERY, HCOUNTER, starting and ending values for the counter. The application using this code should allocate a PDHPROFSTRUCT for each counter used. The address of this structure is passed to all of the PDHProf functions.

Table 5. PDHProf Functions

Function Function explanation
CreatePDHProfItem CreatePDHProfItem should be called first to start watching the desired counter. The first parameter is the name of the counter to watch. The second parameter is the address of the corresponding PDHPROFSTRUCT, which the application must allocate. The function opens a new HQUERY and assigns it to the hQuery member of the structure. It then adds the counter to the query and updates the hCounter member of the structure with the HCOUNTER for the counter.

The name of the counter is usually hard-coded in your application, or may be stored in a file.

ClosePDHProfItem The ClosePDHProfItem should be called when you are finished with a counter. The function removes the HCOUNTER from the query and then closes the HQUERY. This renders the PDHPROFSTRUCT structure useless for more profiling; though it continues to be useful for retrieving the starting and ending values for the counter.
PDHProfStart The PDHProfStart function sets the starting value for the PDHPROFSTRUCT and should be called just before entering the code you wish to profile. It should also be called to reset the starting value for subsequent profiling.
PDHProfUpdate The PDHProfUpdate function sets the ending value for the PDHPROFSTRUCT structure and should be called just after returning from the code you wish to profile. It should also be called to update the ending value for subsequent profiling.
PDHProfGetDifference The PDHProfGetDifference computes the difference between the starting and ending valued in the PDHPROFSTRUCT structure. This function should be called after PDHProfUpdate has been called. The difference is returned in the int * parameter. Note that an app can use the members of the PDHPROFSTRUCT directly instead of this function if desired.

Here's the actual code listing for the PDHProf module:

#include <windows.h>
#include <pdh.h>
#include "PDHProf.h"
// Internal prototypes
BOOL OpenPDHProf(PPDHPROFSTRUCT pStruct);
BOOL ClosePDHProf(PPDHPROFSTRUCT pStruct);
BOOL PDHUpdateValue(HQUERY hQuery, HCOUNTER hCounter, PDWORD pdwValue);
BOOL OpenPDHProf(
    PPDHPROFSTRUCT pStruct ) 
{
    BOOL fRes = TRUE;
    __try {
        if (pStruct->hQuery == NULL)
            fRes = FALSE;
        else {
            if (ERROR_SUCCESS != PdhOpenQuery(
                                    NULL, 
                                    0, 
                                    &(pStruct->hQuery))) {
                pStruct->hQuery = NULL;
                fRes = FALSE;
            }
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER) {
        fRes = FALSE;
    }
    return fRes;
}
BOOL ClosePDHProf(
    PPDHPROFSTRUCT pStruct )
{
    BOOL fRes = TRUE;
    __try {
        if (pStruct->hQuery == NULL)
            fRes = FALSE;
        else {
            if (ERROR_SUCCESS != PdhCloseQuery(
                                    pStruct->hQuery)) {
                pStruct->hQuery = NULL;
                fRes = FALSE;
            }
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER) {
        fRes = FALSE;
    }
    return fRes;
}
BOOL CreatePDHProfItem(
    LPTSTR lpszCounterName, 
    PPDHPROFSTRUCT pStruct ) 
{
    BOOL fRes = TRUE;
    __try {
        if (OpenPDHProf(pStruct))
            fRes = (ERROR_SUCCESS == PdhAddCounter(
                                        pStruct->hQuery, 
                                        lpszCounterName, 
                                        0, 
                                        &(pStruct->hCounter)));
        else
            fRes = FALSE;
    }
    __except(EXCEPTION_EXECUTE_HANDLER) {
        fRes = FALSE;
    }
    return fRes;
}
BOOL ClosePDHProfItem(
    PPDHPROFSTRUCT pStruct )
{
    BOOL fRes = TRUE;
    __try {
        fRes = (ERROR_SUCCESS == PdhRemoveCounter(
                                    pStruct->hCounter));
        if (!ClosePDHProf(pStruct))
            fRes = FALSE;
    }
    __except(EXCEPTION_EXECUTE_HANDLER) {
        fRes = FALSE;
    }
    return fRes;
}
BOOL PDHUpdateValue(
    HQUERY hQuery, 
    HCOUNTER hCounter, 
    PDWORD pdwValue )
{
    BOOL fRes = FALSE;
    PDH_FMT_COUNTERVALUE pdhFMTVal;
    __try {
        if (ERROR_SUCCESS == PdhCollectQueryData(hQuery)) {
            if (ERROR_SUCCESS == PdhGetFormattedCounterValue(
                                    hCounter, 
                                    PDH_FMT_LONG, 
                                    NULL, 
                                    &pdhFMTVal)) {
                if (pdhFMTVal.CStatus == ERROR_SUCCESS) {
                    *pdwValue = pdhFMTVal.longValue;
                    fRes = TRUE;
                }
            }
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER) {
        fRes = FALSE;
    }
    return fRes;
}
BOOL PDHProfStart(
    PPDHPROFSTRUCT pStruct )
{
    BOOL fRes = TRUE;
    __try {
        fRes = PDHUpdateValue(
                       pStruct->hQuery, 
                       pStruct->hCounter, 
                       &(pStruct->dwStartingValue));
    }
    __except(EXCEPTION_EXECUTE_HANDLER) {
        fRes = FALSE;
    }
    return fRes;
}
BOOL PDHProfUpdate(
    PPDHPROFSTRUCT pStruct )
{
    BOOL fRes = TRUE;
    __try {
        fRes = PDHUpdateValue(
                       pStruct->hQuery, 
                       pStruct->hCounter, 
                       &(pStruct->dwEndingValue));
    }
    __except(EXCEPTION_EXECUTE_HANDLER) {
        fRes = FALSE;
    }
    return fRes;
}
BOOL PDHProfGetDifference(
    PPDHPROFSTRUCT pStruct, 
    int * pnDiff )
{
    BOOL fRes = TRUE;
    __try {
        *pnDiff = (pStruct->dwEndingValue) - 
                  (pStruct->dwStartingValue);
    }
    __except(EXCEPTION_EXECUTE_HANDLER) {
        fRes = FALSE;
    }
    return fRes;
}

Using this module in your own code is very simple. For instance, here is a simple program that tracks its own handle count:

#include <stdio.h>
#include "PDHProf.h"
HANDLE ghEvent;
void UndoSomethingInteresting()
{
    CloseHandle(ghEvent);
    return;
}
void DoSomethingInteresting()
{
    ghEvent = CreateEvent(NULL, TRUE, FALSE, NULL); 
    return;
}
void main()
{
    PDHPROFSTRUCT pdhStruct;
    int nDifference;
    if (!CreatePDHProfItem("\\Process(PDHProf)\\Handle Count", &pdhStruct))
        printf("Error creating PDH prof item\n");
    else
        printf("Profiling...\n");
    if (!PDHProfStart(&pdhStruct)) {
        printf("Error starting PDH prof item\n");
    }
    DoSomethingInteresting();
//    UndoSomethingInteresting();
    if (!PDHProfUpdate(&pdhStruct)) {
        printf("Error updating PDH prof item\n");
    }
    if (!PDHProfGetDifference(&pdhStruct, &nDifference)) {
        printf("Erorr getting PDH prof item difference\n");
    }
    else {
        printf("Counter difference = %d\n", nDifference);
        printf("Starting value = %d\n", pdhStruct.dwStartingValue);
        printf("Ending value = %d\n", pdhStruct.dwEndingValue);
    }
    if (!PDHProfStart(&pdhStruct)) {
        printf("Error starting PDH prof item\n");
    }
    if (!ClosePDHProfItem(&pdhStruct)) {
        printf("Error closing PDH prof item\n");
    }
}

The code above, which calls DoSomethingInteresting, but fails to call UndoSomethingInteresting, produces the following output:

Profiling...
Counter difference = 1
Starting value     = 55
Ending value       = 56

The output demonstrates that the difference between the starting and ending values is one. This means that one more handle exists in the process at the end of the code profiled indicating a handle leak.

The code above specifies the Handle Count of the process by using the following code:

if (!CreatePDHProfItem("\\Process(PDHProf)\\Handle Count", &pdhStruct))

The (PDHProf) portion of the counter name represents the Instance of the Process object. It is the application name that I used to test this code. You'll want to use your own application name when specifying counter name strings to profile for your application. To change the counter to watch the Thread Count of the process, you would change the code to:

if (!CreatePDHProfItem("\\Process(PDHProf)\\Thread Count", &pdhStruct))

This would allow the application to self-monitor its thread count to determine how many threads are running in the program.

I have specifically avoided going into detail to explain how each counter represents application or system performance. That topic is too broad for this discussion since I only cover the PDH Library, not performance monitoring in general. For more help interpreting the data retrieved with the PDH Library or other performance monitoring tools like PerfMon, which comes with Windows NT, please see the documentation in the Windows NT Resource Kit. It contains a wealth of information to help in diagnosing performance problems and interpreting data from performance counters.

I imagine that the code I've supplied here could be used in an application without modification, though it is too simple to be very useful. It would be more useful to create code that does self-profiling like this, but adds functionality to save the data in log files for later analysis. Perhaps you could write a library that communicates with a companion application to graphically display the performance data for instance analysis.

Conclusion

With the PDH Library, you can concentrate on getting performance results without having to worry about wading through the native NT performance data. This article set out to explain the library in general, explain the PDHTest sample application and then show to do self-profiling. The PDHTest sample application is a very simple performance monitor that demonstrates how to use the PDH Library functions in a real-world monitoring application. The self-profiling code that I include in this article is a good start, but there's much room for improvement to make it more useful. That is left as an exercise for you, the programmer.