This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


December 1999

Microsoft Systems Journal Homepage

Design Your Application to Manage Performance Data Logs Using the PDH Library

Gary Peluso

For the most part, performance data log files include information that admin­istrators of Windows NT-based systems can use to analyze the general health of a system. However, the developers I talk to also use performance data to reveal potential bugs in their applications.

This article assumes you're familiar with Performance Monitor and PDH.DLL

Code for this article: PDH.exe (26KB)

Gary Peluso lives in Redmond, Washington, with his wife and two cats. When not supporting his customers, he enjoys working with wood and building furniture.


The Performance Data Helper (PDH) Library is a DLL that provides high-level functions for reading performance counters in Windows NT®. If you've never heard of PDH.DLL before, you should probably first look at Matt Pietrek's Under the Hood columns in the March and May 1998 issues of MSJ. Matt introduced PDH there, including discussions of queries, counters, counter paths, and other essentials that you need to understand when working with performance data logs. This article will focus more on introducing some new functions in PDH that manage (read and write) performance data log files.
      For the most part, performance data log files include information that administrators of Windows NT-based systems can use to analyze the general health of a system. However, the developers I talk to also use performance data to reveal potential application bugs. For example, you might use performance data to watch the Handle Count counter of your app's Process object to determine whether it's leaking handles. The ability to create and, better yet, read and analyze performance data logs further facilitates this problem solving. You do not need to go to the customer site to look at the problem—just tell them to create a log file. You could also create a custom tool that logs exactly what you want automatically, although the Performance Monitor in Windows NT does let you use settings files. PDH.DLL gives you these logging capabilities, but the real advantage comes when you can open and read a log file.
      Consider managing a large set of server machines. Say your budget for RAM does not allow upgrading all the machines. Perhaps you need to determine which five machines out of 300 have the greatest need for additional RAM. How would you go about doing this? A clever way would be to collect performance data from them for a 24-hour time frame, maybe over the span of a regular workday. But then what? Would you open each of the 300 log files in Performance Monitor? That could be a very time-consuming task. Instead, it would be helpful to have a tool that can scan through the set of log files and pinpoint which five machines produced the most page faults. PDH can be used to create such a tool.
      Many developers use the HKEY_PERFORMANCE_ DATA registry key and parse through the huge PERF_DATA_BLOCK that is returned to get the performance goodies. This is difficult and error-prone, so developers are turning to PDH.DLL because it makes collecting the performance data easier. Sure, there are more functions to call to actually get the data into your address space. But PDH makes it far easier to find the data you want and to perform calculations based on performance counter types. There is no need to implement complicated functions to calculate the raw data with PDH. Reading a performance data log from Performance Monitor on Windows NT without PDH is just about impossible. The only information you'll find in the Platform SDK is the perfmon source code, which is actually the source for the version of perfmon that shipped with Windows NT 3.5. I have never spoken to a developer who has reverse-engineered the performance data log. With PDH, there is no need to try.
      When I first investigated this new performance data log functionality in PDH, it was not as easy as you would think. At first glance, there are functions that seem intuitive to use, but they may not get you what you are looking for. I tried opening a perfmon-type log using PdhOpenLog—a fairly smart thing to try, right? It turns out that you simply cannot get to the logged performance data this way. To open a log file for reading, you use the less intuitive PdhOpenQuery. After looking at the examples, you'll see that reading data from a log file is not much different from querying live performance data.
      The new PDH log file format (which is the format for Windows® 2000) is much more space-efficient. If you have done much logging with the old Performance Monitor in Windows NT 4.0, you may have noticed it takes a lot of disk space to log only a few counters. With the new Performance Monitor in Windows 2000, you can actually log more performance data without taking up as much disk space. To find the Performance Monitor on Windows 2000, open Control Panel, then open the Administrative Tools folder. This folder contains the Performance icon, which you use to start the Performance Monitor. The Performance Monitor tool is actually a Microsoft® Management Console (MMC) snap-in.
      I'll first describe the performance data log by comparing it to a typical database table. Then I'll cover each type of log file operation (open, read, write, close, and so on) and which PDH functions are used for each. I'll also show how these functions fit together to perform three different tasks on log files: dumping a log file, writing current activity to a log file, and transferring data from one log file to another. Finally, I'll describe two working examples—including using PDH to dump a log file and move data from one log file to another—along with describing the little nuances I discovered along the way.

Performance Data Log Files

      I want to briefly describe what a performance data log looks like in terms of a database table, which should help you understand what some of the PDH functions are doing when working with logs. A typical database table contains records (rows) and fields (columns). In the same way, PDH logs are arranged with data samples (records) and counters (fields). Each record represents one sample of performance data. A log file can contain performance data for one or more counters; each counter has data in a field for each sample. The time stamp for each sample is also stored in the log file as a separate field. Figure 1 shows how a CSV (comma-separated value) log resembles the database table concept.

Figure 1  A CSV-format Performance Data Log File
      Figure 1 A CSV-format Performance Data Log File

      There are four different performance data log file types that PDH supports (see Figure 2). One is the log file from the Performance Monitor in Windows NT 4.0. PDH refers to this type as PDH_LOG_ TYPE_PERFMON. Another is the PDH binary format, which is referred to as PDH_LOG_TYPE_BINARY. Two others are text-delimited files in CSV and TSV (tab-separated value) formats, which are referred to as PDH_LOG_ TYPE_CSV and PDH_LOG_ TYPE_TSV, respectively. Two additional log types, PDH_LOG_TYPE_TRACE_ KERNEL and PDH_LOG_ TYPE_TRACE_GENERIC, refer to logs created through Windows Management Interface (WMI) and the new Trace Event API. However, these topics are beyond the scope of this article.
      Since the beginning of Windows NT, Performance Monitor has had the ability to create and open performance data logs. PDH can only open and read perfmon-type log files; it cannot create or write them. You would not want to anyway since the perfmon log file format is quite inefficient. The perfmon-type log organizes the data with an entire PERF_DATA_BLOCK for each data sample, so at the minimum it stores entire performance data objects in the log file. For example, suppose you want to log the Private Bytes counter of your process's instance of the Process object. The Performance Monitor from Windows NT 4.0 would only allow you to specify the Process object, but not drill down to the Private Bytes counter and select your application instance. You would get the extra baggage of all of the other counters for all instances.
      For a more efficient log file format, consider the PDH_ LOG_TYPE_BINARY. On Windows 2000, the Performance Monitor tool logs performance data in a binary format to a file with a .BLG extension.
      The CSV and TSV formats are text files. For the most part they are very similar to files produced by spreadsheet or database report programs. The first column header is information that PDH uses to get the type (CSV or TSV) and version. The rest of the information is what you'd expect, including the first line being the header that describes the counter names and the subsequent lines holding the actual counter data.

Opening a Log File

      Two different functions exported by PDH will open a log file. PdhOpenLog includes parameters to specify the file name, access to open the file, and a flag. The flag will be set either by PDH to the type of PDH log that is being opened or to specify what type of log PDH is to create. For an application, PdhOpenLog is only used to create and write a performance data log, but not to read a log. The PdhReadRawLogRecord function will read the raw log records from an existing log, but the log records returned are in a structure with a proprietary format that is used internally by PDH.
      The other function used to open a log file is PdhOpenQuery. Historically, this function has been used to set up a query to get live performance data (also called current activity). But if the first parameter of PdhOpenQuery specifies a file name, PDH uses PdhOpenLog internally to open the log file for reading.

Reading a Log File

      You could use PdhOpenLog and PdhReadRawLogRecord to read the log file, but the raw record format is proprietary. When you use PdhOpenQuery to open the log, the corresponding read function is PdhCollectQueryData. Think of the database table concept for a moment. Each time PdhCollectQueryData is called, an internal record pointer advances to the next record in the table. When PdhOpenQuery first opens a log file, the record pointer does not point to a data sample yet, so you must call PdhCollectQueryData at least once. When you call this function and there are no more records, PdhCollectQueryData fails. That's how you can determine when you have reached the end of the log file. There is no function that will reverse the record pointer. However, I cannot imagine a reason to do so.
      Before using PdhCollectQueryData, you must first add counters to the query with PdhAddCounter. The only counters that can be added to a query associated with a log file are counters already present in the log file.
      Although there is a function to read raw records, there is no corresponding function to write a raw record. The only function that outputs log data to a log file is PdhUpdateLog. It is not readily apparent how this works since PdhUpdateLog operates on an hLog opened by PdhOpenLog. There is no parameter in PdhUpdateLog to include the data to write. So where does the data come from?
      Looking at the PdhOpenLog parameters, you'll see hQuery. The hQuery you pass here is returned by PdhOpenQuery to open the data source. This is what links the data source to the output log file when PdhUpdateLog is called. I'll have more to say about writing to a log file later.

Viewing Log File Data

      So far I've talked about opening and reading a log file. PdhCollectQueryData merely reads records in a log file. If you call PdhCollectQueryData repeatedly, you'll only be advancing a record pointer. To actually view the data you must call PdhGetRawCounterValue or PdhGetFormattedCounterValue.
      To get a formatted value, use PdhGetFormattedCounterValue. PDH will use a default scale for the counter to present the value in a PDH_FMT_COUNTERVALUE structure. However, you can call PdhSetCounterScaleFactor to adjust the scale for the data value.
      Just as with viewing current performance activity, PdhGetFormattedCounterValue does the counter calculation for you in the appropriate presentable form according to the type of counter. For example, the value will be a percentage for % Processor Time, and it will be a count for Private Bytes. But the PDH_FMT_COUNTERVALUE structure does not include the time stamp telling you when the data was collected. To get this information, you must call PdhGetRawCounterValue to fill in a PDH_ RAW_COUNTER structure, which holds the time stamp (in file time format) for the data sample.
      There are two functions that will close the log file that corresponds to how the log file was opened, either by PdhOpenLog or PdhOpenQuery. If the log file was opened with PdhOpenLog, you should use PdhCloseLog; if the log file was opened with PdhOpenQuery, call PdhCloseQuery. Of course, this makes sense because you would not want to mix the hLog with the hQuery.

Figure 3 Selecting Counters
      Figure 3 Selecting Counters

      PDH has two functions that provide dialog boxes for the user: PdhBrowseCounters and PdhSelectDataSource. PdhBrowseCounters is the familiar dialog that allows users to select one or more counters. When used with an hQuery referring to a log file, the list of objects, counters, and instances shows only those found in the log file. Also, there may be counters from one or more machines in the log file; the PdhBrowseCounters function provides the objects associated with each machine. Figure 3 shows a dialog for browsing counters with counters included from two machines stored in the log file.
Figure 4 The Data Source Dialog
      Figure 4 The Data Source Dialog
      PdhSelectDataSource is a new function that lets the user type in a log file name or browse the system using the common open file dialog. PdhSelectDataSource returns the name of the file selected by the user. Figure 4 shows the Data Source dialog.

Manipulating Log Files

      There are typically three management tasks you can accomplish with performance data logs using PDH. One is to read a log and use the data in some way, such as simply displaying it to the user in a graph or text output. The second task is to write current performance activity to a log file. The third task is to read a log file and transfer the information to another log file. The source and destination log files can have different formats. This may be useful to get some counter data from a perfmon-type log and transfer that data to a CSV-type log.
      PDH can be used easily in an application—with some exceptions. An application for Windows NT or Windows 2000 may be written using MFC. Some of the log file activities may be in response to menu selections, so PDH follows the rules of the event-driven programming environment. In some cases, the order in which the API is called is critical. So to give you an idea of how to do these log file tasks, I'll lay them out as if I were writing a very simple, iterative console app.
      A very common task is to read a log file and manipulate its contents. Perhaps you want to write an application that looks in a log file and finds when processor utilization hits 100 percent, or maybe you need to detect when a process's Private Bytes begin climbing.
      When reading a log, the general format of the program is:

  • PdhOpenQuery(szDataSource,&hQuery)—the szDataSource can come from PdhSelectDataSource
  • PdhAddCounter(hQuery, szCounterPath, &hCounter)—the szCounterPath can come from PdhBrowseCounters or PdhMakeCounterPath
  • Loop through the next three steps for each data sample you want, or for all samples in the log
  • PdhCollectQueryData(hQuery)
  • PdhGetRawCounterValue and/or PdhGetFormattedCounterValue
  • Manipulate the data—could be output, comparisons, or whatever
  • PdhCloseQuery(hQuery)
      These steps are very similar to what you do to collect live performance data. The only difference is that you use a log file name with PdhOpenQuery. Another minor difference might be how frequently your loop iterates. For live data you typically call PdhCollectQueryData once per second or once every five seconds, depending on how much data you need. But for log files you can rip through the log as fast as the processor can go.

Lossing Current Activity

      When writing a log file you will be using some of the new functions exported from PDH, specifically PdhOpenLog, PdhCloseLog, PdhUpdateLog, and PdhUpdateLogFileCatalog. These functions are not used alone, but augment the task of getting performance data from current activity.
      When writing current activity to a log file, the general format of a program is:

  • PdhOpenQuery(NULL, &hQuery)
  • PdhAddCounter(hQuery, szCounterPath, &hCounter)—the szCounterPath can come from either PdhBrowseCounters or PdhMakeCounterPath
  • PdhOpenLog(szLogFileName, dwAccess, &dwType, hQuery, dwMaxsize, szCaption, &hLog)
  • Loop through the next two steps for each data sample you want to log
  • PdhUpdateLog(hLog)
  • Sleep(dwInterval)
  • PdhCloseLog(hLog)
  • PdhCloseQuery(hQuery)
      When using PdhOpenLog, it is important to pass the hQuery parameter. This parameter will associate data collected by the query for output to the log file. Also, it is important to call PdhOpenLog after you use PdhAddCounter. When I called PdhOpenLog first, PdhUpdateLog did not output any data—the output log file just had time stamps.
      Again, this is similar to monitoring live data. Aside from the new PDH functions, the differences here are that PdhUpdateLog replaces PdhCollectQueryData, and you don't need to call PdhGetRawCounterValue or PdhGetFormattedCounterValue. You specify the source of information for PdhUpdateLog when you pass the hQuery to PdhOpenLog.
      When creating a PDH binary format log, you can optionally call PdhUpdateLogFileCatalog. This will write an index portion, called a catalog, in the log file so that PDH can quickly reference the names of the objects, counters, and instances when the output log is subsequently read. This is important for the speed of PdhAddCounter and other PDH functions that look up the names of these items if there are many different counters in the log file. PdhUpdateLogFileCatalog can be called before or after you finish writing counter data to the log file. Note, however, that calling PdhUpdateLogFileCatalog is only valid when the hLog is opened with PdhOpenLog using the PDH_LOG_ TYPE_BINARY type.

Transferring Log Data

      Using what you know now about PDH, I bet you can figure how to take a log file produced by the Performance Monitor in Windows NT 4.0 and create a CSV file using text file output. There is an easy way to do this using the techniques I just described.
      Remember that when you specify an hQuery to PdhOpenLog you are saying, "read information from this source" when you call PdhUpdateLog. When an hQuery is opened using PdhOpenQuery to read current activity, then PdhUpdateLog, using that hQuery, is logging current activity. But when you call PdhOpenQuery and specify the source of one log file, PdhUpdateLog will read the data from that log and write it to the log opened by PdhOpenLog. When transferring data from one log to another, there is no need to call Sleep between PdhUpdateLog calls. The data transfer can happen without delay.
      I find a couple of good uses for this. One is, as I mentioned earlier, to quickly convert a perfmon-type log to a CSV-type file. Another is if you want a log file that contains only a few of the many counters that a source log contains. Also, you can quickly trim down a log file to include only a subrange of data samples.
      There is a problem I noticed when testing this with the current version of PDH.DLL installed from the May 1999 Platform SDK. While the counter data transferred without problem, PDH did not transfer the time stamps. Instead, it wrote the current system time when PdhUpdateLog was called to write the data to the output log file. This, of course, is an error, especially for time-sensitive counters such as % Processor Time. This only occurs when the output log file is a CSV or TSV-type log. The developer of PDH assured me this will be corrected soon.

A Couple of Caveats

      Before I describe the samples included with this article, I want to discuss a couple of issues I ran into as I was developing sample code with this new PDH.DLL. I hope this will save you time when you begin using the new PDH.
      One problem occurred while installing the Platform SDK. At the time of writing this article I had installed the May 1999 Platform SDK onto Windows NT 4.0 Service Pack 4. The more recent Platform SDK is compressed in CAB files. I discovered that the setup program did not expand the compressed PDH.DL_ file onto my hard disk; rather, it just renamed the file to PDH.DLL. At first it seemed as if the PDH.DLL file was corrupted. But I noticed it was compressed when I opened it in a binary editor to examine the corruption. The expand.exe utility on Windows NT 4.0 did not understand this compression format, so I tried expand.exe from Windows 2000, which successfully expanded the PDH.DLL file.
      The other problem occurred when building with the import library for PDH. At work I currently use Microsoft Visual C++® 6.0, but at home I haven't upgraded from version 5.0. The PDH.LIB import library was created with the new build tools, so I couldn't get around a link error when working on this sample at home. Fortunately, I learned that installing Service Pack 3 for Visual Studio® 97 corrects this problem. After installing Service Pack 3 on my home machine, I had no problems building my applications with the new PDH.LIB import library.
      My sample apps demonstrate the logging capabilities of PDH.DLL. I have implemented them to be easily compiled as either ANSI or Unicode. However, I've had a better experience compiling as Unicode. For example, when I was building ANSI and I misused PdhEnumObjects by specifying NULL for the machine name, this function would present an unhandled Access Violation exception. But when I compiled with Unicode, the function just failed.
      PDH.DLL is used by the Performance Monitor snap-in in Windows 2000, but is built in Unicode. So many of the critical symptoms of passing bad parameters in ANSI versions of the functions are not worked out.

DumpLog

      The DumpLog sample (partially shown in Figure 5) is actually fairly rudimentary, but can be extended easily to accomplish other tasks. The goal for this sample was to demonstrate how to read the log file data.
      DumpLog is a command-line tool with a very simple interface that accomplishes two primary tasks. If you provide only one command-line argument, the log file name, it will simply enumerate the object, counter, and instance names, and then read the log file time range to report when the log file started and stopped. If you provide a log file name and the name of an object, counter, and optionally an instance, then the log file is read and the counter data for the specified performance counter is dumped out to the standard output, along with the time stamp for each data sample.
      The main routine of this sample simply splits off the two major tasks of enumerating the objects, counters, and instances of a log and dumping the data for a performance counter. I have a utility function, get_cmd_args, that will read and interpret the user input to know what the user wants to do based on the command-line arguments, just a log file name or including object, counter, and instance names. From there it calls either enum_perflog_items or perf_log_dump.
      Let's first look at enum_perflog_items. This routine primarily shows the use of PdhEnumObjects and PdhEnumObjectItems. When these functions are called using a log file name, they act upon the log file to get the object, counter, and instance names. Other APIs that require a memory buffer of unknown size can be called with a NULL buffer pointer to get the size requirement. However, this method does not seem to work with these functions so I anticipated a buffer size and used static buffers.
      There is some interesting behavior when opening performance data logs of type PDH_LOG_TYPE_PERFMON. If you supply a buffer large enough to read the objects and items as index numbers, but not large enough to contain the objects and items as full names, it will return a list of indexes. If you supply a bigger buffer, then PDH will give you object and counter names as you would expect. This is not a bug—it's intended to work this way.
      For PdhEnumObjects and PdhEnumObjectItems to work, you must supply a machine name. This is different from using these functions for current activity, where if you did not supply the machine name, PDH would default to the local system. However, when working with performance data logs, these functions will fail if a machine name is not provided. PdhEnumMachines is used to get the machine name that appears in the log file. There may be cases where a log file includes performance data from multiple machines, but for the simplicity of this sample I only picked the first enumerated machine and used it for PdhEnumObjects and PdhEnumObjectItems.
      PdhEnumObjects will enumerate the performance data objects in the log file specified in the szDataSource parameter. The enumeration is returned in the form of a multistring. This means there are multiple null-terminated strings lined up consecutively in the supplied buffer, with the last string terminated by two NULL characters.
      PdhEnumObjectItems will enumerate the counters of a log file specified in szDataSource for the specified szObjectName. Of course, the performance data object with this name must be present in the log file. Some objects do not have instances (for example, the System object) so it may return a zero-length mszInstanceList parameter. I checked the ppchInstanceListLength for a nonzero value to determine if PDH filled the mszInstanceList buffer.
      The combination of using these functions in a nested loop will dump all of the items for each object found in the performance data log. In this routine, for each object enumerated I called PdhEnumObjectItems to get the counters and possible instances. I could have added a level of loop nesting to do all of these for each machine name found in the log file. This would be much more precise because you need to know what counters are from which machines to use PdhAddCounter, as you'll see in the description for perf_log_dump. But to keep this sample easier to understand and because it is not typical to have multiple machines in one log file, I left this out.
      As with enumerating objects and object items, it is essential to provide the machine name to compose a full counter path, including the machine name that PdhAddCounter will accept. So in the beginning of perf_log_dump I used PdhEnumMachines to get a valid machine name for the specified performance data log. There may be performance data logs with counter data from multiple machines, but this code assumes there is only one.
      At first glance, the overall steps are similar to collecting live data. The only difference is that I used a log file name for the szDataSource parameter of PdhOpenQuery. I then used PdhMakeCounterPath to create a nicely formatted counter path for PdhAddCounter. Since this sample dumps the data for one counter at a time, I then went on to the PdhCollectQueryData loop after adding the one counter path. This routine calls PdhCollectQueryData repeatedly until failure, which indicates that you are at the end of the log file. Another minor difference between reading log files and querying live data is that you do not need to call Sleep between calls to PdhCollectQueryData.
      After I called PdhAddCounter, I used PdhGetCounterInfo. It is not necessary to call this function to dump the counter data, but it is a useful function if your application is dealing with many hCounter handles from many PdhAddCounter calls. If you need to distinguish between many hCounters in an array of handles, then use PdhGetCounterInfo.
      While PdhCollectQueryData succeeded, I called both PdhGetRawCounterValue and PdhGetFormattedCounterValue. I used the PdhGetRawCounterValue function to get the time stamp for each data sample; PdhGetFormattedCounterValue does not return this information. Because the time stamp information is in file time format, I used FileTimeToSystemTime to get the information in a SYSTEMTIME structure. I could then easily display the hours, minutes, and seconds of the time stamp using a printf statement.

CvtLog

      Like the DumpLog sample, CvtLog (see Figure 6) is a simple Windows NT-based console application designed to illustrate the steps for using PDH to read a performance data log of one type and transfer the information to a performance log of another type. This utility can transfer information between logs of the same type so that the destination log file contains a subset of the counters from the first log. It is possible to implement a slight augmentation of this utility that allows you to give a subrange of data samples. In this case you would use PdhCollectQueryData to advance the record pointer to skip data samples, and then use PdhUpdateLog to transfer the next record to the destination performance log file. However, the CvtLog sample transfers all data samples of the selected counters.
      To use this program, provide an input log file name, an output log file name, and a parameter that specifies what type of output log you want to create (C for CSV, T for TSV, or B for BINARY). Remember that PDH does not support creating a perfmon-type log. After successfully passing arguments to the command line, CvtLog presents the browse counters dialog, including the machines, objects, counters, and instances found in the input performance log. Counters are added to the query set after you make a selection and click the Add button. You can make multiple selections and click Add to add more than one counter at a time. After clicking the OK button, CvtLog will transfer data samples from only the selected counters to the output performance log.
      The sample starts with a routine that parses the user's command-line arguments. Then, after passing the arguments, the input performance log is opened using PdhOpenQuery. Once the query is created, counters are added at the user's request via the PdhBrowseCounters dialog in the pdhBrowseCallback function. As I mentioned earlier, it's very important that the counters of interest are added to the query before PdhOpenLog is called. If you use PdhAddCounter after calling PdhOpenLog, you'll see no error messages, but no data is written to the log file.
      Because it is essential to specify the machine name in the counter path for PdhAddCounter to work on performance log files, I set the bDisableMachineSelection flag member to FALSE when initializing the PDH_BROWSE_DLG_ CONFIG structure for PdhBrowseCounters. This turns on machine selection in the browse counters dialog. If there are counters from multiple machines within the performance log, they will be listed in the dialog. When machine selection is turned on and the user selects a counter and clicks the Add button, the dialog will fill the counter path buffer with a counter path including the machine name.
      After the user closes the browse counters dialog, PdhBrowseCounters returns and the main routine continues. It then calls PdhOpenLog to create the output performance log. The create flags passed will cause this function to overwrite an existing file. Also note that the hQuery parameter of PdhOpenLog is the query handle returned by PdhOpenQuery. This is how I linked the data from the input performance log (opened by PdhOpenQuery) to the output performance log (also opened by PdhOpenQuery). Finally, the lpdwLogType parameter points to a DWORD with the log type specified by the user using the C, T, or B flag that relates to PDH_LOG_TYPE_CSV, PDH_LOG_ TYPE_TSV or PDH_LOG_TYPE_BINARY, respectively.
      Once the input performance log is opened, the counters are added to the query, and the output performance log is opened, CvtLog continues to do the main work of reading the data samples from the input log and writing the data samples to the output log. The PdhUpdateLog function handles both operations within the same function. Ideally you would call PdhUpdateLog repeatedly until it reaches the end of the input performance log, at which point it will fail. However, I ran into cases where the first time I called PdhUpdateLog it failed with PDH_STATUS_INVALID_DATA, although I was sure there was more information in the input performance log.
      I discovered that after I called PdhUpdateLog a few times, even though it returned a failure status, it then succeeded on subsequent calls. So to make an algorithm that works, I used PdhGetDataSourceTimeRange. This call returns information about the time range for the performance data log and will fill in a PDH_TIME_INFO structure that includes the SampleCount member. I used this to implement a for loop to iterate through all of the data samples of the input performance log. For each iteration I called PdhUpdateLog. After the loop completes, the input and output log files are closed using PdhCloseQuery and PdhCloseLog, respectively.

Wrap-up

      I hope this article helped you grasp the purpose and functionality of performance data logs in PDH. Now you should be able to design your own utility or application that can manage performance data logs, including reading a log, writing live data to a log file, or transferring data from one log to another. While the logging service in Windows 2000 provides just about all you would need to schedule performance logging or to run a log without user interaction, the real power of sifting through the data comes from PDH and the tools you build with it.


For related information see:
Performance Monitoring at: http://msdn.microsoft.com/library/psdk/pdh/perfdata_0pgn.htm.
  Also check http://msdn.microsoft.com for daily updates on developer programs, resources and events.


From the December 1999 issue of Microsoft Systems Journal.