Evolution Programming Resources |
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.
Der direkte Zugriff auf die Performance API erfolgt über die Funktion RegQueryValueEx(). Die Funktion ist folgendermaßen definiert:
|
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
Beispielcode:
|
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.
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.
|
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