Evolution Programming Resources

  Projects

†† Die Win32 Performance API


Einführung

Die Win32 Performance API stellt für Programme im Usermode einen standardisierten Weg dar, auf Daten des Kernels und der Kernelobjekte zuzugreifen. Aber auch beliebige Programme und Treiber können ihre Daten über diese Schnittstelle verfügbar machen. Der Zugriff erfolgt allerdings nur lesend. Die Win32 Performance API stellt im Gegensatz zu den meisten anderen Windows APIís keine Gruppe von Funktionen dar, sondern macht die Daten über einen Schlüssel der Registry verfügbar. Die Daten werden jedoch nicht tatsächlich in der Registry abgelegt, sondern zum Zeitpunkt des Zugriffs dynamisch erzeugt. Der Zugriff auf die Daten kann nun entweder direkt über die Registry Funktionen erfolgen oder über den Umweg der Performance Data Helper (PDH) DLL von Mircosoft. Die PDH Dokumentation und Headerdateien werden nicht mit dem Borland Compiler ausgeliefert, sind aber bei Microsoft verfügbar. Ich habe mich für den direkten Zugriff auf die Performance API entschieden, da ein Teil der Funktionalität nicht über die PDH DLL verfügbar ist. Die Daten der Performance API gliedert sich in 3 Teile: die eigentlichen Performance Daten, die Namensdatenbank (Name Data Base) und die Bescheibungsdatenbank (Explaination Data Base). Die Performance Daten, sogenannte Counter, sind in Objekten zusammengefaßt. So enthält beispielsweise das Prozessorobjekt Daten zu aktuellen Prozessor Auslastung und den auftretenden Interrupts pro Sekunde. Von einem Objekt kann es mehrere Instanzen geben. So ist vom Prozessobjekt für jedem Prozeß eine Instanz verfügbar, die die jeweiligen Daten des Prozesses enthält.
Jedes Objekt und jeder Counter in einem Objekt haben einen Namen und meist auch eine Beschreibung. Diese Daten werden jedoch nicht im Objekt selbst gespeichert, sondern in der Namens- bzw. der Beschreibungsdatenbank. Das Objekt enthält nur einen Verweis auf die Daten in Form einen Indexwertes.

Zugriff auf die Performance API

Der direkte Zugriff auf die Performance API erfolgt über die Funktion RegQueryValueEx(). Die Funktion ist folgendermaßen definiert:

LONG RegQueryValueEx(
  HKEY    hKey,             // handle of key to query 
  LPTSTR  lpszValueName,    // address of name of value to query 
  LPDWORD lpdwReserved,     // reserved 
  LPDWORD lpdwType,         // address of buffer for value type 
  LPBYTE  lpbData,          // address of data buffer 
  LPDWORD lpcbData          // address of data buffer size 
);

Beim Zugriff auf die Performance API ist als hKey die Konstante HKEY_PEFORMANCE_DATA zu übergeben, oder ein Handle, das man über RegConnectRegistry erlangt hat. Durch RegConnectRegistry ist es möglich auch die Performancedaten eines anderen Rechners im Netzwerk abzufragen. In lpszValueName wird angegeben, welche Daten man erfragen will. Beim Zugriff auf die Namensdatenbank ist hier der String "Counter" gefolgt von einer LanguageId anzugeben. Die LanguageId gibt an, in welcher Sprache die Namen übergeben werden sollen. Die LanguageId für Englisch lautet "009", für Deutsch "007". Will man also die deutsche Namendatenbank erfragen übergibt man den String "Counter 007". Die installierten Sprachen kann man unter dem Registry-Schlüssel "HKEY_LOCAL_MACHINE\SOFTWARE\ Microsoft\Windows NT\Current Version\Perflib" nachschauen. Für jede unterstützte Sprache ist hier ein Unterschlüssel mit der LanguageId als Namen angelegt. Die Daten der Namensdatenbank werden in lpbData in folgender Form zurück gegeben:

  index1\0name1\0
  index2\0name2\0
  ...
  index<n>\0name<n>\0\0

Sie bestehen immer aus dem Indexwert (als String), gefolgt vom jeweiligen Namen. Die Liste wird von einem Nullzeichen abgeschlossen. In der Namensdatenbank sind sowohl die Namen von Objekten, als auch von Countern enthalten. Ein kurzer Auszug aus der Datenbank:

  2 -- System
  4 -- Memory
  6 -- % Processor Time
  10 -- File Read Operations/sec
  12 -- File Write Operations/sec
  14 -- File Control Operations/sec
  16 -- File Read Bytes/sec
  18 -- File Write Bytes/sec

Die Beschreibungdatenbank ist im selben Format aufgebaut, wird aber über den String "Explain " abgefragt.

Beispielcode:

char *name;
DWORD type, size = 0;

// Groesse der Daten erfragen. Wird in size zureuck gegeben.
if(RegQueryValueEx(HKEY_PERFORMANCE_DATA, "Counter 009",
     0, &type, NULL,
     &size) != ERROR_SUCCESS)
{
   // Errorhandling
   return;
}

// Puffer von passender Groesse anlegen.
name = new char [size];

// Speicher initalisieren.
memset(name, 0, size);

// Puffer fuellen
if(RegQueryValueEx(HKEY_PERFORMANCE_DATA, "Counter 009",
     0, &type, name,
     &size) != ERROR_SUCCESS)
{
   // Errorhandling
   return;
}

Die eigentlichen Performance Daten erhält man nun, indem man RegQueryValueEx einen oder mehrere der über die Namensdatenbank erlangen Indexwerte übergibt. Werden mehrere Werte übergeben, dann müssen sie per Leerzeichen getrennt werden.
Allerdings kann hier nicht die Größe der Daten im voraus durch das Übergeben von NULL in lpbData erfragt werden, wie im obigen Beispielcode gezeigt. Dies ist auch logisch, da es sich um dynamische Daten handelt, deren Größe sich zwischen den beiden Aufrufen von RegQueryValueEx ändern kann. Deshalb muß hier ein entsprechend dimensionierter Puffer übergeben werden, die bei Bedarf (Rückgabe von ERROR_MORE_DATA) vergrößert werden muß. Die in lpdData übergebenen Performancedaten haben folgenden Aufbau:


  PERF_DATA_BLOCK
    PERF_OBJECT_TYPE(1)
      PERF_COUNTER_DEFINITION(1)
      PERF_COUNTER_DEFINITION(2)
      <...>
      PERF_INSTANCE_DEFINITION(1)
        PERF_COUNTER_BLOCK
          counterData1
          counterData2
          counterData3
          <...>
      PERF_INSTANCE_DEFINITION(2)
        PERF_COUNTER_BLOCK
          counterData1
          counterData2
          counterData3
          <...>
    PERF_OBJECT_TYPE(2)
    <...>

Wie man sieht, haben die Performance Daten einen relativ komplexen Aufbau. Eine weitere Schwierigkeit besteht darin, das die Daten in einem Bytearray zurück gegeben werden. Das verspricht einiges an Pointerarithmetik.
Die Daten beginnen mit einem PERF_DATA_BLOCK. Er enthält zum einen eine Signatur, die die Daten als Performancedaten ausweist und außerdem die Anzahl der in den Daten enthaltenen Objekten. Mehrere Objekte kann man erhalten, wenn man mehrere abfragt (mehrere Indexnummer getrennt durch Leerzeichen) oder wenn ein man Daten eines Objekts abfragt, das von anderen Objekten abhänig ist. Fragt man z.B. die Daten des Threadobjekts ab, erhält man ungefragt auch noch die Daten des Prozeßobjekts, da die Daten der Threads von denen der Prozesse abhängig sind.
Auf den PERF_DATA_BLOCK folgen also einer oder mehrere PERF_OBJECT_TYPE Blöcke. Diese enthalten beispielsweise die Anzahl der Instanzen und Counter pro Objekt und setzen sich aus entsprechend vielen PERF_COUNTER_DEFINITION und PERF_INSTANCE_DEFINITION Blöcken zusammen. Die PERF_COUNTER_DEFINITION Blöcke enthalten den Namen des Counters (als Indexwert der Name Data Base) und den Offset der Daten des Counters vom Anfang eines PERF_COUNTER_BLOCKís. Jeder der PERF_INSTANCE_DEFINITION Blöcke enthält den Namen einer Instanz (diesmal nicht als Indexwert, sondern tatsächlich) und wird von einem PERF_COUNTER_BLOCK gefolgt. Auf den PERF_COUNTER_BLOCK folgen nun endlich mit dem entsprechenden Offset (aus dem PERF_COUNTER_DEFINITION Block) die Performance Daten.

Erstellen eine Prozessliste aus den Performance Daten

Für das Erstellen einer Prozessliste aus den Performance Daten ist die Namensdatenbank nur insofern interessant, das man über sie den Indexwert des Prozessobjekts und der interessanten Counter ermitteln kann. Dies sind im einzelnen:

Indexwert Counter/Objekt
230 Prozessobjekt
6% Prozessorzeit
142 % Benutzerzeit
144 % Privilegierte Zeit
172 Maximale virtuelle Größe
174 Virtuelle Größe
28 Seitenfehler/s
178 Maximum Arbeitsseiten
680 Thread-Anzahl
682 Basispriorität
684 Vergangene Zeit
180 Arbeitsseiten
182 Maximum Auslagerungsdateiseiten
184 Auslagerungsdateiseiten
186 Private Seiten
56 Auslagerungsseiten
58 Nichtauslagerungsseiten
952 Anzahl von Handles
784 Prozeß-ID

Man sieht deutlich, das man alle Daten, die der Taskmanager zu einem Prozeß anzeigt über die Performance API erlangen kann - und noch viele mehr. Dies allerdings ohne je ein Handle auf den jeweiligen Prozeß anzufordern.
Das Vorgehen um dieses Daten zu erhalten ist nun folgendes: man führt ein RegQueryValueEx auf das Objekt 230 (Prozeß) aus. Nun durchläuft man die Liste der Counter und merkt sich für alle auszugebenden Counter den Offset vom PERF_COUNTER_BLOCK. Anschließend durchläuft man die einzelnen Instanzen und ermittelt für sie den entsprechenden PERF_COUNTER_BLOCK. Nun muß man nur noch die Daten am zuvor ermittelten Offset ausgeben. Ich habe dies meinem Programm enum_process exemplarisch für die ProzeßId durchgeführt.

/*************************************************************************
 * enum_process.cpp                                1998 by Thomas Krammer
 *************************************************************************
 * Gibt unter Windows NT eine Liste aller Prozesse im System und deren
 * ProcessId aus.
 *************************************************************************/

#include <windows.h>
#include <limits.h>
#include <iostream.h>
#include <iomanip.h>

#define DEFAULT_BUFFER_SIZE 40960L

PERF_OBJECT_TYPE *FirstObject(PERF_DATA_BLOCK *dataBlock)
{
  return (PERF_OBJECT_TYPE *) ((BYTE *)dataBlock + dataBlock->HeaderLength);
}

PERF_OBJECT_TYPE *NextObject(PERF_OBJECT_TYPE *act)
{
  return (PERF_OBJECT_TYPE *) ((BYTE *)act + act->TotalByteLength);
}

PERF_COUNTER_DEFINITION *FirstCounter(PERF_OBJECT_TYPE *perfObject)
{
  return (PERF_COUNTER_DEFINITION *) ((BYTE *) perfObject + perfObject->HeaderLength);
}

PERF_COUNTER_DEFINITION *NextCounter(PERF_COUNTER_DEFINITION *perfCounter)
{
  return (PERF_COUNTER_DEFINITION *) ((BYTE *) perfCounter + perfCounter->ByteLength);
}

PERF_COUNTER_BLOCK *GetCounterBlock(PERF_INSTANCE_DEFINITION *pInstance)
{
  return (PERF_COUNTER_BLOCK *) ((BYTE *)pInstance + pInstance->ByteLength);
}

PERF_INSTANCE_DEFINITION *FirstInstance (PERF_OBJECT_TYPE *pObject)
{
  return (PERF_INSTANCE_DEFINITION *)  ((BYTE *) pObject + pObject->DefinitionLength);
}

PERF_INSTANCE_DEFINITION *NextInstance (PERF_INSTANCE_DEFINITION *pInstance)
{
  // next instance is after
  //    this instance + this instances counter data

  PERF_COUNTER_BLOCK  *pCtrBlk = GetCounterBlock(pInstance);

  return (PERF_INSTANCE_DEFINITION *) ((BYTE *)pInstance + pInstance->ByteLength + pCtrBlk->ByteLength);
}

// WideToMulti
// Umwandlung eines UnicodeStrings in einen ANSI String.
// source:    UnicodeString
// dest:      Zielpuffer
// size:      Groesse von dest in Bytes
// Rueckgabe: Zeiger auf dest
char *WideToMulti(wchar_t *source, char *dest, int size)
{
  WideCharToMultiByte(CP_ACP, 0, source, -1, dest, size, 0, 0);

  return dest;
}

int main()
{
  // Puffer fuer Performance Daten
  char *data = new char [DEFAULT_BUFFER_SIZE];
  // Rueckgabe von RegQueryValueEx: Typ der Daten - fuer dieses
  // Programm uninteressant
  DWORD type;
  // Groesse des Puffers
  DWORD size = DEFAULT_BUFFER_SIZE;
  // Rueckgabewert von RegQueryValueEx
  DWORD ret;

  // Performance Daten zum Prozessobjekt (Indexwert 230) anfordern
  while((ret = RegQueryValueEx(HKEY_PERFORMANCE_DATA, "230", 0, &type, data, &size)) != ERROR_SUCCESS) {
    if(ret == ERROR_MORE_DATA) {
      // Der Puffer war zu klein. Es liegen noch mehr Daten bereit.
      // Puffer vergroessern und RegQueryValueEx noch einmal aufrufen.
      size += DEFAULT_BUFFER_SIZE;

      delete [] data;
      data = new char [size];
    } else {
      // Es ist ein anderer Fehler aufgetreten. Programm verlassen.
      cout << "RegQueryValueEx failed ErrorCode: " << ret << endl;
      return 1;
    }
  }

  PERF_DATA_BLOCK *dataBlockPtr = (PERF_DATA_BLOCK *)data;

  // Erstes Objekt in Liste der Objekte ermitteln
  PERF_OBJECT_TYPE *objectPtr = FirstObject(dataBlockPtr);

  // Durch die Liste der Objekte laufen.
  for(int a=0 ; a<dataBlockPtr->NumObjectTypes ; a++) {
    char nameBuffer[255];

    // Handelt es sich tatsaechlich um ein Prozessobjekt??
    // (Die Performance API gibt haeufig auch ungefragt mehr Objekte zureuck.
    //  z.B bei Abhaenigkeitsbeziehungen von Objekten...)
    if(objectPtr->ObjectNameTitleIndex == 230) {
      // Offset der ProcessId im PERF_COUNTER_BLOCK
      DWORD processIdOffset = ULONG_MAX;

      // Ersten Counter ermitteln
      PERF_COUNTER_DEFINITION *counterPtr = FirstCounter(objectPtr);

      // Durch Liste der Counter laufen
      for(int b=0 ; b<objectPtr->NumCounters ; b++) {
        // Handelt es sich um den ProcessId Counter??
        // Wenn ja, dann Offset der ProcessId im PERF_COUNTER_BLOCK abspeichern.
        if(counterPtr->CounterNameTitleIndex == 784)
          processIdOffset = counterPtr->CounterOffset;

        // Naechsten Counter betrachten.
        counterPtr = NextCounter(counterPtr);
      }

      if(processIdOffset == ULONG_MAX) {
        cout << "Konnte Indexwert der ProcessId nicht ermitteln!" << endl;
        return 1;
      }

      cout << "Prozessliste:" << endl;

      // Erste Instanz ermitteln
      PERF_INSTANCE_DEFINITION *instancePtr = FirstInstance(objectPtr);

      // Fuer jede Instanz den Namen und die ProzessId ausgeben.
      for(int b=0 ; b<objectPtr->NumInstances ; b++) {
        // Zeiger auf Namensstring ermitteln
        wchar_t *namePtr = (wchar_t *) ((BYTE *)instancePtr + instancePtr->NameOffset);

        // PERF_COUNTER_BLOCK dieser Instanz ermitteln.
        PERF_COUNTER_BLOCK *counterBlockPtr = GetCounterBlock(instancePtr);

        // ProcessId und Namen ausgeben
        cout << "   "
             << setw(4)
             << *((DWORD *) ((BYTE *)counterBlockPtr + processIdOffset))
             << "   "
             << WideToMulti(namePtr, nameBuffer, sizeof(nameBuffer))
             << endl;

        // naechste Instanz
        instancePtr = NextInstance(instancePtr);
      }
    }

    // naechstes Objekt in der Liste
    objectPtr = NextObject(objectPtr);
  }

  return 0;
}

Ausgabe des Programms


Prozessliste:
      0   Idle
      2   System
     20   smss
     24   csrss
     34   WINLOGON
     40   SERVICES
     43   LSASS
     67   SPOOLSS
     78   mgasc
     80   mgactrl
     82   RPCSS
     85   TAPISRV
     97   RASMAN
    167   NDDEAGNT
     99   EXPLORER
    160   systray
    103   TNTWIN
    165   mgahook
    173   CMD
     57   enum
      0   _Total


††  ††Top 
 

| © 1998 by 3rd-evolution