<%@ Language=VBScript %> <% ProductProtected = 1 %> C++ Builders Developer's Journal / Writing a Performance Monitor
June 1999

Writing a performance monitor

by John M. Miano

The System Monitor utility is well known to developers on Windows 95 and Windows 98. This utility can display various system performance statistics, such as page fault rate and CPU utilization. Applications such as Norton Utilities contain similar performance monitors of their own. So, how can you incorporate performance monitoring in your own applications or custom controls? All you need to do is read the registry. When a Windows device driver loads, it can define any number of statistics that are updated in the registry.

Finding out what statistics are available

Just as the device drivers in use vary from system to system, so do the available performance statistics. The first thing your performance monitoring application will need to do is find out what statistics are available. If you run the REGEDIT program and look under the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\PerfStats\Enum, you'll find keys for each server that's generating statistics. The server keys have a value called Name associated with them that gives a translated name for the server key. The subkeys for each server represent the individual statistics. Figure A shows the REGEDIT program displaying the statistics available from KERNEL.

Figure A: This is the REGEDIT program displaying the statistics available from KERNEL.
[ Figure A ]

Each statistic key has three valuesName, Description, and Differentiatethat contain the attributes of the statistic.

Name is a short name for the statistic and Description gives a longer description. The value of Differentiate tells an application if it needs to calculate a rate from the statistic value. When the value of Differentiate is the string TRUE, the application has to calculate a rate using the previous value of the statistic using

display_value = (current_value - last_value) / time_interval
The time_interval is the number of seconds between the times the statistics were read from the registry. For statistics where the value of Differentiate is FALSE, the application can simply display the value from the registry.

You can use the Windows API to read the registry. However, I find it easier to use the TRegistry class in VCL. If you use TRegistry, you must explicitly include the file registry.hpp, because it isn't automatically included in VCL applications.

Listing A contains simple command line application that displays the attributes for all performance statistics available on a system. It reads the server names from the HKEY_LOCAL_MACHINE\System\CurrentControlSet\control\PerfStats\Enum key, and the statistics properties from the subkeys.

Listing A: Application to display performance statistic attributes

#include <vcl.h>
#include <registry.hpp>
#include <condefs.h>
#include <iostream>
#pragma hdrstop
using namespace std ;

#pragma argsused
int main(int argc, char **argv)
{
  // Open the registry key containing the server/statistic information.
  const String root = "System\\CurrentControlSet\\control\\PerfStats\\Enum" ;
  TRegistry *registry = new TRegistry ;
  registry->RootKey = HKEY_LOCAL_MACHINE ;
  bool status = registry->OpenKey (root, false) ;

  if (! status)
  {
    cerr << "Can't read registry key '" << root.c_str () << "'
      " << endl ;
    return 1 ;
  }

  TStringList *servers = new TStringList ;
  TStringList *statistics = new TStringList ;

  // Read the names of the servers and process each one.
  registry->GetKeyNames (servers) ;
  registry->CloseKey () ;
  for (int ii = 0 ; ii < servers->Count ; ++ ii)
  {
    // Read the statistics for the for the server.
    String server = servers->Strings [ii] ;
    const String key = root + "\\" + server ;
    bool status = registry->OpenKey (key, false) ;
    if (! status)
    {
      cerr << "Can't read registry key '" << key.c_str () << "'
        " << endl ;
      continue ;
    }
    String name = registry->ReadString ("Name") ;
    registry->GetKeyNames (statistics) ;
    registry->CloseKey () ;

    // Print the server description.
    cout << "Server: " << server.c_str () << endl ;
    cout << "Name:   " << name.c_str () << endl ;

    // Print the descriptions for the server's statistics.
    for (int jj = 0 ; jj < statistics->Count ; ++ jj)
    {
      String statistic = statistics->Strings [jj] ;
      const String key = root + "\\" + server + "\\
        " + statistic ;
      bool status = registry->OpenKey (key, false) ;
      if (! status)
      {
        cerr << "Can't read registry key 
          '" << key.c_str () << "' " << endl ;
        continue ;
      }
      String description = registry->ReadString ("Description");
      String name = registry->ReadString ("Name") ;
      String differentiate = registry->ReadString("Differentiate");
      registry->CloseKey () ;
      cout << " Statistic:  " << statistic.c_str () << endl ;
      cout << " Name:       " << name.c_str () << endl ;
      cout << " Description: " << description.c_str () << endl ;
      cout << " Differentiate: " << differentiate.c_str () << endl ;
      cout << endl ;
    }
    cout << endl ;
  }
  delete servers ;
  delete statistics ;
  delete registry ;
  return 0 ;
}

Getting statistics values

The actual performance data resides under the registry key HKEY_DYN_DATA\PerfStats\StatData. The value names under this key have the form SERVERNAME\STATISTICNAME. For example, the CPUUsage statistic from the KERNAL server is KERNAL\CPUUsage. Figure B shows the statistics keys displayed by REGEDIT. By pressing [F5], you can update the statistics values in the registry, giving you a poor-man's system monitor.

Figure B: The statistics keys are displayed by REGEDIT.
[ Figure B ]

You'll notice that some statistics don't change. KERNEL\CPUUsage just stays at 64 Hex (=100 %). Obviously the CPU usage isn't 100 percent all the time. The reason there's no change is that you have to tell the server to start reporting statistics.

Using REGEDIT, take a look at the values in the keys HKEY_DYN_DATA\PerfStats\StartStat and HKEY_DYN_DATA\PerfStats\StopStat. You'll notice that the values for these keys have the same names as the ones in HKEY_DYN_DATA\PerfStats\StatData. To start collecting a performance statistic, you simply read the value for the statistic you're interested in from the StartData key. Likewise, to turn off a statistic, you read the corresponding key value from the StopData key.

To see the starting and stopping of a performance statistic in operation, use REGEDIT to view the HKEY_DYN_DATA\PerfStats\StatData key. Try refreshing a few times. Notice that there's no change.

Next, start the System Monitor program and use it to view CPUUsage. Go back to REGEDIT while System Monitor is running and refresh. You should see the CPUUsage get updated. After closing the System Monitor, take another look at REGEDIT. The CPUUsage should stop updating shortly.

Once you've started collecting a statistic, you read the actual statistic value from the HKEY_DYN_DATA\PerfStats\StatData key. All of the statistics values are 4-byte integers. However, they're stored in the registry as binary values, so you can't use the TRegistry::ReadInteger method to access them. You must use TRegistry::ReadBinaryData instead.

An application for displaying CPU usage

To illustrate how to gather performance statistics, I've created a simple application that displays the CPU usage percentage found in the KERNEL\CPUUsage key value. This is a statistic that will be present on all systems. Listing B contains the class definition of a form that displays the current CPU usage. I've placed three controls on the form. A TPanel is aligned to the right, a TPerformanceGraph from the Samples page is aligned to the client, and there's a TTimer control, which isn't visible at runtime. To the class definition, I've added a pointer to a TRegistry object and included the file Registry.hpp.

Listing B: CPU.H

//---------------------------------------------------
#ifndef CPUH
#define CPUH
//---------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <Registry.hpp>
#include <ComCtrls.hpp>
#include <ExtCtrls.hpp>
#include "perfgrap.h"
//---------------------------------------------------
class TForm1 : public TForm
{
  __published:	// IDE-managed Components
    TTimer *Timer;
    TPerformanceGraph *PerformanceGraph;
    TPanel *Panel;
    void __fastcall FormCreate(TObject *Sender);
    void __fastcall FormDestroy(TObject *Sender);
    void __fastcall TimerTimer(TObject *Sender);
		
    void __fastcall FormResize(TObject *Sender);
  private:	// User declarations
    TRegistry *registry ;
  public:	// User declarations
    __fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------
#endif
Listing C contains the implementation for the form class. The form reads the registry to start the reporting of the KERNEL\CPUUsage statistic in the OnCreate event. The OnDestroy event reads the registry to stop the performance measurements when the form closes.

The TTimer control is used to update the CPU usage display. The OnTimer event for the TTimer object gets called every second. In this event, the form reads the current CPU usage and displays it.

Listing C: CPU.cpp

//----------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "CPU.h"
//----------------------------------------------------------------
#pragma package(smart_init)
#pragma link "perfgrap"
#pragma resource "*.dfm"
TForm1 *Form1;
//----------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner): TForm(Owner)
{
}
//----------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
  // Allocate and set up the TRegistry object used throughout.
  registry = new TRegistry ;
  registry->RootKey = HKEY_DYN_DATA ;

  // Read the key value that starts the collection of CPUUsage.
  registry->OpenKey ("PerfStats\\StartStat", false) ;
  unsigned int value ;
  registry->ReadBinaryData ("KERNEL\\CPUUsage", (void *) &value, sizeof (value)) ;
  registry->CloseKey () ;
  return ;
}
//----------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
  // Shut down the CPUUsage statistic.
  registry->OpenKey ("PerfStats\\StopStat", false) ;
  unsigned int value ;
  registry->ReadBinaryData ("KERNEL\\CPUUsage", (void *) &value, sizeof (value)) ;
  registry->CloseKey () ;
  delete registry ; 
  registry = 0 ;
  return ;
}
//----------------------------------------------------------------
void __fastcall TForm1::TimerTimer(TObject *Sender)
{
  // Get the current CPU usage percentage.
  bool status = registry->OpenKey ("PerfStats\\StatData", false) ;
  unsigned int value ;
  registry->ReadBinaryData ("KERNEL\\CPUUsage", (void *) &value, sizeof (value)) ;
  registry->CloseKey () ;

  // Display the current value.
  PerformanceGraph->DataPoint (clRed, value) ;
  PerformanceGraph->Update () ;
  Panel->Caption = String (value) + " %" ;
  return ;
}
//----------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
{
  // Ensure that the graph always has 10 blocks.
  PerformanceGraph->GridSize = ClientHeight / 10 ;
  return ;
}
//----------------------------------------------------------------
The CPU monitor application is shown in Figure C. The left side of the form contains a historical graph of CPU usage and the right side displays the current value.

Figure C: Here's the CPU monitor application.
[ Figure C ]

Conclusion

In this article, we've shown you how to determine which statistics are available to a performance monitor on Windows 95 or 98, how to start and stop statistics gathering, and how to read statistics values. For simplicity, we've split these processes into two simple applications. How you put these pieces together would depend upon the type of application or component you're trying to create.