Skip to main content

IBM WebSphere Developer Technical Journal: Custom Performance Analysis using the Microsoft Performance Data Helper

Kevin Braithwaite (braithwa@uk.ibm.com), Performance Specialist, IBM UK Laboratories
Kevin Braithwaite is a Performance Specialist with the WebSphere Business Integration Message Broker performance team in IBM Hursley. Kevin works with development in evaluating new releases of WebSphere Business Integration Message Broker, and with customers to provide consultancy on design, configuration and tuning issues relating to this product. You can contact Kevin at braithwa@uk.ibm.com.

Summary:  This article provides an introduction to the Microsoft Performance Data Helper library and its functions, and explains, with examples, how to exploit the availability of this library to create effective custom performance monitoring applications.

Date:  14 Oct 2003
Level:  Introductory

Activity:  1052 views
Comments:  

Introduction

The performance of computer applications is becoming increasingly important as users strive to maximize a system's potential in terms of throughput and utilization. However, there are often times when system performance is hindered due to some unforeseen bottleneck, which can reduce the efficiency of an application on the system, and prevent the system from being fully utilized.

When investigating the performance of an application running on the Windows® platform, the facilities provided with the Windows Performance Monitor, packaged with Microsoft® Windows NT® and Windows 2000 operating systems, enable us to retrieve a variety of detailed information, such as process CPU utilization, disk activity, etc., that can help us identify performance bottlenecks. A major drawback of this built-in function, however, has been its inability to automate the collection of such information. On the other hand, Windows does expose the underlying peformance data library that the Performance Monitor uses through a programming interface, which can be accessed by creating custom C++ applications.

This article provides an introduction to the Microsoft Performance Data Helper library (PDH.dll) and its functions, and explains, with examples, how to exploit the availability of this library to create effective custom performance monitoring applications.

This article and the examples provided are intended for developers and testers who require access to detailed performance data.


Performance bottlenecks

There are several different types of bottlenecks, but two main areas of concern for virtually all software products and applications are CPU and I/O, referring to whether an application is CPU-bound or I/O-bound. An application utilizing nearly 100% of a CPU's processing power is considered CPU-bound; if the CPU is reasonably quiet, using noticeably less than one CPU's worth of utilization, but a significant I/O wait is being experienced, the application is considered I/O-bound.

Let's consider a particular scenario where the business needs for a company's application have changed, requiring that the system throughput be significantly increased. The company tries to increase throughput by adding more users to the system, but the increases in throughput peak early, at a point way below the desired level. Such a case is most likely due to a performance bottleneck, whether it be the CPU, disk I/O, or something different. To achieve the desired level of throughput, some investigating needs to be done to find out where the constraints are, followed by analysis to determine the best ways to alleviate the constraints.

For example, take a Windows 2000 server system running WebSphere® MQ, WebSphere Business Integration Message Broker and DB2® on a four way NetFinity® machine:

  • The system is running with a consistently busy workload.
  • The DataFlowEngine process, the fundamental WebSphere Business Integration Message Broker process, is running at only 7% CPU usage, rather than the potential 25% (this simple example is referring to a single instance of the DataFlowEngine process containing a single message flow and therefore can only run in one of the four CPUs, hence a maximum CPU usage of 25%).
  • After investigating the system, it is found that the DB2 and WebSphere MQSeries log writes are taking an average of 15 milliseconds. This is considered high and suggests that the system may be I/O bound. To address this, the logs are placed on fast write cached disks, reducing the disk write time.
  • It is then discovered that the DataFlowEngine process is using 25% of the processor power, equivalent to one of the machine's available four processors, thus suggesting that the machine is now CPU bound, as the process is using all the CPU available to it. From one perspective this is actually good, since this suggests that the system would respond well to faster or additional processors (by running another copy of the DataFlowEngine process in one of the remaining processors).
  • The decision is therefore made to run an additional copy of the DataFlowEngine process, and the throughput target is met.

This is a trivial example and focuses on two simple bottlenecks, but it highlights the basic advantages of having access to system performance information.

So how can you get this data?

There are two ways: The first is through the Windows Performance Monitor packaged within the Windows operating system. The second is by using a programming interface.


The Windows Performance Monitor

The Windows Performance Monitor is built into the Windows operating system and allows the user to access system performance data. The simplest way to access the Performance Monitor is from the Windows Start menu, by selecting Start => Settings => Control Panel => Administrative Tools => Performance.

Figure 1 shows the initial performance monitor window.


Figure 1. The performance monitor
The performance monitor

To begin using the Performance Monitor:

  1. Select the Add button (+) on the tool bar to open the "Add Counters" dialog (Figure 2).
    Figure 2. The performance object and its counters
    The performance object and its counters
  2. Specify the following values:
    • Performance object: essentially represents a particular area of the system to be monitored, such as the processor, memory, or process(es).
    • Counter: the actual piece of data that you wish to look at; for example, if you choose a Process object, then a counter could be %Processor Time, Elapsed Time, Handle Count, etc.
    • Instance: the name of the specific area to be monitored, only available on certain counters; an instance is particularly applicable to the Process counter, as shown in Figure 3.

    Figure 3. The counters and their instances
    The counters and their instances
  3. When all options have been selected and you have decided what is to be monitored, instruct the Performance Monitor to begin monitoring this data by selecting the ADD button (Figure 3). Monitoring of this counter will then begin immediately and the data will be displayed in the monitor window.
  4. Repeat Steps 2 and 3 to add additional counters, if necessary. When all counters have been added, select Close on the "Add Counters" dialog to return control to the Performance Monitor.
  5. The performance data collected will be displayed in the monitor window, but can be presented in either a graph, histogram or a report, by selecting the corresponding button on the Performance Monitor tool bar. (Figure 4 shows output in the report format.) From this data, intuitive conclusions and constructive decisions can be made on the application's and the system's performance, such as those suggested in the above example.
    Figure 4. Data output from the Performance Monitor in report format
    Data output from the performance monitor in report format

The Performance Monitor tool is useful for obtaining the kind of data needed for performance analysis, but there are some serious drawbacks:

  • The user needs to be sitting at the workstation in order to operate the monitor panels, and many real world systems operate unattended.
  • A logging function is available to collect performance data, allowing the user to specify the counters to be monitored and output the data to a file. This is a valuable feature. However, monitoring can only be triggered by date and time scheduling, and not by any other system characteristics or behaviors.
  • Other than specifying the actual format of the log output, (e.g. comma separated file (CSV) or binary), there are no other options available to customize the output to suit a specific user's unique needs.

Practical options to offset these constraints would yield a more desirable solution.


Custom applications

The programming interface used by the Windows Performance Monitor is exposed to the user in the form of the Performance Data Helper library (PDH.dll), along with a series of C/C++ header files, namely pdh.h, pdhmsg.h and winperf.h, which are shipped along with the operating system. The availability of this library allows users to write custom applications in C/C++ that connect to and gather data from the system performance counters, and then output the data in a more usable form.

Any of the data available from the Performance Monitor can also be made available through a custom application. The advantages of well-written custom performance monitoring applications directly address the Performance Monitor drawbacks listed above: counters can be kicked off at any time, from within existing automated components, run for a set period of time, then exit leaving the results in a readable file for later analysis.

To create and use an effective performance monitoring application:

  1. Decide which counters (i.e. processes, etc.) you wish to monitor.
  2. Decide which instances you wish to apply these counters to, if applicable.
  3. Decide upon application design issues, such as the duration of the monitoring and monitoring intervals.
  4. Write the code to do the monitoring, incorporating the following basic steps:
  5. Run the application.
  6. Analyze the data.

It's often difficult to decide which counters need to be monitored and which instances to apply them to. In one recent actual case, it became apparent while investigating a system's performance during a test run that not enough data was being collected to allow a detailed analysis of the running applications. It was thought that process level performance data was needed, rather than just the system-wide data that had been previously captured. By selecting the Process performance object in the "Add Counters" dialog, a comprehensive list of process level counters are displayed (Figure 5).


Figure 5. Available counters on selection of the Process Performance object
Available counters on selection of the Process performance object

In this case, the testers initially thought that CPU, Memory and I/O processes were what they wanted to monitor. However, after reviewing the counter explanations, they decided the best counters for their purposes would be:

  • % Processor Time
  • Working Set
  • I/O Write Bytes/sec.

The lesson is this: If you're not sure what data a specific counter collects, highlight the counter and select EXPLAIN to display a description (Figure 6). This information can help you decide which of the available counters will meet your requirements.


Figure 6. Explain text window with a description of a counter
Explain text window with a description of a counter

The purpose of the individual instances related to each counter are generally self-explanatory, which helps determine the measurement components.

With the counter and process decisions made, the next decisions to be made are:

  • the duration of the monitoring
  • at what intervals the actual measurements should be taken
  • how to specify which counters to monitor (hard code into the application or read in dynamically from a file).

These elements are unique to the monitoring requirements of the specific situation, and critical to creating a successful application. How these elements are represented in custom code is illustrated in the following section.


Code examples

This section contains several coding examples in C, illustrating a number of key principles and functions needed to build an effective custom performance monitoring application.

To start, it's necessary to include a number of header files at the start of your custom application that provide access to the required functions:

#include <windows.h>, #include <pdh.h>, #include <PDHMSG.H>, #include <WINPERF.H>

Now, let's look at each of the four basic parts of the custom program that we need to include when using the PDH library to obtain performance data. Each of these parts, and the function queries that will perform each task, are detailed below.

1. Create a query

A query is a collection of counters, created in your custom code, to manage the collection of performance data. Queries are used within PDH function calls to update the counters it manages, thereby obtaining performance data. The creation of a query returns a handle, which is used to allow access to the query in PDH functions.

PdhOpenQuery:
Creates a new query. Requires two input parameters, returns one parameter and a return code. Sample use:

if( (pdhStatus = PdhOpenQuery( pszDataSource, dwUserData, &hQuery)) == ERROR_SUCCESS)

Input Parameters:
pszDataSource String which refers to the log file from which to read. For real time data capture, specify a value of NULL.
dwUserData User-defined value to be associated with this query. This could be a unique value to identify the query, or, if no value is assigned, then a C++ WORD/DWORD datatype of 0 would be sufficient.
Returned Parameters:
hQuery Points to the handle of the created query. This pointer is required in any subsequent PDH function call.

If opening the query is successful, it will return the value ERROR_SUCCESS; otherwise an error code will be returned.

2. Associate counters with the query

PdhAddCounter:
Adds a counter to a query. Requires three input parameters, returns one parameter and a return code. Sample use:

if( (pdhStatus = PdhAddCounter( hQuery, szFullCounterPath, dwUserData, &phCounter)) != ERROR_SUCCESS)

Input Parameters:
hQuery Handle to the query you wish this counter to be added to.
szFullCounterPath Pointer to the path of the counter. Before passing this parameter, it must first be initialized with the details of the counter by creating a PDH_COUNTER_PATH_ELEMENTS data structure with the names of the machine, object, counter and instance, as follows:

PDH_COUNTER_PATH_ELEMENTS spdhCPE;
spdhCPE.szMachineName = "Some Value";
spdhCPE.szObjectName = "Some Value";
spdhCPE.szInstanceName = "Some Value";
spdhCPE.szCounterName = "Some Value";

and passing this data structure into the PdhMakeCounterPath function call, below.
dwUserData User-defined value to be associated with this query. This could be a unique value to identify the query, or, if no value is assigned, then a C++ WORD/DWORD datatype of 0 would be sufficient.
Returned Parameters:
phCounter Points to the handle of the created counter.

This function needs to be called for every counter which is to be added to the query.

PdhMakeCounterPath:
Creates a full path to the counter using the members of the PDH_COUNTER_PATH_ELEMENTS structure. Requires three input parameters, returns two parameters and a return code. Sample use:

pdhStatus = PdhMakeCounterPath( &spdhCPE, szFullPathBuffer, &dwpcchBufferSize, dwFlags);

Input Parameters:
spdhCPE Pointer to the PDH_COUNTER_PATH_ELEMENTS.
dwpcchBufferSize If the function call is successful, the value of this parameter is set to the size of the available buffer. Otherwise, it is set to the required buffer size*. Therefore, this parameter may be either input or output.

(* It may be necessary to analyze the value of this parameter and extend the buffer accordingly.)
dwFlags Specifies the format of the counter values. This could be:
  • PDH_PATH_WBEM_RESULT: returns the result in WMI format
  • PDH_PATH_WBEM_INPUT: assumes the input value is in WMI format
  • 0: returns the result as a list of registry path items.
Returned Parameters:
szFullPathBuffer Pointer to the full path of the counter which is to be used in the PdhAddCounter function.
dwpcchBufferSize See the dwpcchBufferSize Input Parameter, above.

3. Collect and process the data

Collecting:

Now that a query has been set up and the counters have been added, it is possible to begin gathering performance data. This can be done by either collecting the raw data and processing it manually, or by using the built-in PDH logging functions.

For manual processing:

PdhCollectQueryData:
Retrieves the raw data, real time, for all the counters specified in the query at the time of the call. Requires one input parameters, returns a return code. Sample use:

if( (pdhStatus = PdhCollectQueryData( hQuery)) != ERROR_SUCCESS)

Input Parameters:
hQuery Handle to the query from which you wish to gather data.

When using this function to gather data, some of the raw data collected will not be useful without first processing it in some way. For example, if a counter is monitoring something over time (e.g. I/O data bytes per second) the raw value returned from PdhCollectQueryData would simply be a running total of data bytes. To get the actual data bytes per second, you would have to take two samples (to get a start value and an end value), and divide the difference by the duration between the samples. (This processing can be done for you using the PdhGetFormattedCounterValue function.)

For logging method:

It is also possible to obtain the same data as above and write it directly to a log file. To do this, simply open a log for writing and then update the log each time data is collected. The two functions needed to do this are PdhOpenLog and PdhUpdateLog:

PdhOpenLog:
Opens a log for writing. Requires six input parameters, returns one parameter and a return code. Sample use:

pdhStatus = PdhOpenLog (szLogFileName, dwAccessFalgs, lpdwLogType, hQuery, dwMaxSize, szUserText, pdhLog)

Input Parameters:
szLogFileName String representing the name/path of the log file to be created.
dwAccessFlags Access level required to the log file. These values can be:
  • PDH_LOG_READ_ACCESS (read)
  • PDH_LOG_WRITE_ACCESS (write)
  • PDH_LOG_UPDATE_ACCESS (existing log opened for write).
The read, write and update flags need to be combined with one of the following using an OR operator:
  • PDH_LOG_CREATE_NEW (new log file created)
  • PDH_LOG_CREATE_ALWAYS (erase any existing log with same name)
  • PDH_LOG_OPEN_EXISTING (open an existing log file, if it doesn't exist create a new one)
  • PDH_LOG_OPEN_ALWAYS (open an existing log file or new log file is created).
lpdwLogType Format of the log to be opened. This can be:
  • PDH_LOG_TYPE_UNDEFINED (undefined)
  • PDH_LOG_TYPE_CSV (log has column headers followed by data values; values are separated by double quotes and commas)
  • PDH_LOG_TYPE_SQL (data in SQL format)
  • PDH_LOG_TYPE_TSV (log has column headers followed by data values; values are separated by double quotes and tabs)
  • PDH_LOG_TYPE_BINARY (binary format)
  • PDH_LOG_TYPE_PERFMON (format used by the performance monitor tool to store data, read only; essentially the same as binary, but less efficient space-wise).
hQuery Handle to the query on which to open the log.
dwMaxSize Maximum size of the log file.
szUserText String used to describe the contents of the log file.
Returned Parameters:
pdhLog Handle to the log file.
PdhUpdateLog:
Updates the log. Requires two input parameters, returns a return code. Sample use:

pdhStatus = PdhUpdateLog (hLog, dwText))

Input Parameters:
hLog Handle to the log file to be updated.
dwText Specifies any additional text to be added to the log file, such as comments the user wants to note, in addition to the performance data.

Once the log is no longer required, it should be closed using the PdhCloseLog function:

PdhCloseLog:
Closes the log. Requires two input parameters, returns a return code. Sample use: pdhStatus = PdhCloseLog (hLog, dwFlag)
Input Parameters:
hLog Handle to the log file to be closed.
dwFlag Can be set to PDH_FLAGS_CLOSE_QUERY, in which case the query is closed at the same time as the log.

Processing:

To process the raw data retrieved from the PdhCollectQueryData call, it is necessary to call the PdhGetFormattedDataValue. This is particularly important when monitoring a counter over time, since the PdhCollectQueryData function returns a running total, rather than data per second.

PdhGetFormattedCounterValue:
Retrieves formatted data. Requires two input parameters, returns two parameters and a return code. Sample use:

pdhStatus = PdhGetFormattedCounterValue( hCounter, dwFormat, &dwValue, pdhValue);

This function takes the sample returned by the last call to PdhCollectQueryData to perform its computations. When dealing with a counter over time, the function takes the data from the previous two calls to PdhCollectQueryData.
Input Parameters:
hCounter Handle to the counter whose value is to be formatted.
dwFormat Specifies the format in which to return the data:
  • PDH_FMT_DOUBLE (double precision floating point)
  • PDH_FMT_LARGE (64 bit Integer)
  • PDH_FMT_LONG (long Integer).
Returned Parameters:
&dwValue Pointer that receives the type of the counter, such as text or numeric (optional).
pdhValue Pointer to a formatted counter data structure containing the counter value.

When using the logging method, it is possible to process the data in several ways, depending on the format specified. For example, if the data was written in CSV format, the log file could be imported into a spreadsheet. How the log is processed is entirely up to the user.

4. Close the query

When the query is redundant, in that all the data needed has been captured, close the query by calling the PdhCloseQuery function. (When using logs, PdhCloseLog can also close the Query.)

PdhCloseQuery:
Closes the query. Requires one input parameters, returns a return code. Sample use:

pdhStatus = PdhCloseQuery (hQuery)

Input Parameters:
hQuery Handle of the query to be closed.

The functions outlined above represent a sample of those available in the PDH library, and have been included to illustrate how custom applications can be constructed. A full list of the available functions is available from the Microsoft Web site.

Sample application

Below is sample code based on the examples discussed in this article. This code illustrates a very simple monitoring application where all values are hard coded, but is intended to give you a starting point to write more complex applications. The resulting output that would be written to the log is listed following the code.

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <pdh.h>
#include <pdhmsg.h>

int __cdecl _tmain (void)
{ HLOG phLog;
   PDH_STATUS          pdhStatus;
   HCOUNTER            phCounter;
   DWORD               count;
   char                szFileName[24];
   WORD                dwUserData = 0;
   HQUERY              hQuery = NULL;
   DWORD               logType = PDH_LOG_TYPE_CSV;
   CHAR                szCounterPath[45] = TEXT("\\Process(calc)\\% Processor Time");
   Strcpy              ( szFileName, "QuickMonitor.log");

// Open a query.
   (pdhStatus = PdhOpenQuery( NULL, 0, &hQuery));


// Add a counter.
   pdhStatus = PdhAddCounter( hQuery, szCounterPath,dwUserData,&phCounter) ;


// Open the log file for write access.
   pdhStatus = PdhOpenLog (szFileName, PDH_LOG_WRITE_ACCESS | PDH_LOG_CREATE_ALWAYS , 
   &logType, hQuery, 0, NULL, &phLog);

// Capture 10 samples and write them to the log.
   for (count = 0; count <= 10; count++) {
       pdhStatus = PdhUpdateLog (phLog, TEXT("Some Text."));
       Sleep(1000); // Sleep for 1 seconds between samples
   }
// Close the log and the Query
   pdhStatus = PdhCloseLog (phLog, PDH_FLAGS_CLOSE_QUERY);

   return 0;
}            
            

After building the above code into an exe file and running it from a command line, a log file, called QuickMonitor.log is created, and will contain data similar to the following:

"(PDH-CSV 4.0) (GMT Daylight Time)(-60)","\Process(calc)\% Processor Time"
"09/07/2003 11:21:25.367","5.06732874742129e-008"
"09/07/2003 11:21:26.398","0.98039215686274506"
"09/07/2003 11:21:27.430","0"
"09/07/2003 11:21:28.461","0.97087378640776689"
"09/07/2003 11:21:29.503","3.8461538461538463"
"09/07/2003 11:21:30.554","1.9047619047619049"
"09/07/2003 11:21:31.766","0.82644628099173556"
"09/07/2003 11:21:32.808","6.7307692307692308"
"09/07/2003 11:21:33.859","6.666666666666667"
"09/07/2003 11:21:34.891","5.825242718446602"
"09/07/2003 11:21:35.922","3.8834951456310676"

Though very basic and out of context, the data in this example shows that the calc process used between 0 and 6.7% of the machine's CPU.

The data above is presented in CSV format, and can therefore be imported into a spreadsheet for easier analysis. However, the most practical format for the data collected will depend on the unique combination of the actual problem scenario, the options selected, and the users performing the analysis.


Conclusion

Hopefully, the information and coding examples presented in this article have offered a glimpse of the possibilities a custom performance monitoring application can provide when coupled with the functions available with the Performance Data Helper library. Not only could such an application assist with the diagnosis of a performance problem, but it could also be used to gather data for profiling and establishing what might be considered normal behavior within a system. Whatever the end result, this article should make a good starting point for those who wish to pursue custom coding for performance data collection and analysis for Windows-based applications.


Resources

About the author

Kevin Braithwaite is a Performance Specialist with the WebSphere Business Integration Message Broker performance team in IBM Hursley. Kevin works with development in evaluating new releases of WebSphere Business Integration Message Broker, and with customers to provide consultancy on design, configuration and tuning issues relating to this product. You can contact Kevin at braithwa@uk.ibm.com.

Comments



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere
ArticleID=14224
ArticleTitle=IBM WebSphere Developer Technical Journal: Custom Performance Analysis using the Microsoft Performance Data Helper
publish-date=10142003
author1-email=braithwa@uk.ibm.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Special offers