Everything about Win32 performance counters were a pain for the Win32 developer. The good news is that .NET provides managed classes that make reading and providing data for performance counters straightforward and easy. In this article, I will outline the basic architecture of performance counters and describe how .NET provides its implementation.
A performance counter is any statistical measure, such as a running count, a rate of change over time, or some other rate that you determine. The process generating the performance counters provides the raw data (instantaneous values, or if it is a custom rate, the value and the divisor), and this data is read by the performance monitor, which displays the appropriate data. The .NET process that generates the data and the performance monitor (housed in MMC) that displays it are two different processes, so this means that there must be some interprocess communication between them. In fact, it goes one step further because if you have an administrator account for another machine, you can get performance counters from that machine. In this case, the WinLogon process gathers the performance counters and communicates this to the performance monitor on your machine via RPC.
COM would have been an ideal way to implement the interprocess and data gathering mechanisms, but performance counters predate COM by several years. Instead, the Windows designers simply gave up and pushed the responsibility of the IPC to the developer. Performance counter collection works like this: A process indicates that it can generate counters by adding a value in the system registry and part of this registration includes the name of a DLL. This DLL must export functions that are used to start and stop the performance counting, and a function that is called to collect the data. The names of these functions are registered so that the performance monitor knows what function to call. A process provides one or more counters and each of these will have a unique ID, so during registration, the process must reserve the IDs that it will use.
When a user indicates to a performance monitor that they want to gather performance counters from a process, the DLL that the process registered will be loaded into the performance monitor (or WinLogon.exe, in the remote case) and the Open function will be called to initialize the DLL. Then the performance monitor will call your DLL’s Collect to get the value of one, or more, counters by passing a string containing the IDs of those counters. Thus, the DLL’s Collect function will use the chosen IPC to get the values of the counters and package the values in the (rather arcane) format required by the performance monitor. (In actual fact, performance data is read by reading a special registry key called HKEY_PERFORMANCE_DATA, but lets ignore that detail here.)
.NET allows you to provide performance data through two unmanaged DLLs and several managed classes. Lets keep the managed API details for some other time. Concentrating on the performance, when you create a .NET performance counter through the managed API, the class will add a registry entry in the following key:
The .NET process does not have to be an NT service; however, the performance counter API mandates that the entry is in this key. The managed API requires that each counter is a member of a “category” and it is the name of this category that is used for the name of the key under the Services key. This key registers the netfxperf.dll DLL for the library for all .NET performance counter categories. This DLL is unmanaged and is one of the few .NET DLLs that is located in the %systemroot%System32 folder. This is a shim DLL that locates and loads another unmanaged library called perfcounter.dll, which is in the .NET framework folder. (The shim ensures that the right version of this library is loaded in the situation when you have multiple side-by-side versions of the framework.)
The source of this file is not available (its one of the files omitted from the Shared Source CLI); however, looking through the System.Diagnostics namespace with ILDASM shows a class called PerformanceCounterManager, which implements an interface called ICollectData. This class (and an associated class called SharedPerformanceCounter) creates a file-mapped object called netfxcustomperfcounters.1.0. The ICollectData.CollectData method obtains performance data through this memory-mapped file using SharedPerformanceCounter. The SharedPerformanceCounter class is also used by the PerformanceCounter managed class that user code uses to write performance data to a counter. This class provides the “client” and “server” code to share data between two processes.
Clearly, PerformanceCounterManager is used to read performance counters from managed code and since it used the memory mapped file object—an interprocess communications mechanisms—this class looks like the sort of code that a performance monitor library would use. However, the perfcounter.dll library is unmanaged, which raises the question of how it gets access to PerformanceCounterManager. My guess is that perfcounter.dll calls ICollectData.CollectData through a .NET COM callable wrapper. This guess is further backed up by the fact that the only public member of this class is the constructor, but the class has a GUID and implements a COM interface with a GUID that, when coupled together, will provide access to the interface methods through COM.