March 1998
Download Hdmar98.exe (10KB)
Matt Pietrek is the author of Windows 95 System Programming Secrets (IDG Books, 1995). He works at NuMega Technologies Inc., and can be reached at mpietrek@tiac.com or at http://www.tiac.com/users/mpietrek. |
I described the Windows NT® performance data counters in my March and April 1996 columns. These counters, which unfortunately don't exist on Windows® 95, provide a huge volume of information about a system. You can get things like a list of all running processes, how many interrupts are happening per second, network I/O statistics, and just about anything else you can imagine.
Unfortunately, the raw interface to the performance data counters is so ugly that a career as an air traffic controller might appear less stressful. In the aforementioned columns, I provided a set of C++ classes to encapsulate some of the complexity. I ended up getting quite a bit more mail about those columns than I expected. Apparently, Windows NT performance data counters are a very important subject to a lot of people, especially those who wanted to know how to add their own performance counters. What I didn't know when I wrote my columns was that Microsoft had also developed a simpler, API-based interface to the performance data. The Microsoft APIs have been around for awhile, but somehow haven't attracted much attention. The API set is known as the Performance Data Helper (PDH), and it resides in PDH.DLL. This month, I'll cover some of the basic concepts and functionality in PDH.DLL. Before I go any further, let me address the people who want to add their own counters. PDH.DLL won't help you. There's a decent description on how to do it in the MSDN documentation for the PDH API. It involves using a utility (LODCTR.EXE), and creating a special DLL. I'm not going to talk about adding your own counter here, primarily because I haven't done so myself. Let's start with the essential information for using PDH.DLL. The documentation is found in the Platform SDK under \Windows Base Services\Windows NT Features\Performance Data Helper. There are two header files, PDH.H and PDHMSG.H, in the SDK's \INCLUDE directory. Likewise, the PDH.LIB import library is in the \LIB directory. Unlike a lot of other system-level APIs, the PDH.DLL authors realized that not everybody programs in C++. I was pleasantly surprised to see that PDH.DLL provides a set of APIs that are easily callable from Visual Basic. I know this because the documentation groups these APIs under the category "PDH Functions for Visual Basic 4.0." While I'm still a C++ and MASM diehard, I decided to experiment with PDH.DLL using its Visual Basic APIs. Besides, nothing prevents a C++ program from calling the Visual Basic APIs. In a future column, I'll cover the PDH.DLL APIs specifically designed for C and C++. There are quite a few more of them, and they provide additional power that the Visual Basic-specific APIs can't access easily. In looking over the PDH APIs for Visual Basic, I noticed a few things worth mentioning up front. First, the names of some APIs in the documentation aren't the same as the exported names in PDH.DLL. The MSDN documentation refers to PdhGetDoubleCounterValue, but the real name is PdhVbGetDoubleCounterValue. The Visual Basic-specific functions all have a "Vb" after the "Pdh" part of the name. Next, I noticed that while C++ header files and an import lib are provided for PDH.DLL, there's no .BAS module to make PDH.DLL easy to use for Visual Basic coders. I partially rectified that situation by creating MSJPDH.BAS, which I'll use in the demo program later. In MSJPDH.BAS (see Figure 1), you'll see that many of the APIs I used have "Vb" inserted in their name. Likewise, there are many constants used by PDH.DLL as flags and return values. I created Visual Basic enums for them in MSJPDH.BAS. In using the PDH APIs, there are three fundamental concepts you'll need to become comfortable with: counters, paths, and queries. A counter represents a single value that describes one detail about one thing. For instance, for each process in the system, there's a counter containing the number of threads owned by the process. The amount of committed system memory is kept in another counter. The number of interrupts per second that a given processor handles is yet a third type of counter. If it's measurable, odds are that there's a counter value for it. Counters can be for your local machine, or they can be read from other machines over a network (assuming you have the proper access rights). With all of these counters, some way of organizing and specifying exactly what counter you're after is needed. This is where paths come into play. A path completely describes a given counter. A fully specified path looks like this: |
|
The \\Machine Name part is optional. It's the name assigned to a given machine on the network. If the machine name is omitted, the local machine is assumed.
The Object Name portion bears some scrutiny. Loosely stated, objects are the top level of organization for counters. Think of an object as a category. For example, there's a Memory object, a System object, a Cache object, and a Redirector object. There are also objects that have specific instances. The Process object has multiple instances, one for each process. Likewise, the Thread object has an instance for each thread in the system. In a PDH path, the specific instance of an object is specified in parentheses after the object name. For example: \Process(Explorer) indicates the Explorer.EXE instance of a process object. The last portion of a path is the individual counter name. This counter is relative to its parent object (or object instance) specified previously in the path. Putting this all together, the path |
|
indicates the counter containing the number of threads in the Explorer process on the machine named Wheaty2.
So how do you get to the counter values? The answer lies in the third fundamental concept: queries. A query is a collection of counter paths that you lump together. With a single API call, PDH.DLL retrieves the current value of each counter in the query. You can then read the individual counter values from the query. A program can define and use multiple queries. Each query is referred to by a unique handle. When you're finished with a query, you're responsible for freeing the query (via its handle). The general sequence for using a query looks like this: allocate query, add counters to query, run query, read counter values, and free query. Now let's check out some of the APIs that make PDH.DLL do its stuff. For starters, you'll need to know the path that describes a counter you're interested in. I gave some examples above, but there are hundreds more. What's more, the potential counter paths change at runtime as processes and threads come and go. Just how do you obtain a list of all available counters at the moment? For programmers of Visual Basic, PDH.DLL provides two similar means to get counter paths. To select just one path, the PdhVbGetOneCounterPath API fills the bill. It brings up a dialog that looks like Figure 2. Use the upper-left portion to decide which machine you're interested in. (You can probably just accept the defaults in most cases.) Next, use the Object combobox to select which object you're interested in. The default is the Processor object. As you change the selected object, the Counters listbox updates to show the available counters. If the object has instances, the Instances listbox at the lower-right fills up as appropriate. After you finish selecting the appropriate counter, the complete path is written to a buffer passed to the PdhVbGetOneCounterPath API. |
Figure 2 Using PdhVbGetOneCounterPath |
To select multiple counters at the same time, the PdhVbCreateCounterPathList API brings up a similar dialog, which allows multiple counter selections. The API returns the number of counters selected. You then use that value to figure out how many times to call the PdhVbGetCounterPathFromList API. Each time you call PdhVbGetCounterPathFromList, you pass an incremented index value, starting at 1. Each successive call fills a buffer with the counter path string matching the index value.
When using either dialog, one of the parameters that must be specified is the display detail. Essentially, counters are categorized by how advanced a user you need to be to understand them. The levels are novice, advanced, expert, and wizard. When using the novice level, just a subset of the most easily understood counters are listed. Picking advanced adds some additional counters, while expert brings in even more. Selecting the wizard level means you can pick from every available counter. With the counter path in hand, you're now ready to set up a query. The PdhOpenQuery API allocates a query and fills in the third parameter (passed by reference) with the query's handle value. When you're finished with the query later on, you must release it by passing the handle to the PdhCloseQuery API. Incidentally, neither PdhOpenQuery nor PdhCloseQuery are Visual Basic-specific functions. They don't have Vb in their name, and they're documented as general PDH APIs rather than under the "PDH Functions for Visual Basic 4.0" documentation node. After obtaining the counter path and query handle, you bind the counters to the query via the PdhVbAddCounter API. The third parameter is passed ByRef, and upon return it contains another handle value. This counter handle is used later to retrieve the value of a specific counter. Unlike the query handle, you don't have to free counter handles; they're implicitly freed when you close the containing query. At this point, the query is loaded up and ready to rip. The PdhCollectQueryData API causes PDH.DLL to do its voodoo and get the values of all the counters in the query. (PdhCollectQueryData is yet another API designed for use by both Visual Basic and C++ programs.) After the query data is collected, it's time to go harvest those counter values. Compared to the C++ functions for doing this, the Visual Basic-specific API is blissfully simple. PdhVbGetDoubleCounterValue takes a counter handle and returns the counter value as a double. (In case you've forgotten, a double is an 8-byte floating point number in both Visual Basic and C++.)
The PDH_VBDemo Program
|
Figure 3 The PDH_VBDemo Window |
Since the Visual Basic timer control makes it incredibly easy to make your programs dynamic, I made the counter display update once a second. To add your own counter to this list, click on the Add Counter button to bring up the PdhVbGetOneCounterPath dialog. At the top-right of PDH_VBDemo window are four radio buttons that correspond to the various levels of counter detail geekiness. Since all MSJ readers are wizards, I made that the default.
The code to do all this is in Figure 4). Given everything it does, the code turned out to be quite small. As mentioned earlier, for some reason Microsoft doesn't provide a module with all the function declarations and constants. Figure 1) is my stab at declaring the minimal set of APIs and constants that I used.
Wrap-Up
Have a question about programming in Windows? Send it to Matt at mpietrek@tiac.com |
From the March 1998 issue of Microsoft Systems Journal.