-
HINTERGRUND
DER ERFINDUNG
-
Die
vorliegende Erfindung betrifft ein verbessertes Datenverarbeitungssystem
und speziell ein Verfahren und eine Vorrichtung zur Leistungsoptimierung
in einem Datenverarbeitungssystem. Ganz speziell bietet die vorliegende
Erfindung ein Verfahren und eine Vorrichtung für ein Softwareprogrammentwicklungs-Tool
zur Verbesserung der Leistungsfähigkeit
eines Softwareprogramms durch Software-Profilierung.
-
Bei
der Analyse und Verbesserung der Leistungsfähigkeit eines Datenverarbeitungssystems
und den darauf ausgeführten
Anwendungsprogrammen ist es vorteilhaft, zu wissen, welche Softwaremodule in
einem Datenverarbeitungssystem Systemressourcen verwenden. Eine
effektive Verwaltung und Verbesserung von Datenverarbeitungssystemen
erfordert Kenntnisse darüber,
wie und wann verschiedene Systemressourcen verwendet werden. Zur Überwachung
und Prüfung
eines Datenverarbeitungssystems werden Leistungsbewertungs-Tools
eingesetzt, um den Ressourcenverbrauch bei der Ausführung verschiedener
Anwendungsprogramme auf dem Datenverarbeitungssystem festzustellen.
Ein Leistungsbewertungs-Tool
kann beispielsweise die am häufigsten
ausgeführten
Module und Instruktionen in einem Datenverarbeitungssystem identifizieren,
oder es kann erkennen, welche Module am meisten Speicherplatz zuteilen
oder die meisten E/A-Anforderungen ausführen. Als Hardware implementierte
Leistungsbewertungs-Tools können
in das System eingebaut sein oder zu einem späteren Zeitpunkt hinzugefügt werden.
Leistungsbewertungs-Tools in Softwareform sind in Datenverarbeitungssystemen
ebenfalls von Nutzen, z.B. in PC-Systemen,
die in der Regel nicht viele oder gar keine eingebauten hardwaremäßig implementierten
Leistungsbewertungs-Tools
enthalten.
-
Ein
bekanntes Leistungsbewertungs-Tool in Softwareform ist ein Ablaufverfolgungs-Tool.
Ein Ablaufverfolgungs-Tool kann mehrere Verfahren verwenden, um
Protokolldaten zu liefern, die Ausführungsflüsse für ein laufendes Programm erkennen lassen.
Ein Verfahren verfolgt bestimmte Abfolgen von Instruktionen, indem
bestimmte Ereignisse bei ihrem Auftreten protokolliert werden. Dieses
Verfahren wird als ereignisbasiertes Profilierungsverfahren bezeichnet.
So kann beispielsweise ein Ablaufverfolgungs-Tool jeden Aufruf und
jedes Verlassen eines Moduls, einer Subroutine, eines Verfahrens,
einer Funktion oder einer Systemkomponente protokollieren. Alternativ
kann ein Ablaufverfolgungs-Tool aufzeichnen, wer Speicherplatz anfordert
und wieviel Speicherplatz bei jeder Speicherzuteilungsanforderung
zugeteilt wird. Typischerweise wird für jedes derartige Ereignis
eine mit einem Zeitstempel versehene Aufzeichnung erzeugt. Zusammengehörige Paare
von Aufzeichnungen, ähnlich
wie Einstiegs-/Aufstiegs-Aufzeichnungen,
werden zur Verfolgung der Ausführung
beliebiger Codesegmente, die Ein-/Ausgaben oder Datenübertragungen
starten oder beenden, aber auch zur Verfolgung vieler anderer relevanter
Ereignisse verwendet.
-
Um
die Leistungsfähigkeit
des von verschiedenen Computerfamilien generierten Codes zu steigern,
muss oft festgestellt werden, für
was der Prozessor bei der Codeausführung Zeit aufwendet. Solche
Anstrengungen werden in der Datenverarbeitungsbranche üblicherweise
als Bestimmung von "Hot
Spots" bezeichnet.
Im Idealfall würde
man wünschen,
solche Hot Spots auf der Instruktions- und/oder Quellcodezeilenebene
zu erkennen, um die Aufmerksamkeit auf Bereiche zu konzentrieren,
in denen Verbesserungen des Codes die größten Vorteile bringen könnten.
-
Bei
einem anderen Ablaufverfolgungsverfahren wird periodisch der Ausführungsfluss
eines Programms erfasst, um bestimmte Stellen im Programm festzustellen,
an denen das Programm offensichtlich viel Zeit benötigt. Dieses
Verfahren basiert auf dem Konzept, das Anwendungsprogramm oder das
Datenverarbeitungssystem in regelmäßigen Zeitabständen zu
unterbrechen. Dies wird als Profilierung auf Abtastungsbasis bezeichnet.
Bei jeder Unterbrechung werden eine bestimmte Zeit lang oder über eine
bestimmte Anzahl von Ereignissen Informationen aufgezeichnet. So
kann beispielsweise der Programmzähler des gerade ausgeführten Threads,
eines Prozesses der Bestandteil des größeren zu profilierenden Programms
ist, in den Intervallen protokolliert werden. Diese Werte können nach
der Verarbeitungszeit in ein Auslastungsdiagramm und Symboltabellen
für das
Datenverarbeitungssystem eingetragen werden, und aus dieser Analyse
kann ein Profil erstellt werden, aus dem zu ersehen ist, wo die
Zeit aufgewendet wird.
-
In
wiederum anderen Ansätzen,
wie etwa der Lehre gemäß dem US
Patent
US 5,838,976 ,
geht es um die Korrektheit der Leistungsermittlung bei der Ausführung eines
Programmes auf einem Multiprozessor-Systems unter dem Gesichtpunkt
der Parallelverarbeitung. Gemäß dieser
Lehre soll verhindert werden, dass sich sog. "symmetrically executing code", der gleichzeitig
auf unterschiedlichen CPUs ausgeführt wird, mehrfach auf die
gesamte Ausführungszeit
durchschlägt
und diese dadurch verfälscht.
-
Die
Erkennung solcher Hot Spots auf Instruktionsebene ermöglicht es
Compilerprogrammierern, wesentliche Bereiche suboptimaler Codegenerierung zu
finden, auf die sie ihre Anstrengungen zur Steigerung der Effizienz
bei der Codegenerierung konzentrieren können. Details auf Instruktionsebene
können außerdem auch
von Entwicklern zukünftiger
Systeme als Richtlinie verwendet werden. Solche Entwickler benutzen
Profilierungs-Tools, um charakteristische Codesequenzen und/oder
einzelne Instruktionen zu finden, die einer Optimierung für die verfügbare Software
für eine
bestimmte Art von Hardware bedürfen.
-
Die
kontinuierliche ereignisbasierte Profilierung hat ihre Grenzen.
Sie nimmt zum Beispiel viel Leistung in Anspruch (ein Ereignis pro
Einstieg und pro Ausstieg), wodurch die resultierende Leistungsbeurteilung
gestört
werden kann und oft auch gestört wird.
Außerdem
steht dieses Verfahren nicht immer zur Verfügung, da eine statische oder
dynamische Einfügung
von Einstiegs-/Ausstiegsereignissen in den Code erforderlich ist.
Diese Einfügung
von Ereignissen ist nicht immer möglich oder oft schwierig. Wenn
zum Beispiel der Quellcode nicht für den zu instrumentierenden
Code zur Verfügung
steht, ist eine ereignisbasierte Profilierung eventuell nicht möglich. Es
besteht jedoch die Möglichkeit,
einen Quellcode-Interpreter so zu instrumentieren, dass ereignisbasierte
Profilierungsinformationen gewonnen werden, ohne dass der Quellcode
geändert
wird.
-
Andererseits
ermöglicht
die auf Abtastung basierende Profilierung nur einen "flachen Blick" auf die Systemleistung,
bietet aber nicht den Vorteil geringerer Kosten und einer geringeren
Abhängigkeit von
der Buchungskapazität.
Ferner lassen auf Abtastung basierende Verfahren nicht erkennen,
wo die Zeit für
viele kleine und augenscheinlich nicht miteinander zusammenhängende Funktionen
aufgewendet wird, oder für
was die Zeit in Situationen, in denen kein deutlicher "Hot Spot" erkennbar ist, benötigt wird.
Ohne ein Verständnis
der Programmstruktur ist aus einem "ebenen" Profil nicht klar ersichtlich, wie festgestellt
werden kann, an welcher Stelle Leistungsverbesserungen möglich sind.
-
Es
wäre daher
vorteilhaft, wenn es ein System gäbe, das die Vorteile der ereignisbasierten
Profilierung mit den Vorteilen geringerer Systemstörung bei
der auf Abtastung basierenden Profilierung verbindet. Es wäre besonders
vorteilhaft, wenn es die Möglichkeit
gäbe, die
Profilierung ausgewählter
Teile des Datenverarbeitungssystems zu aktivieren und zu deaktivieren
und das Ergebnis dieser verschiedenen Profilierungsperioden in einer
einzigen kombinierte Darstellung aufzuzeigen. Insbesondere soll
das Problem gelöst
werden mittels einer Profilierung zu ermitteln, wie und in welcher
Häufigkeit
sich die einzelnen Routinen (dh Funktionen) wechselseitig aufrufen,
aus denen sich ein Programm zusammensetzt, ums so die Aufrufcharakteristik
innerhalb eines Programmes zu bestimmen.
-
ÜBERBLICK ÜBER DIE
ERFINDUNG
-
Die
vorliegende Erfindung liefert ein Verfahren und ein System zur Profilierung
eines Programms mit Hilfe einer periodischen Abtastung zur Erstellung eines
Ablaufprotokolls. Während
der Programmausführung
wird eine auf Abtastung basierende Profilierung des ausgeführten Programms
durchgeführt – über eine
vorgegebene Zeitspanne führt
ein Profilierungsverfahren eine Protokollierungsverarbeitung für das Programm
durch, danach macht das Profilierungsverfahren eine Pause und führt eine
bestimmte Zeit lang keine Protokollierungsverarbeitung durch. Die
Zeitspannen zur Steuerung des Profilierungsverfahrens können von
einem Benutzer ausgewählt
werden, und die Zeitspannen können
durch temporale oder nicht-temporale Metrik gemessen werden. Das Profilierungsverfahren
durchläuft
diese Perioden, während
derer ausgewählte
Ereignisse verarbeitet werden, um ein Profil der Ausführungsflüsse in dem Programm
zu erstellen. Für
jede Abtastperiode wird eine Baumdatenstruktur erstellt, in der
die Knoten die während
der Abtastperiode ausgeführten
Routinen des Programms darstellen. Dies kann durch Ein- und Ausstiegsereignisse,
die durch die Ausführung
der Routinen verursacht werden, signalisiert werden. Wenn die Programmausführung abgeschlossen
ist, werden die Baumdatenstrukturen aller Abtastperioden zu einer
resultierenden Baumdatenstruktur verschmolzen. Aus dieser resultierenden
Baumdatenstruktur ergibt sich schließlich, wie und in welcher Häufigkeit
sich die einzelnen Routinen (dh Funktionen) wechselseitig aufrufen,
aus denen sich ein Programm zusammensetzt.
-
Die
neuen Funktionen, die als charakteristische Merkmale der Erfindung
betrachtet werden, sind in den angefügten Ansprüchen definiert. Die Erfindung
selber sowie eine bevorzugte Einsatzmöglichkeit und weitere Aufgaben
und Vorteile der Erfindung sind aber am besten aus der nachstehenden detaillierten
Beschreibung einer illustrativen Ausführungsform in Verbindung mit
den beigefügten
Zeichnungen zu verstehen.
-
Die
Zeichnungen haben folgenden Inhalt:
-
1 zeigt
ein verteiltes Datenverarbeitungssystem, in dem die vorliegende
Erfindung eingesetzt werden kann;
-
Die 2A–2B sind
Blockdiagramme, in denen ein Datenverarbeitungssystem dargestellt ist,
in dem die vorliegende Erfindung implementiert werden kann;
-
3A ist
ein Blockdiagramm, in dem die Beziehung zwischen den Software-Komponenten
in einem Computersystem, in dem die vorliegende Erfindung implementiert
ist, aufgezeigt wird.
-
3B ist
ein Blockdiagramm, in dem eine virtuelle Java-Maschine gemäß einer bevorzugten Ausführungsform
der vorliegenden Erfindung dargestellt ist;
-
4 ist
ein Blockdiagramm, in dem die Komponenten dargestellt sind, die
zur Profilierung von Prozessen in einem Datenverarbeitungssystem verwendet
werden.
-
5 ist
eine Zeichnung, in der verschiedene Phasen bei der Profilierung
des aktiven Prozesses in einem Betriebssystem dargestellt werden;
-
6 ist
ein Flussdiagramm, in dem von einem Ablaufverfolgungsprogramm verwendeter
Prozess zur Generierung von Protokolldatensätzen aus den auf einem Datenverarbeitungssystem
ausgeführten
Prozessen dargestellt ist;
-
7 ist
ein Flussdiagramm eines Prozesses, der in einem Systeminterrupt-Handler-Protokollanker
verwendet wird;
-
8 ist
ein Diagramm, in dem der Aufrufstack, der Stack Frames enthält, dargestellt
ist;
-
9 zeigt
ein Beispiel für
einen Aufrufstack;
-
10A zeigt eine Programmausführungssequenz zusammen mit
dem Zustand des Aufrufstack an jedem Funktionseinstiegs- und Funktionsausstiegspunkt.
-
10B zeigt eine bestimmte zeitperiodenbasierte
Abtastung des in 10A dargestellten Ausführungsflusses;
-
Die 10C und 10D sind
Zeitdiagramme, die ein Beispiel der vom Profilierungs-Tool berücksichtigten
Zeittypen zeigen;
-
11A ist ein Diagramm, in dem eine aus der Abtastung
von Aufrufstacks generierte Baumstruktur dargestellt ist;
-
11B ist ein Diagramm, das einen Ereignisbaum zeigt,
der die bei der Systemausführung
beobachteten Aufrufstacks widerspiegelt.
-
12 ist
eine tabellarische Darstellung eines Aufrufstacks;
-
13 ist
ein Flussdiagramm, in dem ein Verfahren zur Erstellung eines Aufrufstack-Baums mit
Hilfe einer Ablaufprotokoll-Textdatei als Eingabedatei dargestellt
ist;
-
14 ist
ein Flussdiagramm, in dem ein Verfahren zur dynamischen Erstellung
eines Aufrufstack-Baums bei der Abtastung während der Systemausführung dargestellt
ist;
-
15 zeigt
einen Datensatz, der mit Hilfe der erfindungsgemäßen Prozesse generiert wurde;
-
16 zeigt
eine andere Berichtsart, die erzeugt werden kann, um die Aufrufstruktur
zwischen den in 12 dargestellten Routinen aufzuzeigen;
-
17 ist
eine Tabelle, in der ein Bericht dargestellt ist, der aus einer
Protokolldatei generiert wurde, die sowohl ereignisbasierte Profilierungsdaten (Methoden-Ein/Ausstiege)
und auf Abtastung basierende Profilierungsdaten (Aufrollen von Stacks)
enthält.
-
18 ist eine Tabelle, in der Haupt- und Untercodes
aufgeführt
sind, die dazu benutzt werden können,
Module für
die Profilierung zu instrumentieren;
-
19 ist
ein Flussdiagramm, in dem die Verarbeitung von Ausstiegsereignissen
einschließlich
der Fehlerbehebungsverarbeitung in einem Ausführungsfluss, der Fehler in
Form entsprechungsloser Ereignisse enthalten kann, dargestellt ist;
-
20 ist
ein Flussdiagramm, in dem ein Prozess zur auf Abtastung basierten
Profilierung von Ein-/Ausstiegsereignissen unter Verwendung benutzerdefinierter
Metriken zur Erzeugung unabhängiger Aufrufstack-Baumsegmente
dargestellt ist; und
-
21 ist
ein Flussdiagramm, in dem der Prozess dargestellt ist, durch den
ein Baum, der sogenannte Quellbaum, zu einem zweiten Baum, dem sogenannten
Zielbaum, hinzugefügt
wird, um eine Vereinigung zwischen den beiden Bäumen zu schaffen.
-
AUSFÜHRLICHE
BESCHREIBUNG DER BEVORZUGTEN AUSFÜHRUNGSFORMEN
-
In
den Zeichnungen, und speziell in 1, ist ein
Datenverarbeitungssystem, in dem die vorliegende Erfindung implementiert
werden kann, bildhaft dargestellt.
-
Das
verteilte Datenverarbeitungssystem 100 ist ein Netzwerk
aus Computern, in denen die vorliegende Erfindung implementiert
werden kann. Das verteilte Datenverarbeitungssystem 100 enthält ein Netzwerk 102,
das das Medium für Übertragungsverbindungen
zwischen verschiedenen Vorrichtungen und Computern, die im verteilten
Datenverarbeitungssystem 100 miteinander verbunden sind,
bildet. Das Netzwerk 102 kann permanente Verbindungen wie
Draht- oder Glasfaserkabel oder temporäre Verbindungen per Telefon
enthalten.
-
In
dem dargestellten Beispiel ist ein Server 104 zusammen
mit der Speichereinheit 106 an das Netzwerk 102 angeschlossen.
Ferner sind die Clients 108, 110 und 112 an
ein Netzwerk 102 angeschlossen. Diese Clients 108, 110 und 112 können zum
Beispiel PCs oder Netzwerkrechner sein. Für die Zwecke der vorliegenden
Patentanmeldung ist ein Netzwerkrechner jeder an ein Netzwerk angeschlossene Rechner,
der ein Programm oder eine andere Anwendung von einem anderen an
das Netzwerk angeschlossenen Rechner empfängt. In dem dargestellten Beispiel
sendet der Server 104 Daten wie z.B. Boot-Dateien, Betriebssystemabbilder
und Anwendungen an die Clients 108–112. Die Clients 108, 110 und 112 sind
Clients des Servers 104. Das verteilte Datenverarbeitungssystem 100 kann
weitere Server, Clients und sonstige Geräte enthalten, die nicht in
der Zeichnung dargestellt sind. In dem abgebildeten Beispiel ist
das verteilte Datenverarbeitungssystem 100 das Internet,
wobei das Netzwerk 102 eine weltweite Sammlung von Netzwerken
und Gateways darstellt, die zur Kommunikation untereinander die TCP/IP-Protokolle
verwenden. Im Zentrum des Internet befindet sich ein Backbone von
schnellen Datenübertragungsleitungen
zwischen den Hauptknoten oder Host-Computern, die aus tausenden
Computersystemen von kommerziellen Betreibern, von Regierungsstellen,
von Ausbildungsstätten
und sonstigen Betreibern bestehen, die Daten und Nachrichten durch
das Netz leiten. Ein verteiltes Datenverarbeitungssystem 100 kann
selbstverständlich
aus einer Anzahl verschiedener Netzwerktypen wie z.B. Intranet oder
LAN bestehen.
-
1 ist
als Beispiel zu verstehen, nicht als Einschränkung der Netzwerkarchitektur
für die
erfindungsgemäßen Prozesse.
-
In 2A ist
ein Blockdiagramm eines Datenverarbeitungssystems dargestellt, das
im Rahmen der Erfindung als Server wie z.B. Server 104 in 1 implementiert
werden kann. Das Datenverarbeitungssystem 200 kann ein
symmetrisches Multiprozessorsystem (SMP) mit mehreren an den Systembus 206 angeschlossenen
Prozessoren 202 und 204 sein. Alternativ kann
auch ein Einprozessorsystem verwendet werden. An den Systembus 206 ist auch
ein Speichercontroller/Cache 208 angeschlossen, der eine
Schnittstelle zum lokalen Speicher 209 bildet. Die E/A-Busbrücke 210 ist
an den Systembus 206 angeschlossen und bildet eine Schnittstelle
zum E/A-Bus 212. Der Speichercontroller/Cache 208 und die
E/A-Busbrücke 210 können so
implementiert sein wie in der Zeichnung.
-
Die
an den E/A-Bus 212 angeschlossene PCI-Busbrücke 214 bildet
eine Schnittstelle zum lokalen PCI-Bus 216. An den lokalen
PCI-Bus 216 kann
ein Modem 218 angeschlossen sein. Typische PCI-Bus-Implementierungen
unterstützen
vier PCI-Erweitungssteckplätze.
Die Übertragungsverbindungen
zu den Netzwerkrechnern 108–112 in 1 können über das
Modem 218 und die Netzwerkkarte 220, als Erweiterungskarte
an den lokalen PCI-Bus 216 angeschlossen ist, hergestellt
werden.
-
Weitere
PCI-Busbrücken 222 und 224 bilden Schnittstellen
für weitere
PCI-Busse 226 und 228, von denen zusätzliche
Modems oder Netzwerkkarten unterstützt werden können. Auf
diese Weise ermöglicht
der Server 200 Verbindungen zu mehreren Netzwerkrechnern.
Wie abgebildet können
auch ein Grafikadapter 230 mit Speicherabbild, und eine
Festplatte 232 entweder direkt oder indirekt an den E/A-Bus 212 angeschlossen
sein.
-
Der
durchschnittliche Fachmann weiß,
daß die
in 2A dargestellte Hardware auch anders aussehen
kann. So können
zum Beispiel zusätzlich zu
der dargestellten Hardware oder anstelle der dargestellten Hardware
andere Peripheriegeräte
wie ein optisches Plattenlaufwerk oder ähnliches verwendet werden.
Das abgebildete Beispiel soll keine Einschränkungen der Architektur für die vorliegende
Erfindung implizieren.
-
Das
in 2A abgebildete Datenverarbeitungssystem kann beispielsweise
ein RISC/System 6000 sein, auf dem das Betriebssystem AIX (Advanced
Interactive Executive) läuft.
In 2B ist ein Blockdiagramm eines Datenverarbeitungssystems, in
dem die vorliegende Erfindung implementiert werden kann, zu sehen.
Das Datenverarbeitungssystem 250 ist ein Beispiel für einen
Client-Computer.
Das Datenverarbeitungssystem 250 verwendet eine PCI-Lokalbus-Architektur.
Statt des abgebildeten PCI-Bus kann auch eine andere Busarchitektur
wie Micro Channel oder ISA verwendet werden. Der Prozessor 252 und
der Hauptspeicher 254 sind über die PCI-Brücke 258 an
den PCI-Lokalbus 256 angeschlossen. Die PCI-Brücke 258 kann
auch einen integrierten Speichercontroller und Cache-Speicher für den Prozessor 252 enthalten.
Weitere Verbindungen zum PCI-Lokalbus 256 können über Komponentendirektverbindungen
oder Erweiterungskarten hergestellt werden. In dem dargestellten
Beispiel sind der LAN-Adapter 260,
der SCSI-Hostbusadapter 262 und die Erweiterungsbusschnittstelle 264 durch
eine Komponentendirektverbindung an den PCI-Lokalbus 256 angeschlossen.
Die Soundkarte 266, die Grafikkarte 268 und der
Audio/Video-Adapter (A/V) 269 hingegen sind durch Erweiterungskarten
in den Steckplätzen
an den PCI-Lokalbus 266 angeschlossen. Die Erweiterungsbusschnittstelle 264 bietet
einen Anschluss für
einen Tastatur- und Mausadapter 270, ein Modem 272 und
einen Erweiterungsspeicher 274. Über den SCSI-Hostbusadapter 262 sind
in diesem Beispiel ein Festplattenlaufwerk 276, ein Bandlaufwerk 278 und
ein CD-ROM-Laufwerk 280 angeschlossen. Typische PCI-Lokalbus-Implementierungen
unterstützen
drei oder vier PCI-Erweitungssteckplätze.
-
Auf
dem Prozessor 252 läuft
ein Betriebssystem, das verschiedene Komponenten im Datenverarbeitungssystem 250 in 2B koordiniert
und steuert. Das Betriebssystem kann ein handelsübliches Betriebssystem wie
Java-OS For Business oder OS/2 sein. JavaOS wird von einem Server
in einem Netzwerk auf einen Netzwerk-Client geladen und unterstützt Java-Programme
und Applets. Eine Eigenschaft von JavaOS, die günstig für die Ablaufverfolgung mittels
Stack-Aufrollung wie unten beschrieben sind, ist, dass JavaOS keine
Seitenauslagerung und keinen virtuellen Speicher unterstützt. Ein
objektorientiertes Programmiersystem wie Java kann in Verbindung
mit dem Betriebssystem laufen und Betriebssystemaufrufe von Java-Programmen oder Anwendungen,
die auf dem Datenverarbeitungssystem 250 ausgeführt werden,
ermöglichen.
Instruktionen für
das Betriebssystem, das objektorentierte Betriebssystem und Anwendungen
oder Programme befinden sich in Speichervorrichtungen wie z.B. auf dem
Festplattenlaufwerk 276 und können zur Ausführung durch
den Prozessor 252 in den Hauptspeicher 254 geladen
werden. Wenn das Datenverarbeitungssystem 250 als Netzwerk-Client
verwendet wird, ist oft kein Festplattenlaufwerk vorhanden und der
Speicher begrenzt.
-
Der
durchschnittliche Fachmann weiß,
daß die
in 2B dargestellte Hardware je nach Implementierung
unterschiedlich aussehen kann. So können zum Beispiel zusätzlich zu
der in 2B dargestellten Hardware oder
anstelle der in 2B dargestellten Hardware andere
Peripheriegeräte
wie optische Plattenlaufwerke oder ähnliches verwendet werden.
Das abgebildete Beispiel soll keine Einschränkungen der Architektur für die vorliegende
Erfindung implizieren. So können
die erfindungsgemäßen Prozesse
beispielsweise auf ein Multiprozessor-Datenverarbeitungssystem angewendet
werden.
-
Die
vorliegende Erfindung bietet einen Prozess und ein System zur Profilierung
von Software-Anwendungen. Sie kann auf verschiedenen Computerplattformen
und Betriebssystemen eingesetzt werden, u.a. in einer Java-Laufzeitumgebung.
Die vorliegende Erfindung kann daher in Verbindung mit einer JVM
(Java Virtual Machine) eingesetzt werden und sich dennoch innerhalb
der Grenzen einer JVM nach den Spezifikationen des Java-Standards
bewegen. Um einen Kontext für
die vorliegende Erfindung herzustellen, wird hier zum Teil auch
die Funktionsweise einer JVM nach den Java-Spezifikationen beschrieben.
-
3A ist
ein Blockdiagramm, in dem die Beziehung zwischen den Software-Komponenten
in einem Computersystem, in dem die vorliegende Erfindung implementiert
ist, aufgezeigt wird. Das Java-basierte System 300 enthält das plattformspezifische
Betriebssystem 302, das Hardware- und Systemunterstützung für die auf
einer bestimmten Hardware-Plattform laufende Software bietet. JVM 304 ist ein
Anwendungsprogramm, das in Verbindung mit dem Betriebssystem ausgeführt werden
kann. Die JVM 304 bildet eine Java-Laufzeitumgebung mit
der Möglichkeit,
Java-Anwendung oder
Applet 306, d.h. ein Programm, ein Servlet oder eine Softwarekomponente
in der Programmiersprache Java, auszuführen. Das Computersystem, in
dem JVM 304 läuft, kann ähnlich sein
wie das Datenverarbeitungssystem 200 oder das Computersystem 100,
die oben beschrieben wurden. JVM 304 kann aber auch auf
einer dedizierten Hardware auf einem sogenannten Java-Chip, Java-on-Silicon
oder Java-Prozessor mit eingebettetem picoJava-Kern implementiert
sein.
-
Im
Zentrum einer Java-Laufzeitumgebung steht die JVM, die alle Aspekte
der Java-Umgebung einschließlich
Architektur, Sicherheitsfunktionen, Mobilität zwischen Netzwerken und Plattformunabhängigkeit
unterstützt.
-
Die
JVM ist ein virtueller Computer, d.h. ein Computer, der abstrakt
spezifiziert ist. Die Spezifikation definiert bestimmte Merkmale,
die jede JVM implementieren muss, wobei je nach Plattform, auf der die
JVM laufen soll, bestimmte Auswahlmöglichkeiten bestehen. So müssen alle
JVMs Java-Bytecode ausführen, und
sie können
verschiedene Verfahren verwenden, um die durch die Bytecodes repräsentierten Instruktionen
auszuführen.
Eine JVM kann komplett als Software oder teilweise als Hardware implementiert
werden. Durch diese Flexibilität
können
verschiedene JVMs für
Großrechern
und PDAs entwickelt werden.
-
Die
JVM ist der Name einer virtuellen Computerkomponente, die Java-Programme
tatsächlich ausführt. Java-Programme
werden nicht direkt von der CPU ausgeführt, sondern von der JVM, die
ihrerseits eine auf dem Prozessor laufende Software ist. Die JVM
ermöglicht
die Ausführung
von Java-Programmen auf einer anderen Plattform als derjenigen, für die der
Code kompiliert worden ist. Java-Programme werden für die JVM
kompiliert. Auf diese Weise kann Java Anwendungen für viele
verschiedene Arten von Datenverarbeitungssstemen unterstützen, die
verschiedene CPUs und Betriebssystemarchitekturen aufweisen können. Damit
eine Java-Anwendung auf verschiedenen Arten von Datenverarbeitungssystemen
ausgeführt
werden kann, generiert ein Compiler typischerweise ein architekturneutrales Dateiformat – der kompilierte
Code kann auf vielen Prozessoren ausgeführt werden, sofern das Java-Laufzeitsystem
vorhanden ist. Der Java-Compiler generiert Bytecode-Instruktionen,
die nicht für
eine bestimmte Computerarchitektur spezifisch sind. Ein Bytecode
ist ein maschinenunabhängiger
Code, der vom Java-Compiler
generiert und von einem Java-Interpreter ausgeführt wird. Ein Java-Interpreter
ist ein Teil der JVM, der abwechselnd einen oder mehrere Bytecodes
decodiert und interpretiert. Diese Bytecode-Instruktionen sind so
konzipiert, dass sie auf jedem Computer einfach zu interpretieren
und leicht auf der Stelle in nativen Maschinencode umgewandelt werden
können.
Bytecodes können
durch einen JIT-Compiler (Just-in-Time Compiler) in nativen Code umgewandelt
werden.
-
Eine
JVM muss Klassendateien laden und die Bytecodes darin ausführen. Die
JVM enthält
einen Klassenlader, der Klassendateien von einer Anwendung und die
von der Anwendung benötigten Klassendateien
von den Java-Anwendungsprogrammierschnittstellen
(APIs) lädt.
Die Ausführungsmaschine,
die den Bytecode ausführt,
kann je nach Plattform und Implementierung unterschiedlich gestaltet
sein. Eine Art software-basierter Ausführungsmaschine ist ein JIT-Compiler. Bei dieser
Art der Ausführung
wird der Bytecode eines Verfahrens in nativen Maschinencode kompiliert,
wenn bestimmte Kriterien für
das Jitting eines Verfahrens erfüllt
sind. Der native Maschinencode für
das Verfahren kommt dann in den Cache und wird beim nächsten Aufruf des
Verfahrens verwendet. Die Ausführungsmaschine
kann auch als Hardware implementiert und in einen Chip integriert
werden, so dass die Java-Bytecodes nativ ausgeführt werden. Üblicherweise
interpretieren JVMs den Bytecode, sie können aber auch andere Verfahren
wie die JIT-Kompilierung verwenden, um Bytecodes auszuführen.
-
Die
Interpretation des Codes bietet einen zusätzlichen Vorteil. Anstelle
des Java-Quellcodes kann der Interpreter instrumentiert werden.
Protokolldaten können über ausgewählte Ereignisse
und Timer durch den instrumentierten Interpreter generiert werden,
ohne dass der Quellcode modifiziert wird. Die Instrumentation der
Profilierung wird weiter unten ausführlicher beschrieben.
-
Wenn
eine Anwendung auf einer JVM ausgeführt wird, die in Form von Software
auf einem plattformspezifischen Betriebssystem implementiert ist,
kann eine Java-Anwendung durch Aufruf nativer Verfahren mit dem
Host-Betriebssystem interagieren. Ein Java-Verfahren ist in der
Java-Sprache geschrieben, in Bytecodes kompiliert und in Klassendateien gespeichert.
Ein natives Verfahren ist in einer anderen Sprache geschrieben und
in den nativen Maschinencode eines bestimmten Prozessors kompiliert. Native
Verfahren werden in einer dynamisch verknüpften Bibliothek (dynamically
linked library – DLL) gespeichert,
deren exakte Form plattformspezifisch ist. 3B ist
ein Blockdiagramm einer JVA gemäß einer
bevorzugten Ausführungsform
der vorliegenden Erfindung. JVM 350 enthält ein Klassenlader-Subsystem 352,
bei dem es sich um einen Mechanismus zum Laden von Typen wie Klassen
und Schnittstellen mit vollständig
qualifizierten Namen handelt. Die JVM 350 enthält auch
Laufzeit-Datenbereiche 354, die Ausführungsmaschine 356,
die Schnittstelle 358 für
native Verfahren und die Speicherverwaltung 374. Die Ausführungsmaschine 356 ist
ein Mechanismus zum Ausführen
von Instruktionen, die in den vom Klassenlader-Subsystem 352 geladenen
Klassenmethoden enthalten sind. Die Ausführungsmaschine 356 kann
beispielsweise ein Java-Interpreter 362 oder
ein JIT-Compiler 360 sein. Die Schnittstelle 358 für native
Verfahren ermöglicht den
Zugriff auf Ressourcen im zugrunde liegenden Betriebssystem. Die
Schnittstelle 358 für
native Verfahren kann beispielsweise eine native Java-Schnittstelle
sein.
-
Die
Laufzeitdatenbereiche 354 enthalten Stacks 364 für native
Verfahren, Java-Stacks 366, PC-Register 368, einen
Methodenbereich 370 und einen Heap 372. Diese
verschiedenen Datenbereiche repräsentieren
die Speicherorganisation, die von der JVM 350 benötigt wird,
um ein Programm auszuführen.
-
Die
Java-Stacks 366 dienen zur Speicherung des Status von Java-Verfahrensaufrufen.
Wenn ein neuer Thread angefangen wird, erzeugt die JVM einen neuen
Java-Stack für
den Thread. Die JVM führt
nur zwei Operationen direkt an Java-Stacks aus: das Schieben und
Wegnehmen von Frames. Der Java-Stack eines Threads speichert den
Status von Java-Verfahrensaufrufen für diesen Thread. Der Status
eines Java-Verfahrensaufrufs umfasst dessen lokale Variablen, die
Parameter, mit denen er aufgerufen wurde, ggf. seinen Rückkehrwert
sowie Zwischenrechnungen. Java-Stacks sind aus Stack Frames zusammengesetzt.
Ein Stack Frame enthält
den Status eines einzigen Java-Verfahrensaufrufs. Wenn ein Thread
ein Verfahren aufruft, schiebt die JVM einen neuen Frame auf den
Java-Stack des Threads. Wenn das Verfahren abgeschlossen ist, nimmt
die JVM den Rahmen für
dieses Verfahren weg und löscht
ihn. Die JVM besitzt keine Register zur Speicherung von Zwischenwerten;
jede Java-Instruktion, die einen Zwischenwert benötigt oder
erzeugt, verwendet den Stack zur Speicherung der Zwischenwerte.
Dadurch ist der Java-Instruktionssatz für verschiedene Plattformarchitekturen
exakt definiert.
-
Die
PC-Register 368 enthalten Informationen darüber, welche
Instruktion als nächste
auszuführen
ist. Jeder Thread, von dem eine Instanz gebildet wird, erhält sein
eigenes PC-Register (Programmzähler)
und seinen eigenen Java-Stack. Wenn der Thread ein JVM-Verfahren
ausführt,
gibt der Wert im PC-Register
an, welche Instruktion als nächste auszuführen ist.
Wenn der Thread ein natives Verfahren ausführt, ist der Inhalt des PC-Registers
undefiniert.
-
Die
Stacks 364 für
native Verfahren speichern den Status von Aufrufen nativer Verfahren.
Der Status von Aufrufen nativer Verfahren wird auf eine von der
Implementierung abhängigen
Weise in Stacks für
native Verfahren, Registern oder anderen von der Implementierung
abhängigen
Speicherbereichen gespeichert. In einigen JVM-Implementierungen
sind Stacks 364 für
native Verfahren und Java-Stacks 366 kombiniert.
-
Der
Verfahrensbereich 370 enthält Klassendaten, während Heap 372 alle
Objekte, von denen eine Instanz gebildet wurde, enthält. In den JVM-Spezifikation
sind Datentypen und Operationen streng definiert. Die meisten JVMs
entscheiden sich für
einen Verfahrensbereich und einen Heap, die beide von allen in der
JVM laufenden Threads gemeinsam benutzt werden. Wenn eine JVM eine
Klassendatei lädt,
analysiert sie Informationen über
einen Typ aus den in der Klassendatei enthaltenen Binärdaten. Diese
Typinformationen stellt sie in den Verfahrensbereich. Jedesmal,
wenn eine Klasseninstanz oder Matrix erzeugt wird, wird Speicher
von Heap 372 für das
neue Objekt zugeteilt. Die JVM 350 enthält eine Instruktion, die Speicherplatz
im Speicher für
Heap 372 zuteilt, sie enthält aber keine Instruktion zum Freigeben
dieses Speicherbereichs. Die Speicherverwaltung 374 in
dem dargestellten Beispiel verwaltet den Speicherplatz in dem Speicher,
der Heap 370 zugeteilt ist. Die Speicherverwaltung 374 kann
einen "Müllsammler" enthalten, der automatisch
den Speicher zurückfordert,
der von Objekten benutzt wird, auf die es keinen Verweis mehr gibt.
Außerdem
kann ein "Müllsammler" auch Objekte verschieben,
um die Fragmentierung des Heap zu reduzieren.
-
Die
Prozesse in den folgenden Figuren geben einen globalen Überblick über die
zahlreichen Prozesse, die in der Erfindung verwendet werden: Prozesse,
die ereignisbasierte Profilierungsinformationen generieren; Prozesse,
die auf Abtastung basierende Profilierungsinformationen generieren;
Prozesse, die die Profilierungsinformationen zur Erzeugung nützlicherer
Informationen wie Darstellungen von Aufrufstack-Bäumen,
die in Profilberichte geschrieben werden, verwenden; und Prozesse,
die die Profilberichte für
den Benutzer des Profilierungsdienstprogramms generieren.
-
4 ist
ein Blockdiagramm, in dem die Komponenten dargestellt sind, die
zur Profilierung von Prozessen in einem Datenverarbeitungssystem verwendet
werden. Zur Profilierung der Prozesse 402 wird ein Ablaufverfolgungsprogramm 400 verwendet.
Das Ablaufverfolgungsprogramm 400 kann zur Aufzeichnung
von Daten bei der Ausführung
eines Ankers verwendet werden. Bei diesem handelt es sich um einen
speziellen Code an einer bestimmten Stelle in einer Routine oder
in einem Programm, wo andere Routinen ansetzen können. Ablaufverfolgungsanker
werden typischerweise für
die Fehlersuche, für
die Leistungsanalyse oder zur Erweiterung der Funktionalität eingefügt. Diese
Ablaufverfolgungsanker werden dazu benutzt, Protokolldaten an das
Ablaufverfolgungsprogramm 400 zu senden, das die Protokolldaten
im Puffer 404 speichert. Die Protokolldaten im Puffer 404 können anschließend zur Weiterverarbeitung
in einer Datei gespeichert oder in Echtzeit verarbeitet werden.
-
Bei
Java-Betriebssystemen benutzt die vorliegende Erfindung Ablaufverfolgungsanker,
die helfen, die Verfahren zu identifizieren, die in den Prozessen 402 verwendet
werden. Da Klassen geladen und entladen werden können, können diese Änderungen ebenfalls mit Hilfe
von Protokolldaten identifiziert werden. Dies ist vor allem bei
Netzwerk-Client-Datenverarbeitungssystemen
wie den unter JavaOS laufenden Systemen relevant, da Klassen und JIT-Verfahren
möglicherweise
wegen des beschränkten
Speicherplatzes und der Rolle als Netzwerk-Client häufiger geladen
und entladen werden. Es sei darauf hingewiesen, dass Informationen über das
Laden und Entladen von Klassen auch in eingebetteten Anwendungsumgebungen,
in denen meist nur beschränkt
Speicherplatz zur Verfügung
steht, relevant sind.
-
In 5 sind
verschiedene Phasen bei Profilierung der in einem Betriebssystem
aktiven Prozesse in einem Diagramm dargestellt. Im Rahmen der Speichermöglichkeiten
kann die generierte Protokollausgabe so lang und so detailliert
sein wie der Analysierende es für
den Zweck der Profilierung eines bestimmten Programms für erforderlich
hält.
-
In
einer Initialisierungsphase 500 wird der Status der Client-Maschine zu Beginn
der Protokollierung erfasst. Diese Protokollinitialisierungsdaten enthalten
Protokolldatensätze, die
alle vorhandenen Threads, alle geladenen Klassen und alle Verfahren für die geladenen
Klassen identifizieren. Von den von Ankern erfassten Protokolldaten
werden Datensätze geschrieben,
die über
Umschaltungen zwischen Threads, Interrupts und das Laden und Entladen
von Klassen und gejitteten Verfahren informieren. Für jede geladene
Klasse gibt es Protokolldatensätze,
in denen der Name der Klasse und ihre Verfahren angegeben sind.
In dem dargestellten Beispiel werden vier Byte lange IDs als Kennungen
für Threads,
Klassen und Verfahren verwendet. Diese IDs werden Namen zugeordnet,
die in den Datensätzen
ausgegeben worden sind. Es wird ein Datensatz geschrieben, der signalisiert,
wenn die Erfassung aller Startdaten abgeschlossen ist.
-
Dann
werden in der Profilierungsphase 502 Protokolldatensätze in einen
Protokollpuffer oder in eine Protokolldatei geschrieben. In der
vorliegenden Erfindung kann ein Protokollpuffer eine Kombination von
Satztypen enthalten, z.B.
-
Sätze, die
von einem Ablaufverfolgungsanker stammen, der als Reaktion auf eine
bestimmte Art von Ereignis, z.B. einen Aufruf oder die Beendigung
eines Verfahrens, ausgeführt
wird, und Sätze, die
von einer Stackdurchlauffunktion stammen, die als Reaktion auf einen
Timer-Interrupt ausgeführt werden,
z.B. ein Stack-Aufrollsatz, der auch als Aufrufstack-Datensatz bezeichnet
wird.
-
In
der Profilierungsphase können
beispielsweise folgende Prozesse vorkommen, falls der Benutzer des
Profilierungsdienstprogramms auf Abtastung basierende Profilierungsinformationen
angefordert hat. Jedesmal, wenn eine bestimmte Art von Timer-Interrupt
auftritt, wird ein Protokolldatensatz geschrieben, der den Systemprogrammzähler angibt. Dieser
Systemprogrammzähler
kann zur Identifikation der unterbrochenen Routine verwendet werden. In
dem dargestellten Beispiel wird ein Timer-Interrupt dazu benutzt, die
Erfassung von Protokolldaten einzuleiten. Selbstverständlich können nicht
nur Timer-Interrupts verwendet werden, sondern auch andere Arten
von Interrupts. Es können
auch Interrupts verwendet werden, die auf einem programmierten Leistungsüberwachungsereignis
oder anderen Arten periodisch wiederkehrender Ereignisse basieren.
-
In
der Nachverarbeitungsphase 504 werden die im Protokollpuffer
gesammelten Daten zur Nachverarbeitung an eine Protokolldatei gesendet.
In einer Konfiguration kann die Datei an einen Server gesendet werden,
der das Profil für
die Prozesse auf der Client-Maschine ermittelt. Selbstverständlich kann
die Nachverarbeitung, je nachdem, wieviel Systemressourcen verfügbar sind,
auch auf der Client-Maschine stattfinden. In der Nachverarbeitungsphase 504 können B-Bäume und/oder
Hash-Tabellen verwendet werden, um die Namen, die den Datensätzen in
der zu verarbeitenden Protokolldatei zugeordnet sind, zu verwalten.
Eine Hash-Tabelle verwendet die Hashing-Technik, um eine Kennung oder einen Schlüssel, die
für einen
Benutzer eine Bedeutung tragen, in einen Wert für die Position der entsprechenden
Daten in der Tabelle umzuwandeln. Während der Verarbeitung von
Protokolldatensätzen
werden die B-Bäume
und/oder Hash-Tabellen aktualisiert, so dass sie den aktuellen Status
der Client-Maschine einschließlich
des neu geladenen JIT-Codes oder des entladenen Codes wiedergeben.
Außerdem
werden in der Nachverarbeitungsphase 504 alle Protokolldatensätze der
Reihe nach verarbeitet. Sobald der Indikator, dass alle Startinformationen
verarbeitet worden sind, gefunden wird, werden die Protokolldatensätze von
Ablaufverfolgungsankern und die Protokolldatensätze von Timer-Interrupts verarbeitet.
Timer-Interrupt-Informationen aus den Timer-Interrupt-Datensätzen werden
anhand der vorhandenen Hash-Tabellen aufgelöst. Außerdem identifizieren diese
Informationen den Thread und die Funktion, die ausgeführt werden.
Die Daten werden in Hash-Tabellen gespeichert, wobei ein Zählerwert
die Anzahl der Ticks für
jede Art der Betrachtung der Daten zählt. Wenn alle Protokolldatensätze verarbeitet
worden sind, werden die Informationen für die Ausgabe in Form eines
Berichts formatiert.
-
Alternativ
können
die Protokollinformationen an Ort und Stelle verarbeitet werden,
so dass die Protokolldatenstrukturen während der Profilierungsphase
verwaltet werden. Mit anderen Worten: Während eine Profilierungsfunktion
wie ein Timer-Interrupt ausgeführt wird,
anstatt dass Protokolldatensätze
in einen Puffer oder in eine Datei geschrieben werden (oder zusätzlich dazu),
wird die in den Protokolldatensätzen
enthaltene Information verarbeitet, um geeignete Datenstrukturen
zu erstellen und zu verwalten.
-
Zum
Beispiel könnte
während
der Verarbeitung eines Timer-Interrupts
in der Profilierungsphase festgestellt werden, ob der unterbrochene
Code gerade vom Java-Interpreter interpretiert wird. Wenn der unterbrochene
Code tatsächlich
gerade interpretiert wird, kann die Verfahrenskennung des interpretierten
Verfahrens in den Protokolldatensatz geschrieben werden. Außerdem kann
der Name des Verfahrens ermittelt und in den entsprechenden B-Baum
geschrieben werden. Nach Abschluss der Profilierungsphase können die
Datenstrukturen alle zur Erstellung eines Profilberichts erforderlichen
Informationen enthalten, ohne dass die Protokolldatei einer Nachverarbeitung
bedarf.
-
6 ist
ein Flussdiagramm, in dem ein von einem Ablaufverfolgungsprogramm
verwendeter Prozess zur Generierung von Protokolldatensätzen aus
den auf einem Datenverarbeitungssystem ausgeführten Prozessen dargestellt
ist. 6 zeigt weitere Details über die Generierung von Protokolldatensätzen, auf
die im Zusammenhang mit 5 nicht eingegangen wurde.
-
Protokolldatensätze können durch
Ausführung
kleiner Codestücke,
den sogenannten "Ankern" hooks erzeugt werden.
Anker können
auf verschiedene Weise in den von Prozessen ausgeführten Code
eingefügt
werden, z.B. statisch (Quellcode) oder dynamisch (durch Modifikation
eines geladenen ausführbaren
Codes). Dieser Prozess wird angewendet, nachdem bereits Anker in
den relevanten Prozess oder die relevanten Prozesse eingefügt worden sind.
Der Prozess beginnt damit, dass ein Puffer wie Puffer 404 in 4 zugeteilt
wird (Schritt 600). Anschließend werden in dem dargestellten
Beispiel Ablaufverfolgungsanker aktiviert (Schritt 602),
und die Protokollierung der Prozesse im System beginnt (Schritt 604).
Von den interessierenden Prozessen werden Protokolldaten empfangen
(Schritt 606). Diese Art der Protokollierung kann in den
Phasen 500 und/oder 502 erfolgen. Diese Protokolldaten
werden als Protokolldatensätze
im Puffer gespeichert (Schritt 608). Nun wird festgestellt,
ob die Protokollierung beendet ist (Schritt 610). Die Protokollierung
endet, wenn der Protokollpuffer voll ist, oder wenn der Benutzer
die Protokollierung durch einen Befehl beendet und anfordert, dass
der Pufferinhalt an eine Datei gesendet wird. Ist die Protokollierung
noch nicht abgeschlossen, kehrt der Prozess zu dem oben beschriebenen
Schritt 606 zurück.
-
Andernfalls,
wenn die Protokollierung abgeschlossen ist, wird der Pufferinhalt
zur Nachverarbeitung an eine Datei gesendet (Schritt 612).
In der Nachverarbeitung wird dann ein Bericht erstellt (Schritt 614),
und danach endet der Prozess.
-
In
dem beschriebenen Beispiel findet eine Nachverarbeitung statt, um
die Protokolldatensätze zu
analysieren; die erfindungsgemäßen Prozesse können je
nach Implementierung aber auch zur Verarbeitung von Protokollinformationen
in Echtzeit verwendet werden.
-
In 7 ist
in einem Flussdiagramm ein Prozess dargestellt, der während eines
Interrupt-Handler-Ablaufverfolgungsankers verwendet werden kann.
-
Der
Prozess beginnt damit, dass ein Programmzähler abgelesen wird (Schritt 700).
Typischerweise steht der Programmzähler in einem der gespeicherten
Programm-Stack-Bereiche zur Verfügung.
Anschließend
wird festgestellt, ob es sich bei dem unterbrochenen Code um interpretierten
Code handelt (Schritt 702). Diese Feststellung kann getroffen
werden, indem festgestellt wird, ob der Programmzähler in
einem Adressbereich für
den zur Interpretation von Bytecodes verwendeten Interpreter steht.
Wenn es sich bei dem unterbrochenen Code um einen interpretierten
Code handelt, wird für
den interpretierten Code eine Verfahrensblockadresse ermittelt.
Dann wird ein Protokolldatensatz geschrieben (Schritt 706).
Der Protokolldatensatz wird geschrieben, indem die Protokollinformation
an ein Ablaufverfolgungsprogramm wie das Ablaufverfolgungsprogramm 400 gesendet
wird, das im dargestellten Beispiel Protokolldatensätze zur
Nachverarbeitung generiert. Dieser Protokolldatensatz wird als Interrupt-Datensatz
oder Interrupt-Anker beschrieben.
-
Diese
Art der Protokollierung kann während der
Phase 502 durchgeführt
werden. Alternativ kann ein entsprechender Prozess, d.h. die Feststellung,
ob es sich bei dem unterbrochenen Code um einen interpretierten
Code handelt, bei der Nachverarbeitung einer Protokolldatei stattfinden.
-
Zur
Gewinnung von auf Abtastung basierenden Profilierungsinformationen
kann eine Gruppe von Prozessen verwendet werden. Bei der Ausführung von
Anwendungen können
diese periodisch unterbrochen werden, um Informationen über die
aktuelle Laufzeitumgebung zu gewinnen. Diese Informationen können zur
Nachverarbeitung in einen Puffer oder eine Datei geschrieben werden,
oder sie können sofort
an Ort und Stelle zu Datenstrukturen verarbeitet werden, die eine
fortlaufende Geschichte der Laufzeitumgebung wiedergeben. In 8 und 9 wird
die auf Abtastung basierende Profilierung ausführlicher beschrieben.
-
Ein
auf Abtastung basierendes Profilierungsdienstprogramm kann Informationen
aus dem Stack eines unterbrochenen Threads gewinnen. Der Thread
wird durch einen Software-Timer-Interrupt wie er in vielen Betriebssystemen
zur Verfügung steht,
unterbrochen. Der Benutzer der Protokollierungseinrichtung wählt entweder
die Programmzähleroption
oder die Stack-Aufschlüsselungsoption. Dies
kann dadurch erreicht werden, dass ein Hauptcode oder ein anderer
Hauptcode aktiviert wird wie weiter unten beschrieben. Dieser Timer-Interrupt
wird zur Abtastung von Informationen von einem Aufrufstack verwendet.
Indem der Aufrufstack von hinten aufgerollt wird, kann ein kompletter
Aufrufstack für die
Analyse abgefragt werden. Das "Durchlaufen" eines Stacks kann
auch als "Aufrollen" des Stacks beschrieben
werden. Diese Begriffe sind Metaphern für den Prozess. Der Prozess
kann insofern als "Durchlaufen" beschrieben werden,
als der Prozess die Stack Frames Schritt für Schritt oder Frame für Frame
abrufen und verarbeiten muss. Der Prozess kann insofern auch als "Aufrollen" bezeichnet werden,
als er die Stack Frames, die auf einander verweisen, abrufen und
verarbeiten muss, und diese Zeiger und ihre Informationen müssen durch
viele Verweisaufhebungen hindurch "abgerollt" werden.
-
Das
Abrollen des Stacks folgt der Reihenfolge der Funktionen/Verfahrensaufrufe
zum Zeitpunkt der Unterbrechung. Ein Aufrufstack ist eine geordnete
Auflistung von Routinen plus der Offsets in den Routinen (d.h. Module,
Funktionen, Verfahren usw.), deren Abarbeitung bei der Ausführung eines
Programms begonnen wurde. Wenn beispielsweise Routine A Routine
B aufruft und Routine B dann Routine C aufruft, während der
Prozessor Instruktionen in Routine C aufruft, lautet der Aufrufstack
ABC. Wenn die Steuerung von Routine C zu Routine B zurückgegeben
wird, lautet der Aufrufstack AB. Zur kompakteren Darstelllung und
einfacheren Interpretation in einem generierten Bericht werden die
Namen der Routinen ohne Informationen über Offsets präsentiert. Offsets
könnten
für eine
detailliertere Analyse der Ausführung
eines Programms verwendet werden, werden hier aber nicht weiter
behandelt.
-
Während der
Timer-Interrupt-Verarbeitung oder bei der Nachverarbeitung spiegelt
die generierte auf Abtastung basierende Profilinformation eine Abtastung
von Aufrufstacks wider, und nicht nur Blätter der möglichen Aufrufstacks wie in
manchen Programmzähler-Abtastverfahren.
Ein Blatt ist ein Knoten am Ende eines Zweiges, d.h. ein Knoten,
der keine weitere Abkömmlinge
aufweist. Ein Abkömmling ist
ein Kind eines Elternknotens, und ein Blatt ist ein Knoten, der
keine Kinder hat.
-
In 8 ist
der Aufrufstack mit den enthaltenen Stack Frames in einem Diagramm
dargestellt. Ein "Stack" ist ein reservierter
Speicherbereich, in dem ein Programm oder mehrere Programme Statusdaten
wie z.B. Prozedur- und Funktionsaufrufadressen, übergebene Parameter und gelegentlich auch
lokale Variablen speichern. Ein "Stack
Frame" ist ein Teil
des Stacks eines Threads, der einen lokalen Speicher (Argumente,
Rückkehradressen,
Rückgabewerte
und lokale Variablen) für
einen einzigen Funktionsaufruf darstellt. Jedem aktiven Ausführungs-Thread
ist ein Teil des Systemspeichers als Stack-Bereich zugeteilt. Der
Stack eines Threads besteht aus Sequenzen von Stack Frames. Die
Frames im Stack eines Threads geben den Ausführungsstatus des Threads zu
jedem Zeitpunkt wieder. Das Stack Frames typischerweise miteinander
verknüpft sind
(so verweist z.B. jeder Stack Frame auf den vorhergehenden Stack
Frame), ist es oft möglich,
die Abfolge der Stack Frames zurückzuverfolgen
und den "Aufrufstack" zu entwickeln. In
einem Aufrufstack sind alle noch nicht abgeschlossenen Funktionsaufrufe
gespeichert, d.h. der Aufrufstack spiegelt die Reihenfolge der Funktionsaufrufe
zu jedem Zeitpunkt wider.
-
Aufrufstack 800 enthält Informationen
zur Identifikation der gerade ablaufenden Routine, der Routine,
von der sie aufgerufen wurde usw., bis zurück zum Hauptprogramm. Aufrufstack 800 enthält eine
Anzahl von Stack Frames 802, 804, 806 und 808.
In dem dargestellten Beispiel befindet sich Stack Frame 802 oben
auf dem Aufrufstack 800 und Stack Frame 808 an
der untersten Position von Aufrufstack 800. Der oberste
Stack Frame des Aufrufstacks wird auch als "Wurzel" bezeichnet. Der Timer-Interrupt (den
es in den meisten Betriebssystemen gibt) wird so abgewandelt, dass
der Programmzählerwert
(pcv) des unterbrochenen Thread zusammen mit dem Zeiger auf den
gerade aktiven Stack Frame für
diesen Thread ermittelt wird. In der Intel-Architektur wird dies typischerweise
durch den Inhalt folgender Register wiedergegeben: EIP (Programmzähler) und
EBP (Zeiger auf Stack Frame). Durch den Zugriff auf den gerade aktiven
Stack Frame kann die (typische) Stack Frame-Verknüpfungskonvention genutzt werden,
um alle Frames miteinander zu verknüpfen. Ein Teil der Standardverknüpfungskonvention
schreibt auch vor, dass die Funktionsrückkehradresse genau über dem
Stack Frame der aufgerufenen Funktion liegen muss; dies kann dazu
verwendet werden, die Adresse für
die aufgerufene Funktion sicher zu bestimmen. Die Verwendung einer
Intel-basierten Architektur in dieser Erläuterung ist nicht als Einschränkung zu
betrachten. In den meisten Architekturen werden Verknüpfungskonventionen
verwendet, nach denen ein modifizierter Profilierungs-Interrupt-Handler
auf ähnliche
Weise navigieren kann.
-
Bei
einem Timer Interrupt wird als erster Parameter der Programmzählerwert
ermittelt. Der nächste
Wert ist der Zeiger auf das oberste Element des aktuellen Stack
Frame für
den unterbrochenen Thread. Im vorliegenden Beispiel würde dieser
Wert auf EBP 808a in Stack Frame 808 zeigen. EPB 808 verweist
wiederum auf EBP 806a in Stack Frame 806, und
dieser auf EPB 804a in Stack Frame 804. Dieser
EBP verweist auf EBP 802a in Stack Frame 802.
In den Stack Frames 802–808 befinden sich
die EIPs 802b–808b,
die die Rückkehradresse
der aufrufenden Routine bezeichnen. Die Routinen können anhand
dieser Adressen identifiziert werden. Die Routinen werden also definiert,
indem alle Rückkehradressen
vorwärts
oder rückwärts durch
den Stack verfolgt werden.
-
In 9 ist
ein Aufrufstack dargestellt. Ein Aufrufstack wie Aufrufstack 900 wird
gewonnen, indem der Aufrufstack durchlaufen wird. Jedesmal, wenn
ein periodisch wiederkehrendes Ereignis, z.B. ein Timer Interrupt,
stattfindet, wird ein Aufrufstack gewonnen. Diese Aufrufstacks können als
Aufrufstack-Abrollprotokollsätze
(oder einfach als "Stack-Abrollung") zur Nachverarbeitung
in der Protokolldatei gespeichert oder auf der Stelle während der
weiteren Programmausführung
verarbeitet werden.
-
In
dem vorliegenden Beispiel enthält
Aufrufstack 900 eine pid (Prozesskennung) 902 und
eine tid (Thread-Kennung) 904. Aufrufstack 900 enthält außerdem die
Adressen addr1 906, addr2 908 ... addrN 910.
In diesem Beispiel gibt addr1 906 den Wert des Programmzählers zum
Zeitpunkt des Interrupts an. Diese Adresse liegt irgendwo im Bereich der
unterbrochenen Funktion. addr2 908 ist eine Adresse in
dem Prozess, der die unterbrochene Funktion aufgerufen hat. Bei
Datenverarbeitungssystemen, die auf einem Intel-Prozessor basieren,
ist dies die Rückkehradresse
für diesen
Aufruf; wenn man von diesem Wert 4 subtrahiert, erhält man die Adresse
des eigentlichen Aufrufs, die Aufrufstelle. Diese entspricht EIP 808b in 8,
addrN 910 ist die Spitze des Aufrufstack (EIP 802b).
Der Aufrufstack, der zurückgegeben
würde,
wenn der Timer Interrupt den Thread unterbrechen würde, dessen
Aufrufstack-Zustand in 8 dargestellt ist, würde aus folgenden
Elementen bestehen: einer pid (Prozesskennung des unterbrochenen
Thread), einer tid (Thread-Kennung des unterbrochenen Thread), einer
pcv (Programmzählerwert
für den
unterbrochenen Thread, in 8 nicht
dargestellt), EIP 808b, EIP 806b, EIP 804b und
EIP 802b. In 9 ist pcv = addr1, EIP 808b =
addr2, EIP 806b = addr3, EIP 804b = addr4, EIP 802b =
addr5.
-
10A zeigt eine Programmausführungssequenz zusammen mit
dem Zustand des Aufrufstack an jedem Funktionseinstiegs- und Funktionsausstiegspunkt.
In dieser Zeichnung sind Einstiege und Ausstiege in regelmäßigen Abständen dargestellt;
dies dient aber nur der Vereinfachung der Darstellung. Wenn jede
Funktion (A, B, C und X in der Zeichnung) mit Ankern für Einstiegs-
und Ausstiegsereignisse instrumentiert wäre, wäre es einfach festzustellen,
wieviel Zeit in und unter jeder Funktion aufgewendet wird. In 10A befindet sich zu diesem Zeitpunkt 0 der ausgeführte Thread
in Routine C. Der Aufrufstack zum Zeitpunkt 0 lautet C. Zum Zeitpunkt 1
ruft Routine C Routine A auf, und der Aufrufstack wird zu CA usw.
Es sei darauf hingewiesen, dass der Aufrufstack in 10A ein rekonstruierter Aufrufstack ist, der generiert
wird, indem die ereignisbasierten Protokolldatensätze in einer
Protokolldatei so verarbeitet werden, dass die Ereignisse als Einstiege in
Methoden und Ausstiege aus Methoden verfolgt werden. Das dazu verwendete
Verfahren und die Datenstruktur werden weiter unten ausführlicher
beschrieben. Leider kann diese Art der Instrumentierung aufwendig
sein, zu Beeinträchtigungen
führen und
manchmal schwierig anzuwenden sein. Eine Profilierung auf Stichprobenbasis,
bei der die Abtastung auf den Aufrufstack des Programms beschränkt ist,
hilft, die Leistungseinbuße
und andere Komplikationen, die durch Ein-/Ausstiegsanker entstehen
können,
zu mindern.
-
In 10B wird das gleiche Programm ausgeführt, diesmal
aber in regelmäßigen Zeitabständen abgetastet.
In diesem Beispiel erfolgt die Unterbrechung mit einer Frequenz,
die zwei Zeitstempelwerten entspricht. Jede Abtastung enthält eine
Momentaufnahme des Aufrufstack des unterbrochenen Thread. Mit diesem
Verfahren sind nicht alle Aufrufstack-Kombinationen zu erkennen – wie hier
zu sehen ist, taucht Routine X in den Aufrufstack-Abtastungen in 10B überhaupt
nicht auf. Diesen Nachteil der Abtastung kann man in Kauf nehmen.
Die zugrunde liegende Idee besteht darin, dass bei einer geeigneten
Abtastrate (z.B. 30-1000 mal pro Sekunde) die Aufrufstacks identifiziert
werden, in denen die meiste Zeit aufgewendet wird. Dass einige Stacks fehlen
ist kein großes
Problem, vorausgesetzt, es handelt sich dabei um Kombinationen,
für die
wenig Zeit aufgewendet wird.
-
In
den ereignisbasierten Protokollen liegt eine fundamentale Annahme
zugrunde, dass die Protokolle Informationen über Einstiege in Routinen und
die zugehörigen
Ausstiege aus den Routinen enthalten. Oft sind diese Ein-/Ausstiegspaare
in den Protokollen verschachtelt, da Routinen andere Routinen aufrufen.
Die zwischen dem Einstieg in eine Routine und dem Ausstieg aus der
Routine aufgewendete Zeit (oder der verbrauchte Speicher) wird dieser Routine
zugeschrieben; ein Benutzer eines Profilierungs-Tools will aber
möglicherweise
zwischen der direkt in einer Routine aufgewendeten Zeit und der
in anderen von ihr aufgerufenen Routinen aufgewendeten Zeit unterscheiden.
-
In 10C ist an einem Beispiel dargestellt, wie Zeit
von zwei Routinen verbraucht werden kann: die "Hauptroutine" eines Programms ruft Routine A zum
Zeitpunkt t = 0 auf; Routine A rechnet 1 ms lang und ruft dann Routine
B auf; Routine B rechnet 8 ms lang und kehrt dann zu Routine A zurück; Routine
A rechnet 1 ms lang und kehrt dann zur "Hauptroutine" zurück.
Aus der Sicht der "Hauptroutine" benötigte Routine
A 10 ms für
die Ausführung,
davon wurde aber der größte Teil
der Zeit für
die Ausführung
von Instruktionen in Routine B verwendet und nicht für die Ausführung von
Instruktionen in Routine A. Dies ist eine nützliche Information für jemanden,
der versucht, das Beispielprogramm zu optimieren. Wenn Routine B
von vielen Stellen im Programm aus aufgerufen wird, könnte es
auch nützlich
sein, zu wissen, wieviel Zeit der in Routine B aufgewendeten Zeit
auf das Konto von (oder den Aufruf durch) Routine A zurückzuführen ist,
und wieviel auf andere Routinen.
-
Ein
wesentliches Konzept bei den Ergebnissen der hier beschriebenen
Methoden ist der Aufrufstack. Aufrufstack besteht aus der gerade
ablaufenden Routine, der Routine, von der sie aufgerufen wurde usw.,
bis zurück
zum Hauptprogramm. Ein Profilierer kann eine höhere Thread-Ebene mit der pid/tid
(Prozess- und Thread-Kennung) hinzufügen. In jedem Fall wird versucht,
die protokollierten Ereignisse wie Einstiege in Methoden und Ausstiege
aus Methoden zu verfolgen wie in 10A,
um die Struktur der Aufrufstack-Frames während der Programmausführung zu
verschiedenen Zeiten der Protokollierung zu rekonstruieren.
-
Die
Nachverarbeitung einer Protokolldatei kann einen Bericht ergeben,
in dem drei Arten der in einer Routine wie Routine A aufgewendeten
Zeit aufgeschlüsselt
sind: (1) die Basiszeit – d.h.
die Zeit, die zur Ausführung
von Code in Routine A selber aufgewendet wird; (2) die kumulative
Zeit (kurz "CUM-Zeit") – d.h. die
Zeit, die für
die Ausführung
in Routine A aufgewendet wird plus die gesamte Zeit, die für die Ausführung aller
von Routine A aufgerufenen Routinen (und aller von diesen Routinen aufgerufenen
Routinen usw.) aufgewendet wird; und (3) die nach der Uhr verstrichene
Zeit. Diese Art der Zeitinformation kann aus den ereignisbasierten
Protokolldatensätzen
herausgelesen werden, da jeder Datensatz mit einem Zeitstempel versehen
ist. Die kumulative Zeit einer Routine ist die Summe aller bei der Ausführung der
Routine aufgewendeten Zeiten plus der Zeit, die für die Ausführung anderer
Routinen aufgewendet wird, solange diese Routine im Aufrufstack darunter
liegt. In dem obigen Beispiel in 10C beträgt die Basiszeit
von Routine A 2 ms und ihre kumulative Zeit 10 ms. Die Basiszeit
von Routine B beträgt 8
ms, und ihre kumulative Zeit beträgt ebenfalls 8 ms, da sie keine
anderen Routinen aufruft. Es sei darauf hingewiesen, dass kumulative
Zeit nicht generiert werden kann, wenn ein Aufrufstack-Baum auf
der Stelle generiert wird – die
kumulative Zeit kann nur nachträglich
in der Nachverarbeitungsphase eines Profilierungsdienstprogramms
berechnet werden.
-
Bei
der verstrichenen Zeit verhält
es sich so, dass wenn während
der Ausführung
von Routine B das System einen Interrupt erzeugte oder diesen Thread
suspendierte, um einen anderen Thread auszuführen, oder wenn Routine B durch
das Warten in einem Wartezustand oder bei einer Ein-/Ausgabe blockiert
war, Routine B und alle Einstiege im Aufrufstack oberhalb von Routine
B verstrichene Zeit ansammeln, aber keine Basiszeit oder kumulative
Zeit. Basiszeit und kumulative Zeit werden nicht durch Interrupts,
Zuteilung oder Blockierung beeinflusst. Die Basiszeit nimmt nur
zu, während
eine Routine ausgeführt
wird, und die kumulative Zeit nimmt nur zu, während die Routine oder eine
im Aufrufstack unter ihr liegende Routine ausgeführt wird.
-
In
dem Beispiel in 10C ist die verstrichene Zeit
von Routine A mit ihrer kumulativen Zeit identisch – sie beträgt 10 ms.
Wandeln wir nun das Beispiel etwas ab und nehmen wir an, dass in
der Mitte von B eine 1 ms dauernde Unterbrechung stattgefunden hat
wie in 10D. Die Basiszeit und die kumulative
Zeit von Routine A betragen unverändert 2 ms und 10 ms, die verstrichene
Zeit beträgt
aber jetzt 11 ms.
-
Auch
wenn hier die Basiszeit, die kumulative Zeit und die verstrichene
Zeit als in Routinen aufgewendete Prozessorzeit definiert wurde,
ist die Profilierung für
den Verbrauch fast aller Systemressourcen für eine Gruppe von Routinen
nützlich.
Darauf wird weiter unten im Zusammenhang mit 11B ausführlicher
eingegangen. Wenn in 10C Routine A zwei Platten-E/A-Operationen
initiiert hat und dann Routine B bei ihrem Aufruf durch Routine
A drei weitere E/A-Operationen initiiert hat, hat Routine A zwei "Basis-E/As" und fünf "kumulative E/As". "Verstrichene E/As" wären alle
E/As einschließlich
derer von anderen Threads und Prozessen, die zwischen dem Einstieg
in Routine A und dem Ausstieg aus Routine A erfolgt sind. Allgemeinere
Definitionen der Berechnungskonzepte bei der Profilierung: Basis – die Menge
der protokollierten Systemressourcen, die direkt von dieser Routine
verbraucht worden sind; kumulativ – die Menge der protokollierten
Systemressourcen, die von dieser Routine und allen im Aufrufstack
unter ihr liegenden Routinen verbraucht worden sind; verstrichen – die gesamte
Menge der (von allen Routinen) verbrauchten protokollierten Systemressourcen
zwischen dem Einstieg in diese Routine und dem Ausstieg aus dieser
Routine.
-
11A ist ein Diagramm, in dem eine aus Protokolldaten
generierte Baumstruktur dargestellt ist. Diese Figur zeigt einen
Aufrufstack-Baum 1100, in dem jeder Knoten in der Baumstruktur 1100 einen Funktionseinstiegspunkt
darstellt.
-
Außerdem sind
in jedem Knoten in der Baumstruktur 1100 einige statistische
Angaben aufgezeichnet. In dem dargestellten Beispiel enthält jeder
Knoten 1102–1108 eine
Adresse (addr), eine Basiszeit (BASE), eine kumulative Zeit (CUM)
und Zeiger auf Eltern und Kinder. Wie erwähnt kann diese Art der Zeitinformation
aus den ereignisbasierten Protokolldatensätzen herausgelesen werden,
da jeder Datensatz mit einem Zeitstempel versehen ist. Die Adresse
stellt einen Funktionseinstiegspunkt dar. Die Basiszeit bezeichnet
die Zeit, die von dem Thread direkt für die Ausführung dieser Funktion aufgewendet wird.
Die kumulative Zeit ist die Zeit, die von dem Thread, der diese
Funktion ausführt,
und allen im Aufrufstack darunter liegenden Funktionen aufgewendet
wird. In dem dargestellten Beispiel sind für jeden Knoten Zeiger enthalten.
Ein Zeiger ist der Elternzeiger, d.h. ein Zeiger auf den Elternknoten.
Außerdem
enthält
jeder Knoten einen Zeiger auf jeden seiner Kindknoten.
-
Dem
durchschnittlichen Fachmann ist klar, dass die Baumstruktur 1100 auf
verschiedene Weise implementiert werden kann, und dass viele verschiedene
Arten von Statistiken an den Knoten geführt werden können und
nicht nur die im Beispiel dargestellten.
-
Der
Aufrufstack wird entwickelt, indem an allen Rückkehradressen zurückgeschaut
wird. Diese Rückkehradressen
lösen sich
in den Hauptteilen der Funktionen auf. Anhand dieser Informationen
kann zwischen verschiedenen Aufrufen der gleichen Funktion unterschieden
werden. Anders ausgedrückt, wenn
Funktion X zwei verschiedene Aufrufe von Funktion A hat, kann die
Zeit für
diese Aufrufe separat berechnet werden. In den meisten Berichten
wird allerdings keine solche Unterscheidung vorgenommen.
-
In 11B wird nun ein Aufrufstack-Baum beschrieben,
der alle in einem bestimmten Beispiel der Systemausführung beobachteten
Aufrufstacks reflektiert. An jedem Knoten des Baums werden verschiedene
statistische Angaben aufgezeichnet. In dem Beispiel in 11B handelt es sich dabei um zeitbasierte Statistikdaten.
Die dargestellten statistischen Angaben bezeichnen, wie oft der
Stack erzeugt wurde, wieviel Zeit insgesamt in dem Aufrufstack aufgewendet
wurde, wieviel Zeit insgesamt im Aufrufstack plus den von ihm aufgerufenen
Aufrufstacks aufgewendet wurde (kumulative Zeit), und wieviele Ausprägungen dieser
Routine über
dieser Ausprägung
stehen (Rekursionstiefe).
-
An
Knoten 1152 in 11B beispielsweise lautet
der Aufrufstack CAB, und zu diesem Knoten sind folgende statistische
Angaben vorhanden: 2:3:4:1. Aufrufstack CAB wird zum Zeitpunkt 2
in 10A zum ersten Mal erzeugt und wird zum Zeitpunkt
3 verlassen. Aufrufstack CAB wird zum Zeitpunkt 4 erneut erzeugt
und zum Zeitpunkt 7 wieder verlassen. Die erste Statistik besagt
also, dass dieser spezielle Aufrufstack, CAB, während der Protokollierung zwei
Mal erzeugt worden ist. Die zweite Statistik besagt, dass Aufrufstack
CAB drei Zeiteinheiten lang existiert (zum Zeitpunkt 2, zum Zeitpunkt
4 und zum Zeitpunkt 6). Die dritte Statistik bezeichnet die kumulative
Zeit in Aufrufstack CAB und den von Aufrufstack CAB aufgerufenen
Aufrufstacks (d.h. die Aufrufstacks, die CAB als Präfix haben,
z.B. CABB). Die kumulative Zeit in dem Beispiel aus 11B beträgt
vier Zeiteinheiten. Schließlich
hat Aufrufstack CAB die Rekursionstiefe eins, da keine der im Aufrufstack
enthaltenen Routinen rekursiv aufgerufen wurde.
-
Dem
Fachmann ist klar, dass die in 11B dargestellte
Baumstruktur auf verschiedene Weise implementiert werden kann, und
dass an jedem Knoten verschiedene Arten von Statistiken geführt werden
können.
In der hier beschriebenen Ausführungsform
enthält
jeder Knoten im Baum Daten und Zeiger. Die Datenelemente enthalten
den Namen der Routine an diesem Knoten und die vier erwähnten statistischen
Angaben. Selbstverständlich
können
an jedem Knoten noch viele andere Arten statistischer Informationen
gespeichert werden. In der hier beschriebenen Ausführungsform
enthalten die Zeiger für
jeden Knoten einen Zeiger auf den Elternknoten, einen Zeiger auf
den ersten Kindknoten (d.h. den am weitesten links stehenden Kindknoten),
einen Zeiger auf den nächsten
Geschwisterknoten und einen Zeiger auf die nächste Ausprägung einer bestimmten Routine im
Baum. In 11B enthält beispielsweise Knoten 1154 einen
Elternzeiger auf Knoten 1156, einen Kindzeiger auf Knoten 1158,
einen Geschwisterzeiger gleich NULL (da Knoten 1154 keinen
nächsten
Geschwisterknoten besitzt) und einen Ausprägungszeiger auf Knoten 1162.
Dem Fachmann ist klar, dass weitere Zeiger gespeichert werden können, um
die nachfolgende Analyse effizienter zu gestalten. Außerdem können andere
Strukturelemente wie Tabellen für
die Eigenschaften einer Routine, die über alle Ausprägungen konstant
sind, gespeichert werden, z.B. der Name der Routine. Die Art der
Leistungsdaten und Statistiken, die an jedem Knoten verwaltet werden,
ist nicht auf zeitbasierte Leistungsstatistiken beschränkt. Die
vorliegenden Informationen können zu
einer kompakten Darstellung vieler Arten von Protokollinformationen,
die viele Leistungsabfragen unterstützt, verwendet werden. So können beispielsweise
anstelle von Zeitstatistiken die Anzahl der in jeder aufgerufenen
Methode (d.h. Routine) ausgeführten
Java-Bytecodes protokolliert
werden. Die Baumstruktur in der vorliegenden Erfindung würde dann keine
zeitbasierten Statistiken, sondern Statistiken über die ausgeführten Bytecodes
enthalten. Insbesondere die Angaben in der zweiten und dritten Kategorie
würden
nicht die in jeder Methode aufgewendete Zeit, sondern die Anzahl
der ausgeführten
Bytecodes widerspiegeln.
-
Die
Protokollierung kann auch zur Verfolgung der Speicherzuteilung und
-freigabe verwendet werden. Jedesmal, wenn eine Routine ein Objekt
erstellt, könnte
ein Protokolldatensatz generiert werden. Die erfindungsgemäße Baumstruktur
würde dann
zur effizienten Speicherung und Abfrage von Informationen über die
Speicherzuteilung verwendet werden. Jeder Knoten würde die
Anzahl der Methodenaufrufe, den in einer Methode zugeteilten Speicherplatz,
den Speicherplatz, der von den von dieser Methode aufgerufenen Methoden
zugeteilt wird, und die Anzahl der Methoden über dieser Ausprägung (d.h.
die Rekursionstiefe) wiedergeben. Dem Fachmann ist klar, dass die
erfindungsgemäße Baumstruktur
zu einer sehr kompakten Darstellung verschiedener Leistungsdaten
verwendet werden kann und eine große Vielfalt von Leistungsabfragen
ermöglicht.
-
Die
in 11B dargestellte Baumstruktur zeigt eine Möglichkeit
der bildlichen Darstellung der Daten. Die gleichen Daten können einem
Benutzer auch in Tabellenform präsentiert
werden wie in 12.
-
In 12 wird
nun ein Aufrufstack-Baum in Tabellenform beschrieben. 12 enthält eine
Routine, pt pidtid, die der Hauptprozess bzw. Thread ist, der Routine
C aufruft. Tabelle 12 enthält
Datenspalten für
die Ebene 1230, RL 1232, Aufrufe 1234,
Basis 1236, CUM 1238 und Einrückung 1240. Ebene 1230 ist
die Baumebene (ausgehend von der Wurzel als Ebene 0) des Knotens.
RL 1232 ist die Rekursionsebene. Aufrufe 1234 gibt
an, wie oft dieser Aufrufstack vorkommt, d.h. wie oft genau diese
Aufrufstack-Konfiguration auftritt. Basis 1236 bezeichnet
die Gesamtzeit, die in dem betreffenden Aufrufstack beobachtet wurde,
d.h. die Gesamtzeit, während
der der Stack genau diese Routinen enthalten hat. CUM 1238 ist die
Gesamtzeit in dem betreffenden Aufrufstack plus den darunter liegenden
tieferen Ebenen. Einrückung 1240 gibt
die Ebene im Baum in Form einer Einrückung an. Aus dieser Art von Aufrufstack-Konfigurationsdaten
ist jede Aufrufstack-Konfiguration
ersichtlich, mit der Information, wie oft sie vorgekommen ist und
wie lange sie sich im Stack befunden hat. Diese Art von Information
zeigt auch die dynamische Struktur eines Programms, da zu erkennen
ist, welche Routine welche andere Routine aufgerufen hat. Sie vermittelt
aber keine Vorstellung über
die zeitliche Abfolge im Aufrufstack-Baum. Es kann nicht herausgelesen
werden, ob Routinen auf einer bestimmten Ebene vor oder nach anderen
Routinen auf der gleichen Ebene aufgerufen wurden.
-
Die
bildliche Darstellung des Aufrufstack-Baums wie in 11B kann dynamisch an Ort und Stelle oder statisch
mit einer Protokolldatei als Eingabe erstellt werden. 13 ist
ein Flussdiagramm, in dem ein Verfahren zur Erstellung eines Aufrufstack-Baums
mit Hilfe einer Protokolltextdatei als Eingabedatei dargestellt
ist. In 13 wird der Aufrufstack-Baum
aufgebaut, um Moduleinstiegs- und Modulausstiegspunkte aufzuzeigen.
-
In 13 wird
zuerst festgestellt, ob weitere Protokollsätze in der Protokolldatei vorhanden
sind (Schritt 1350). Ist dies der Fall, so werden mehrere Daten
aus dem Protokollsatz abgerufen, u.a. die Zeit, ob es sich um ein
Einstiegs- oder um ein Ausstiegsereignis handelt, und der Modulname
(Schritt 1352). Dann wird das letzte Zeitinkrement dem
aktuellen Knoten im Baum zugeschrieben (Schritt 1354).
Nun wird festgestellt, ob es sich um einen Einstiegs- oder einen
Ausstiegsdatensatz handelt (Schritt 1356). Wenn es sich
um einen Ausstiegsdatensatz handelt, wird der Baum bis zum Elternknoten
durchlaufen (mit Hilfe des Elternzeigers), und der aktuelle Baumknoten
wird mit dem Elternknoten gleichgesetzt (Schritt 1358).
Handelt es sich um einen Einstiegsdatensatz, so wird festgestellt,
ob das Modul bereits ein Kindknoten des aktuellen Baumknotens ist
(Schritt 1360). Ist dies nicht der Fall, wird ein neuer
Knoten für
das Modul erstellt und unterhalb des aktuellen Baumknotens an den
Baum angehängt
(Schritt 1362). Dann wird der Baum bis zum Knoten des Moduls
durchlaufen, und der aktuelle Baumknoten wird mit dem Modulknoten
gleichgesetzt (Schritt 1364). Die Anzahl der Aufrufe bis
zu aktuellen Baumknoten wird dann inkrementell erhöht (Schritt 1366).
Dieser Prozess wird für
jeden Protokolldatensatz in der Protokollausgabedatei wiederholt,
bis kein weiterer Protokolldatensatz mehr zu analysieren ist (Schritt 1368).
-
14 ist
ein Flussdiagramm, in dem ein Verfahren zur dynamischen Erstellung
eines Aufrufstack-Baums bei der Protokollierung während der Systemausführung dargestellt
ist. In 14 wird ein Ereignis bei seiner
Protokollierung in Echtzeit dem Baum hinzugefügt. Vorzugsweise wird für jeden Thread
ein separater Aufrufstack-Baum verwaltet. Der Aufrufstack-Baum zeigt
die bis zu diesem Zeitpunkt aufgezeichneten Aufrufstacks, und ein
Feld für den
aktuellen Baumknoten enthält
die Angabe der aktuellen Position in einem bestimmten Baum. Wenn ein
Ereignis eintritt (Schritt 1470), wird die Thread-Kennung
ermittelt (Schritt 1471). Dann werden die Zeit, die Art
des Ereignisses (d.h. in diesem Fall, ob es sich um einen Methodeneinstieg
oder einen Methodenausstieg handelt), der Name des Moduls (d.h.
der Methode), die Position des Aufrufstack des Threads und die Position
des "aktuellen Baumknotens" des Threads ermittelt
(Schritt 1472). Das letzte Zeitinkrement wird dem aktuellen
Knoten im Baum zugeschrieben (Schritt 1474). Nun wird festgestellt,
ob es sich um ein Einstiegs- oder ein Ausstiegsereignis handelt
(Schritt 1476). Wenn es sich um ein Ausstiegsereignis handelt,
wird der Baum bis zum Elternknoten durchlaufen (mit Hilfe des Elternzeigers),
und der aktuelle Baumknoten wird mit dem Elternknoten gleichgesetzt
(Schritt 1478). An dieser Stelle kann der Baum dynamisch
beschnitten werden, um den für
seine Verwaltung erforderlichen Speicherplatz zu reduzieren (Schritt 1479).
Auf das Beschneiden wird weiter unten ausführlicher eingegangen. Handelt
es sich um ein Einstiegsereignis, so wird festgestellt, ob das Modul
bereits ein Kindknoten des aktuellen Baumknotens ist (Schritt 1480).
Ist dies nicht der Fall, wird ein neuer Knoten für das Modul erstellt und unterhalb
des aktuellen Baumknotens an den Baum angehängt (Schritt 1482).
Dann wird der Baum bis zum Knoten des Moduls durchlaufen, und der
aktuelle Baumknoten wird mit dem Modulknoten gleichgesetzt (Schritt 1484).
Die Anzahl der Aufrufe bis zu aktuellen Baumknoten wird dann inkrementell erhöht (Schritt 1486).
Nun geht die Steuerung an das ausführende Modul zurück, und
das dynamische Protokollierungs- und
Reduktionsprogramm wartet auf das nächste Ereignis (Schritt 1488).
-
Ein
Vorteil des in 14 beschriebenen dynamischen
Protokollierungs- und Reduktionsverfahrens ist die Möglichkeit,
das System mit einem endlichen Speicherpuffer über lange Zeit zu protokollieren.
Es können
sehr detaillierte Leistungsprofile ermittelt werden, ohne dass der
Protokollpuffer "unendlich
groß" sein muss. In Verbindung
mit der dynamischen Beschneidung kann das in 14 dargestellte Verfahren
einen Protokollierungsmechanismus mit fester Puffergröße unterstützen.
-
Die
dynamische Protokollierung und Reduktion (und in manchen Fällen auch
die dynamische Beschneidung) ist besonders vorteilhaft bei der Profilierung
der Leistungsmerkmale von Programmen mit langer Ausführungsdauer.
Bei diesen lang laufenden Programmen kann ein endlicher Protokollpuffer erhebliche
Auswirkungen auf die Menge nützlicher Protokollinformationen,
die gesammelt und analysiert werden können, haben. Durch die dynamische
Protokollierung und Reduktion (und vielleicht die dynamische Beschneidung)
kann auch für
ein lang laufendes Programm eine genaues und informatives Leistungsprofil
erstellt werden.
-
Viele
lang laufende Anwendungen erreichen einen stetigen Zustand, in dem
jede mögliche
Routine und jeder mögliche
Aufrufstack im Baum und in den Aktualisierungsstatistiken vorhanden
ist. Für
solche Anwendungen können
somit Protokolldaten mit Hilfe der dynamischen Beschneidung auch
innerhalb der Zwänge
beschränkter
Speicheranforderungen ewig aufgezeichnet und gespeichert werden.
Der Wert der Beschneidung liegt in der Reduzierung des Speicherbedarfs
in Situationen, in denen die Aufrufstacks praktisch unbegrenzt sind.
Unbegrenzte Aufrufstacks werden zum Beispiel von Anwendungen erzeugt,
die andere Anwendungen laden und ausführen.
-
Die
Beschneidung kann auf verschiedene Weise erfolgen, und es ist eine
Vielzahl von Beschneidungskriterien denkbar. Beschneidungsentscheidungen
können
beispielsweise auf der kumulativen Zeit, die einem Teilbaum zugeschrieben
wird, basieren. Die Beschneidung kann deaktiviert werden, solange
der für
die Verwaltung des Aufrufstacks dedizierte Speicherplatz einen Grenzwert
nicht überschreitet.
Wenn ein Ausstiegsereignis festgestellt wird (wie Schritt 1478 in 14),
wird die kumulative Zeit des aktuellen Knotens mit der kumulativen
Zeit des Elternknotens verglichen. Wenn das Verhältnis zwischen diesen beiden
kumulativen Zeiten einen Schwellenwert für die Beschneidung (z.B. 0,
1) nicht überschreitet,
werden der aktuelle Knoten und alle seine Abkömmlinge von dem Baum entfernt.
Der Algorithmus zur Erstellung des Baums fährt wie bisher fort, indem
er zum Elternknoten zurückgeht
und den aktuellen Knoten mit dem Elternknoten gleichsetzt.
-
Zu
dem beschriebenen Beschneidungsmechanismus sind zahlreiche Abwandlungen
denkbar. So kann beispielsweise der Grenzwert für die Beschneidung erhöht oder
verringert werden, um den Beschneidungsgrad von einer sehr aggressiven
Beschneidung bis zu keiner Beschneidung zu regulieren. Auch globalere
Verfahren sind möglich,
u.a. ein periodisches Überarbeiten
des gesamten Aufrufstack-Baums, bei dem alle Teilbäume entfernt
werden, deren individuelle kumulativen Zeiten keinen wesentlichen
Teil der kumulativen Zeiten des Elternknotens ausmachen.
-
Die
Datenreduktion ermöglicht
es Analyseprogrammen, viele Fragen bezüglich der Aufwendung von Rechenzeit
in dem protokollierten Programm einfach und schnell zu beantworten.
Diese Informationen können
gesammelt werden, indem der Baum durchlaufen wird und die an verschiedenen Knoten
im Aufrufstack-Baum gespeicherten Daten zusammengeführt werden.
Daraus kann festgestellt werden, wieviel Zeit strikt in Routine
A aufgewendet wurde, wieviel Zeit insgesamt in Routine A und den von
Routine A direkt oder indirekt aufgerufenen Routinen aufgewendet
wurde, usw.
-
15 ist
ein Diagramm eines mit dem erfindungsgemäßen Verfahren erzeugten Datensatzes.
In 15 ist jede Routine in Datensatz 1500 separat aufgeführt, zusammen
mit Informationen über
die Routine. In der Aufrufspalte 1504 beispielsweise ist angegeben,
wie oft die Routine aufgerufen wurde. In der BASIS-Spalte 1506 ist
die insgesamt in der Routine aufgewendete Zeit angegeben, und in
der CUM-Spalte 1508 die kumulative Zeit, die in der Routine
und allen von ihr aufgerufenen Routinen aufgewendet wurde. Die Namensspalte 1512 enthält den Namen
der Routine.
-
In 16 ist
ein Diagramm einer anderen Berichtsart, die erstellt werden kann,
dargestellt. Der in 16 dargestellte Bericht zeigt
im wesentlichen die gleichen Informationen wie 15,
aber in einem etwas anderen Format. Wie bei 15 enthält ein Diagramm 1600 Informationen über Aufrufe,
Basiszeit und kumulative Zeit. In 16 ist
eine Protokollausgabe zu sehen, in der die in verschiedenen Routinen aufgewendeten
Zeiten in Millisekunden angegeben sind. 16 enthält einen
Abschnitt (begrenzt durch horizontale Linien) für jede Routine, die in der
Protokollausgabe erscheint. Der Abschnitt enthält in der Zeile "Selbst" Informationen über die
Routine selber, in den Zeilen "Eltern" Informationen darüber, von wem
sie aufgerufen worden ist, und in den Zeilen "Kind" Informationen
darüber,
welche Routinen sie ihrerseits aufgerufen hat. Die Abschnitte sind
nach der kumulativen Zeit geordnet. Der dritte Abschnitt betrifft Routine
A, wie in der mit "Selbst" beginnenden Zeile zu
sehen ist. Den Zahlen in der Zeile "Selbst" dieses Abschnitts ist zu entnehmen,
dass Routine A in diesem Protokoll dreimal aufgerufen wurde, und
zwar einmal von Routine C und zweimal von Routine B. In der Profilterminologie
sind Routine C und Routine B (unmittelbare) Eltern von Routine A.
Routine A ist ein Kind von Routine C und Routine B. Alle Zahlen
in der Zeile "Eltern" im zweiten Abschnitt
sind Aufschlüsselungen
der entsprechenden Zahlen von Routine A. Drei Mikrosekunden der
sieben Mikrosekunden langen Gesamtbasiszeit, die in A aufgewendet
wurde, fallen auf den Aufruf durch Routine C, drei weitere Mikrosekunden
auf dem ersten Aufruf durch Routine B und eine weitere Mikrosekunde
auf den zweiten Aufruf durch Routine B. Entsprechend wurde in diesem Beispiel
für jede
Elternroutine die Hälfte
der kumulativen Zeit von 14 Mikrosekunden der Routine A aufgewendet.
-
Im
zweiten Abschnitt ist zu sehen, dass Routine C einmal Routine B
und einmal Routine A aufgerufen hat. Alle Zahlen in den "Kind"-Zeilen sind Teilmengen
der Zahlen aus dem Profil des Kindes. So erfolgte beispielsweise
von den drei Aufrufen der Routine A in diesem Protokoll einer durch
Routine C; von der 7 Mikrosekunden umfassenden Gesamtbasiszeit der
Routine A entfielen drei Mikrosekunden auf den direkten Aufruf durch
Routine C; von der 14 Sekunden umfassenden kumulativen Zeit der
Routine A gehen sieben Mikrosekunden auf Routine C zurück. Die gleichen
Zahlen stehen in der ersten Zeile des dritten Abschnitts, wo Routine
C als eine der Elternroutinen von Routine A aufgelistet ist. Die
vier Beziehungen, die für
alle Abschnitte gelten, sind im oberen Teil von 16 zusammengefasst.
Erstens ist die Summe der Zahlen in der Spalte "Aufrufe" bei "Eltern" gleich der Anzahl der Aufrufe in der
Zeile "Selbst". Zweitens ist die
Summe der Zahlen in der Spalte "Basis" bei "Eltern" gleich der Basiszeit
bei "Selbst". Drittens ist die
Summe der Zahlen in der Spalte "CUM" bei "Eltern" gleich der kumulativen
Zeit bei "Selbst". Diese ersten drei
Invarianten gelten, weil diese Eigenschaften die Definition von
Eltern sind; kollektiv sollen sie alle Aktivitäten von "Selbst" ausmachen. Viertens entspricht "CUM" in den "Kind"-Zeilen der Gesamtsumme
der kumulativen Zeit von "Selbst", mit Ausnahme der
eigenen Basiszeit.
-
Die
Programmabtastung kann Informationen aus dem Aufrufstack enthalten
und ein Profil liefern, das nicht nur die Abtastung der Blätter, sondern
des gesamten Aufrufstack widerspiegelt. Darüber hinaus kann das auf Abtastung
basierende Profilierungsverfahren auch auf andere Arten von Stacks
angewendet werden. In Java-Programmen wird zum Beispiel viel Zeit
in einer als "Interpreter" bezeichneten Routine
aufgewendet. Würde
nur der Aufrufstack geprüft, so
würde das
Profil in diesem Fall nicht viel nützliche Information liefern.
Da der Interpreter auch Informationen im eigenen Stack, z.B. einem
Java-Stack (mit eigenen Verknüpfungskonventionen)
protokolliert, kann der Prozess zum Durchlaufen des Java-Stacks benutzt
werden, um die Aufrufsequenz aus der Perspektive des interpretierten
Java-Programms zu
erhalten.
-
In 17 ist
ein Bericht dargestellt, der aus einer Protokolldatei generiert
wurde, die sowohl ereignisbasierte Profilierungsdaten wie Methodeneinstiege
und Methodenausstiege als auch Stack-Aufrollinformationen aus der
auf Abtastung basierenden Profilierung enthält. 17 entspricht
im wesentlichen 12, wo ein Aufrufstack-Baum
als Bericht dargestellt ist, wobei 17 aber
eingebettete Stackdurchlaufinformationen enthält. Der Aufrufstack-Baum 1700 enthält zwei
Stack-Aufrollungen, die in der durch insgesamt 342 Ticks dargestellten Zeitperiode
generiert wurden. Die Stackaufrollkennung 1702 kennzeichnet
den Anfang der Stackaufrolldaten 1706 wobei die nach rechts
eingerückten Routinennamen
die Stack-Informationen enthalten, die beim Durchlaufen des Stacks
erkannt werden konnten. Die Stackaufrollkennung 1704 kennzeichnet
den Anfang der Stackaufrolldaten 1708. In diesem Beispiel
bezeichnet "J:" eine interpretierte
Java-Methode und "F:" eine native Funktion,
z.B. eine native Funktion in JavaOS. Ein Aufruf einer nativen Methode
durch eine Java-Methode erfolgt mittels "ExecuteJava". Daher kann an der Stelle, wo der Stackdurchlauf
einen Stack Frame für
ein "ExecuteJava" erreicht, kein weiterer
Schritt durch den Stack folgen, da die Stack Frames aufhören. Der
Prozess zum Erstellen eines Baums, der sowohl ereignisbasierte Knoten
als auch auf Abtastung basierende Knoten enthält, wird weiter unten ausführlicher
beschrieben. In diesem Fall bezeichnen die Kennungen 1702 und 1704 auch
den Hauptcode für
diese Stack-Aufrollung.
-
18 ist eine Tabelle, in der Haupt- und Untercodes
aufgeführt
sind, die dazu benutzt werden können,
Module für
die Profilierung zu instrumentieren. Ein Codesatz kann dazu verwendet
werden, verschiedene Arten von Profilierungsfunktionen in einer bestimmten
Profilierungssitzung zu aktivieren und zu deaktivieren. Wie in 18 zu sehen ist, wird beispielsweise der
Untercode für
eine Stackaufrollung mit 0 × 7fffffff
bezeichnet und kann für
zwei verschiedene Zwecke eingesetzt werden. Der erste Zweck, hier
mit dem Hauptcode 0 × 40
bezeichnet, ist die Stackaufrollung bei einem Timer-Interrupt. Der
zweite Zweck, hier mit dem Hauptcode 0 × 41 bezeichnet, ist die Stackaufrollung
in einer instrumentierten Routine. Wenn die Stackdaten mit Haupt-
und Untercodes in eine Protokolldatei ausgegeben werden, können die
in der Datei erscheinende Protokolldaten auf die durch Haupt- und
Untercodes signalisierte Weise angemessen analysiert werden.
-
Andere
Beispiele in der Tabelle zeigen einen Profil- oder Hauptcodezweck
der Ablaufverfolgung von JIT-Methoden mit einem Hauptcodewert von
0 × 50.
Die Protokollierung gejitteter Methoden kann anhand des Untercodes
unterschieden werden, der einen Methodenaufruf oder das Verlassen
einer Methode signalisiert. Im Gegensatz dazu bezeichnet ein Hauptcode
von 0 × 30
einen Profilierungszweck der Instrumentierung von interpretierten
Methoden, während
ein Untercode mit den gleichen Werten einen Methodenaufruf oder
das Verlassen einer Methode signalisiert.
-
In 17 kann
eine Verbindung zwischen der Verwendung von Haupt- und Untercodes,
der Instrumentierung von Code und der Nachverarbeitung von Profildaten
hergestellt werden. In dem in 17 dargestellten
generierten Bericht ist zu sehen, dass die Stackaufrollkennungen
den Wert 0 × 40
haben, was nach der Tabelle in 18 eine
als Reaktion auf einen Timer-Interrupt
generierte Stackabrollung ist. Diese Art der Stackaufrollung kann
als Reaktion auf einen Interrupt erfolgt sein, der erzeugt wurde,
um ein auf Abtastung basierendes Profil der ausgeführten Software
zu generieren.
-
Wie
in der letzten Spalte der Tabelle in 18 zu
sehen ist, kann durch Verwendung eines Dienstprogramms, das einen
Anker in ein zu profilierendes Softwaremodul setzt, eine Stackaufrollung
in eine Routine instrumentiert werden. In diesem Fall wird die Ausgabe
für diese
Art der Stackaufrollung mit einem Hauptcode von 0 × 41 gekennzeichnet.
-
Bei
der Profilierung eines ausführbaren
Programms würde
der Profilierer im Idealfall Paare zusammengehöriger Einstiegs- und Ausstiegsereignisse
empfangen. Dies ist aber nicht oft der Fall. In einem typischen
Beispiel kann die Protokollierung beginnen, nachdem ein Teil des
ausführbaren
Programms bereits ausgeführt
worden ist. Dies kann geschehen, wenn der Mechanismus zum Starten
des Profilierers dynamisch aktiviert wird. In diesem Fall empfängt der
Profilierer möglicherweise
Ausstiegsereignisse, ohne dass zuvor die zugehörigen Einstiegsprotokolldatensätze empfangen
wurden, d.h. ohne ein zugehöriges
Einstiegsereignis. In diesem Fall hat das Ausstiegsereignis keine
Entsprechung.
-
Ein
weiteres Beispiel wäre,
dass es in einer Profilierungssitzung mehrere Ausführungspfade
gibt, die nicht korrekt als praktische Angelegenheit instrumentiert
sind. Dies kommt bei Ausnahmepfaden häufig vor. Im Fall von Ausführungspfaden
empfängt
der Profilierer Einstiegsereignisse ohne die zugehörigen Ausstiegsereignisse,
da ein Ausnahmepfad inhärent ein
abruptes Verlassen einer Routine oder einer Methode bewirkt.
-
In
einem weiteren Beispiel von Ereignissen ohne entsprechendes Gegenstück kann
die Instrumentierung des ausführbaren
Codes möglicherweise nicht
automatisch durchgeführt
werden. Um eine ereignisbasierte Profilierung zu verwenden, muss
ein ausführbares
Programm mit Ankern für
die Protokollierung von Ein- und Ausstiegen instrumentiert werden.
Bei einem manuell instrumentierten Code kann es vorkommen, dass
einem Programmierer ein Fehler unterläuft und er keine zusammengehörigen Ein- und
Ausstiegsprotokollanker in eine Routine einfügt. In diesem Fall kann es
dazu kommen, dass der Profilierer ein Ausstiegsereignis für eine Routine
ohne entsprechenden Einstiegsprotokollsatz empfängt.
-
In
jedem dieser Beispiele ist es wichtig, den Ausführungspfadfluss zu ermitteln
und eine sinnvolle Darstellung der Ausführungspfade zu erstellen. Man kann
versuchen, diese Fehlertypen im Profilierer zu erkennen und zu beheben.
Die Protokollverarbeitung kann in allen Fehlerfällen fortgesetzt werden, und
in der Regel werden sowohl für
häufige
als auch für
ungewöhnliche
Probleme vernünftige
Ausnahmepfade festgelegt.
-
Die
Fehlerbehebungsverarbeitung kann sowohl in Form einer Echtzeitverarbeitung
des Protokolls als auch in einer Nachverarbeitungsphase der ausgegebenen
Protokolldaten erfolgen. Bei der Protokollverarbeitung in Echtzeit
kann ein Aufrufstack-Baum erstellt werden wie weiter oben im Zusammenhang
mit 11A und 11B beschrieben.
Zu jedem Zeitpunkt während
der Erstellung des Aufrufstack-Baums wird ein Zeiger auf einen aktuellen
Knoten verwaltet, der die Routine, die zuletzt als gerade ausgeführt identifiziert
wurde, darstellt.
-
Wenn
das gerade verarbeitete Ereignis ein Einstiegsereignis ist, kann
davon ausgegangen werden, dass das Einstiegsereignis einen Aufruf
durch die vom aktuellen Knoten repräsentierte Routine darstellt.
Da noch nicht festgestellt werden kann, ob ein Fehler aufgetreten
ist, bei dem ein Ausstiegsereignis im Ausführungsfluss vor dem gerade
verarbeiteten Ereignis fehlt, wird davon ausgegangen, dass Einstiegsereignisse
keine Fehlerbedingung enthalten. Selbst wenn nachträglich eine
Fehlerbedingung erkannt wird, wird die Verarbeitung von Einstiegsereignissen
auf die gleiche Weise fortgesetzt, ob nun in dem durch die generierten
Ereignisse dargestellten Ausführungsfluss
eine Fehlerbedingung aufgetreten ist oder nicht.
-
Bei
der Verarbeitung eines Ereignisses wird angenommen, dass das Ausstiegsereignis
einen Ausstieg aus der aktuellen Routine gemäß der Darstellung in der Aufrufstack-Baumstrukur
darstellt. Das Ausstiegsereignis kann aber eine Routine identifizieren,
die nicht in den aktuellen Baumknoten passt, d.h. ein Ausstiegsereignis
und der aktuelle Knoten im Aufrufstack-Baum passen nicht zusammen,
so dass ein Entsprechungsfehler vorliegt. In den Protokolldaten
fehlt deshalb mindestens ein Einstiegsereignis. Obwohl anhand der
Protokolldaten nicht festgestellt werden kann, wie viele Ereignisse fehlen,
kann man doch versuchen, einen Knoten im Aufrufstack-Baum zu ermitteln,
der das gerade verarbeitete Ausstiegsereignis darstellt.
-
Ergänzend ist
zu sagen, dass beim Empfang eines Einstiegs- oder Ausstiegsereignisses ein aktueller
Zeitstempel (oder ein anderes ausführungsbezogenes Merkmal) erfasst
werden kann, und dass das Zeitinkrement (oder eine andere ausführungsbezogene
Metrik) zwischen dem gerade verarbeiteten Ereignis und dem vorausgehenden
Ereignis auf den aktuellen Knoten angewandt wird. Diese Standardregel
funktioniert selbst in Fällen,
wo ein Fehler mit nicht zusammenpassenden Ein- und Ausstiegsereignissen
erkannt wird, recht gut, da der aktuelle Knoten die letzte als gerade
ausgeführt
erkannte Routine darstellt und mindestens ein Teil der Zeit (oder
einer anderen ausführungsbezogenen
Metrik) zwischen dem letzten Ereignis und dem gerade verarbeiteten Ereignis
im aktuellen Knoten aufgewendet wurde.
-
19 ist
ein Flussdiagramm, in dem die Verarbeitung von Beendigungsereignissen
einschließlich
der Fehlerbehebungsverarbeitung in einem Ausführungsfluss, der Fehler in
Form nicht einander zuzuordnender Ereignisse enthalten kann, dargestellt
ist. Der im Flussdiagramm dargestellte Prozess geht davon aus, dass
ein gerade verarbeitetes Ereignis als Ausstiegsereignis erkannt
wurde. Auf diese Weise kann der in 19 dargestellte
Prozess Schritt 1478 in 14 ersetzen.
-
In
diesem Beispiel ist X der Modulname des Moduls, das durch das Ausstiegsereignis
verlassen wird, Y ist der Modulname des aktuellen Knotens des Aufrufstack-Baums,
und R ist der Modulname des Wurzelknotens. Für die Baumknoten gelten bestimmte
Beziehungen, so z.B., dass der Elternknoten von NODE als PAR(NODE)
bezeichnet wird und der Kindknoten von NODE als CHILD(NODE).
-
Der
Prozess in 19 beginnt damit, dass festgestellt
wird, ob das verlassene Modul X das gleiche ist wie der aktuelle
Knoten Y, d.h. ob der Modulname des verlassenen Moduls X dem Modulnamen des
aktuellen Knotens Y entspricht (Schritt 1902). Wenn dies
der Fall ist, handelt es sich um einen normalen Ausstieg ohne Fehler.
Der Baum wird zurückverfolgt,
d.h. zum Elternknoten des aktuellen Knotens verfolgt (Schritt 1904),
und der Prozess ist in Bezug auf das gerade verarbeitete Ausstiegsereignis abgeschlossen.
-
Wenn
das verlassene Modul nicht mit dem aktuellen Knoten Y identisch
ist, wird festgestellt, ob der aktuelle Knoten Y mit dem Wurzelknoten
R identisch ist (Schritt 1906). Dies ist der häufigste
Fehlerfall, der eintritt, wenn das Einstiegsereignis, das dem gerade
verarbeiteten Ausstiegsereignis entspricht, erfolgte, bevor die
Protokollierung aktiviert wurde. Wenn der aktuelle Knoten mit dem
Wurzelknoten identisch ist, wird der Baum "repariert", indem der Wurzelknoten R in das verlassene
Modul X umbenannt wird und Modul X die Möglichkeit bekommt, alle Metriken,
die zu diesem Zeitpunkt im Wurzelknoten R vorhanden sind, zu übernehmen
(Schritt 1908). Dann wird für die Wurzel des Baumes ein
neuer Knoten erstellt und als Elternknoten des Knotens für das verlassene
Modul X angehängt,
d.h. PAR(X) = R und CHILD(R) = X (Schritt 1910). Die Baumstruktur
sieht jetzt genauso aus, als wenn die Protokollierung mit einem
Einstieg in Modul X begonnen hätte.
Nun ist der Prozess in Bezug auf die Verarbeitung des Ausstiegsereignisses,
wenn der aktuelle Knoten der Wurzelknoten ist, abgeschlossen.
-
Wenn
der aktuelle Knoten nicht mit dem Wurzelknoten identisch ist, wird
der Baum zurückverfolgt, d.h.
der Zeiger des aktuellen Knotens verweist auf den Elternknoten des
aktuellen Knotens Y (Schritt 1912). Es wird festgestellt,
ob das verlassene Modul X das gleiche ist wie der aktuelle Knoten
Y, d.h. ob der Modulname des verlassenen Moduls X dem Modulnamen
des aktuellen Knotens Y entspricht (Schritt 1914). Ist
dies der Fall, fährt
die Verarbeitung mit Schritt 1904 fort, um die Verarbeitung
des gerade verarbeiteten Ausstiegsereignissatzes fortzusetzen. In
diesem Fall muss der Baum über
mehrere Schritte zurückverfolgt
werden, um einen passenden Ausstiegsknoten zu finden. Dies kann
folgende Gründe haben:
(1) ein langer "Sprung", der verwendet wird, um
von einer verschachtelten Fehlerbedingung zurückzukehren; (2) ein nicht instrumentierter
Ausnahmeverarbeitungspfad; oder (3) eine fehlerhafte Instrumentierung,
die dazu führt,
dass ein oder mehrere Ausstiegsanker fehlen.
-
Wenn
das verlassene Modul nicht mit dem aktuellen Knoten Y identisch
ist, wird festgestellt, ob der aktuelle Knoten Y mit dem Wurzelknoten
R identisch ist (Schritt 1916). Ist dies nicht der Fall,
verzweigt der Prozess zurück
zu Schritt 1912. Ist der aktuelle Knoten Y mit dem Wurzelknoten
R identisch, wird der Baum "repariert", indem zu Schritt 1908 und Schritt 1910 verzweigt
wird. Die Interpretation der zu behebenden Situation ist aber anders
als oben beschrieben. Die Feststellung in Schritt 1916 erkennt
einen schweren Fehler, möglicherweise
aufgrund von Instrumentierungsfehlern oder Schwierigkeiten bei der
Protokolldatenerfassung, und bei Bedarf kann eine Meldung, dass
es sich um einen schweren Fehler handelt, ausgegeben werden.
-
Auf
diese Weise kann mit dem in 19 beschriebenen
Prozess eine Aufrufstack-Baumdarstellung erzeugt werden, die nützliche
Informationen liefert, obwohl einige der Knoten keine exakte Entsprechung
zum Ausführungsfluss
des ausgeführten
Programms darstellen.
-
Wie
oben erwähnt
kann es sein, dass die Protokollierung erst beginnt, nachdem schon
ein Teil des ausführbaren
Programms abgelaufen ist, wodurch es zu fehlenden Ein- und Ausstiegsereignissen im
Protokollablauf kommen kann, und wenn der Profilierer ein Ereignis
empfängt,
entspricht dieses keinem Knoten im Aufrufstack-Baum. Diese Art von
Entsprechungsfehler kann am Anfang einer Profilierungsphase vorkommen,
wenn die Protokollierung eines Programms beginnt, sie kann aber
auch bei der auf Abtastung basierenden Profilierung gemäß einer bevorzugten
Ausführungsform
der vorliegenden Erfindung vorkommen – falls die Generierung der
Protokolldaten unterbrochen und dann wiederaufgenommen wurde, können entsprechungslose
Ein- und Ausstiegsereignisse im Ausführungsfluss vorkommen. Der
Prozess der Unterbrechung und Wiederaufnahme der ereignisbasierten
Profilierung, d.h. die auf Abtastung basierende Profilierung von
Ereignissen, hat im Vergleich mit anderen Arten der Profilierung
gewisse Vorteile.
-
Die
Erzeugung eines Aufrufstack-Baums während einer kontinuierlichen
ereignisbasierten Profilierung hat aufgrund der Baumerstellung und Metrikerfassung
im Profilierungsprozess eine erhebliche Verminderung der Systemleistung
zur Folge. Die Aufrollung von Stacks bei der auf Abtastung basierenden
Profilierung ist nicht immer zuverlässig und kann inhärente Implementierungsprobleme
haben; so kann es beispielsweise unmöglich sein, eine Zeigerkette
im Aufrufstack zu verfolgen, oder der profilierte Programmcode entspricht
nicht den Standardkonventionen. Selbst wenn die Stackaufrollungen
zuverlässig
sind, liefern sie für
die zu profilierenden Routinen keine Informationen über die
Länge der Ausführung. Die
periodische Abtastung von Einstiegs- und Ausstiegsankern liefert
daher ein gewisses strukturelles und quantitives Maß der Ausführung eines
Programms bei geringerer Beeinträchtigung der
Systemleistung als bei einer kontinuierlichen ereignisbasierten
Profilierung und ohne totale Abhängigkeit
von Stackaufrollungen.
-
20 ist
ein Flussdiagramm, in dem ein Prozess zur auf Abtastung basierten
Profilierung von Aufruf-/Beendigungsereignissen
unter Verwendung benutzerdefinierter Metriken zur Erzeugung unabhängiger Aufrufstack-Baumsegmente
dargestellt ist. Der Prozess geht davon aus, dass die beschriebenen Aktionen
für jeden
Thread separat ausgeführt
werden, d.h. der Profilierer würde
die gleichen Aktionen oder die gleiche Verarbeitung für jeden
einzelnen pidtid-Wert durchführen,
d.h. Segmente eines Aufrufstack-Baums können nicht nur anhand der Abtastperiode
identifiziert werden, sondern auch anhand des pidtid-Wertes. Außerdem kann
der Profilierer einen Sequenzwert für die Abtastperiode verwalten und
inkrementieren, der eine Ordnungsnummer für die Abtastperioden liefert.
-
Der
Prozess beginnt damit, dass der Profilierer vom Benutzer angegebene
Metrikwerte akzeptiert, die der Profilierer zur Steuerung der auf
Abtastung basierten Profilierung verwenden soll (Schritt 2002).
Diese Metrikwerte können
z.B. in Umgebungsvariablen gespeichert oder durch Befehlszeilenparameter
eingegeben werden. Alternativ enthält der Profilierer Standardmetrikwerte,
so dass der Benutzer keine Metrikwerte eingeben muss. An einem Zeitpunkt
wird die Profilierungsphase eines Programms schließlich initiiert
(Schritt 2004).
-
Der
Profilierer deaktiviert dann die Protokollierung für einen
vom Benutzer angegebenen Metrikwert (Schritt 2006) und
führt in
dieser Zeit keine Protokollverarbeitung durch (Schritt 2008).
Nach Ablauf dieser Zeit erwacht der Profilierer wieder und aktiviert die
Protokollverarbeitung in Echtzeit (Schritt 2010). Der Profilierer
erstellt ein neues, unabhängiges
Aufrufstack-Baumsegment anhand der Einstiegs-/Ausstiegsereignisse mit einer Fehlerverarbeitung
während
einer Periode, die nach einem anderen vom Benutzer angegebenen Metrikwert
gemessen wird (Schritt 2012).
-
Nun
wird festgestellt, ob die Programmausführung abgeschlossen ist (Schritt 2014),
und wenn dies nicht der Fall ist, verzweigt der Prozess zu Schritt 2006 zurück, um die
Profilierung der Programmausführung
fortzusetzen. Wenn die Programmausführung abgeschlossen ist, gibt
der Profilierer die in der Profilierungsphase generierten Aufrufstack-Baumsegmente
aus (Schritt 2016). In der Nachverarbeitungsphase werden
die Aufrufstack-Baumsegmente zu einem einzigen Aufrufstack-Baum
verschmolzen (Schritt 2018), und der Prozess ist abgeschlossen.
Das "Verschmelzen" eines Baums ist
definiert als Durchlaufen eines ersten Baums, um einen zweiten Baum
zu erstellen, der die Vereinigung des ersten und des zweiten Baums
enthält.
In diesem Fall werden alle Aufrufstack- Baumsegmente zu einem einzigen Baum
verschmolzen, d.h. es wird durch Vereinigung aller Aufrufstack-Baumsegmente
ein einziger Baum generiert.
-
Jedes
Aufrufstack-Baumsegment ist eine eindeutige Aufzeichnung der Aufrufsequenz
zwischen Routinen in dieser Abtastperiode. Ein Aufrufstack-Baumsegment
kann als Baumdatenstruktur klassifiziert werden. Für jede Abtastperiode
wird ein unabhängiger
Baum erstellt, so dass ein Wald von Bäumen entsteht, der auf den
pidtid-Werten im zeitlichen Verlauf basiert. Obwohl jede Abtastperiode
einen eigenen Aufrufstack-Baum
erzeugt, wird der Begriff "Aufrufstack-Baumsegment" benutzt, um zu betonen,
dass jeder dieser Aufrufstack-Bäume
aus diesen Periode relativ klein sein kann im Vergleich zu einem
kompletten Aufrufstack-Baum, der die Aufrufsequenzen zwischen den
Routinen für
einen vollständige
Profilierungslauf eines ausführbaren
Programms aufzeichnet. Der Begriff "Aufrufstack-Baumsegment" wird auch benutzt,
um zu betonen, dass diese Aufrufstack-Bäume nur ein Mittel zu dem Zweck
sind, am Ende einen resultierenden Aufrufstack-Baum für alle Abtastperioden
zu erzeugen. Deshalb ist die Information in einem Aufrufstack-Baumsegment
im Vergleich zu der gesamten Profilinformation, die bei der Profilierung
des Programms generiert wird, nur ein "Segment" der Profilinformation für eine Abtastperiode.
-
Ein
Aufrufstack-Baumsegment von einer Abtastperiode wird insofern als "unabhängig" bezeichnet als jedes
Aufrufstack-Baumsegment
in dieser Vorstellung eine eigene Wurzel und eigene Knoten besitzt,
die die Abfolge der Routinen- oder Methodenaufrufe zwischen den
Routinen entsprechend der Baumstruktur wiedergeben. Der Profilierer
kann aber eine andere Datenstruktur verwalten, die diese Aufrufstack-Baumsegmente assoziativ
speichert – in diesem
Kontext muss "Unabhängigkeit" nicht bedeuten,
dass die einzelnen Datenstrukturen nicht mit anderen Datenstrukturen
verknüpft
sind.
-
Da
die Aufrufstack-Baumsegmente anhand der pidtid-Werte identifizierbar
sind, kann der Profilierer eine Art Datenstrukur für jede pidtid
verwalten. Er kann beispielsweise eine verknüpfte Liste verwalten, in der
jedes Element der Wurzelknoten eines kompletten Aufrufstack-Baumsegments
ist. Anders ausgedrückt
können
miteinander in Verbindung stehende Aufrufstack-Baumsegmente assoziativ
verknüpft werden,
so wie der Profilierer dies für
erforderlich hält,
um die Aufrufstack-Baumsegmente zu verwalten und zu protokollieren.
-
Der
Benutzer kann eine Abtastzeit und eine Anzahl der Ereignisse für jede Abtastung
identifizieren. Bei jeder Abtastung wird ein Aufrufstack-Baum generiert.
Nach Abschluss der Profilierungsphase kann der Profilierer Tausende
dieser Bäume,
d.h. einen "Wald" von Bäumen, besitzen.
Alternativ kann der Profilierer in regelmäßigen Abständen die Aufrufstack-Bäume zur Nachverarbeitung in
eine Datei ausgeben. Dieser Wald von Bäumen kann dann bei der Nachverarbeitung
verschmolzen werden.
-
Der
Benutzer kann beispielsweise eine Wartezeit oder Pause von zwei
Sekunden und eine aus tausend Ereignissen bestehende Protokollierungsperiode
angeben. Der Profilierer erstellt dann alle zwei Sekunden tausend
Ereignis-Momentaufnahmen. Nach tausend Ereignissen deaktiviert der
Profilierer die Verarbeitung von Einstiegs- und Ausstiegsereignissen
und wartet zwei Sekunden lang. Nachdem der Profilierer sich entschließt, wieder
mit der Protokollverarbeitung zu beginnen, z.B. nach einem Timer-Interrupt,
aktiviert er die Verarbeitung von Ein-/Ausstiegsereignissen und
ermöglicht
die Fortsetzung der Ereignisverarbeitung. Der Profilierer durchläuft auf diese Weise
eine Schleife, bis die Ausführung
des Jobs abgeschlossen ist.
-
Alternativ
könnte
die Abtastung nicht durch eine Anzahl von Ereignissen, sondern durch
eine Zeitangabe definiert werden. In diesem Fall könnte der
Benutzer zum Beispiel eine 10 Millisekunden lange Abtastdauer angeben,
die einmal pro Sekunde stattfindet. In einer bevorzugten Ausführungsform kann
der Benutzer verschiedene Arten von ausführungsbezogenen Metriken für die Wartezeit
und/oder die Abtastperiode wählen.
Zusätzlich
kann der Profilierer in jeder Routine neben der Ausführungszeit
verschiedene ereignisbezogene Merkmale überwachen.
-
21 ist
ein Flussdiagramm, in dem der Prozess dargestellt ist, durch den
ein Baum, der sogenannte Quellbaum, zu einem zweiten Baum, dem sogenannten
Zielbaum, hinzugefügt
wird, um eine Vereinigung zwischen den beiden Bäumen zu schaffen. Dahinter
steht die Absicht, den Quellbaum von der Wurzel des Zielbaus ausgehend
in den Zielbaum zu kopieren. Wenn im Zielbaum bereits ein Teil des Quellbaums
an der Wurzel beginnend als Kind der Wurzel existiert, darf kein
neuer, doppelt vorhandener Knoten im Zielbaum erstellt werden, sondern
die vorhandenen Knoten im Zielbaum müssen wiederbenutzt werden,
wobei ihre Profilierungsmetriken addiert werden.
-
Jeder
Quellbaumknoten S durch die Definition von "Baum" besitzt
einen eindeutigen Elternknoten par(S) im Quellbaum; davon ausgenommen
ist nur der Wurzelknoten Sroot, der keinen
Elternknoten besitzt. Außerdem
gibt es zu jedem S schließlich
einen eindeutigen entsprechenden Knoten T(S) im Zielbaum. Jedes
S wird als Kind seines eindeutigen entsprechenden Elternknotens
im Zielbaum kopiert. Anders ausgedrückt: Wenn S1 ein
Kind von S2 ist, dann ist T(S1)
ein Kind von T(S2). par (S) muss also in
den Zielbaum kopiert worden sein, bevor S kopiert werden kann. Alle
Profilmetriken für
den Quellknoten werden direkt zu den entsprechenden Feldern des Zielknotens
addiert. Zu diesen Daten zählen
die Anzahl der Aufrufe, die kumulative Zeit und die Basiszeit. Damit
der Kopierprozess starten kann, wird diese Anforderung für die Wurzel
des Quellbaums ausgesetzt; es wird angenommen, dass T(par(Sroot)) die Wurzel des Zielbaums ist. Die
Profilmetriken für
den Wurzelknoten des Quellbaums werden zu dem Wurzelknoten des Zielbaums
addiert, und der Zielknoten des Quellbaums wird als verarbeitet
markiert (Schritt 2102).
-
Jeder
Knoten S des Quellbaums, dessen Elternknoten bereits in den Zielbaum
kopiert worden ist, kann als nächster
zu kopierender Knoten ausgewählt
werden (Schritt 2104). Dann wird entschieden, ob T(par(S))
bereits einen Kindknoten besitzt, der die gleiche Routine darstellt
wie S (Schritt 2106). Wenn dies nicht der Fall ist, wird
im Zielbaum ein neuer Kindknoten erstellt (Schritt 2108),
der Routinenname des neuen Knotens wird auf den Routinennamen von S
gesetzt (Schritt 2110), und die Profilmetriken im Zielknoten
wie z.B. die Anzahl der Aufrufe des Zielknontens wird auf Null gesetzt
(Schritt 2112). Die Profilmetriken in S wie z.B. die Anzahl
der Aufrufe von S werden dann zu den Profilmetriken addiert, die
im (neu erstellten oder bereits vorher vorhandenen) entsprechenden
Knoten T(S) im Zielbaum gespeichert sind, und der Quellknoten wird
als verarbeitet markiert (Schritt 2114). Dann wird festgestellt,
ob der Quellbaum weitere noch nicht kopierte Knoten enthält (Schritt 2116).
Ist dies der Fall, so verzweigt der Prozess zurück zu 2104, wo ein
anderer Knoten zum Kopieren ausgewählt wird. Andernfalls, wird
der Befehl verarbeitet. Auf diese Weise werden alle Aufrufstack-Baumsegmente
zu dem resultierenden Aufrufstack-Baum hinzugefügt.
-
Die
Vorteile der vorliegenden Erfindung sollten aus der obigen detaillierten
Beschreibung klar hervorgehen. Mit dem oben beschriebenen Profilierungsansatz
wird jede neue periodische Abtastung als ein unabhängiges Baumsegment
behandelt. Wenn der Job abgeschlossen ist, werden alle Aufrufstack-Baumsegmente
in dem während
des gesamten Profilierungsjobs generierte Wald verschmolzen. Bei
der Verschmelzung werden Profilmetriken wie Basiszeiten als Summe
der Basiszeiten der Routinen aktualisiert. Außerhalb der Abtastperioden, wenn
keine Protokollverarbeitung stattfindet, wird die Systemleistung
kaum beeinträchtigt.
-
Es
kann vorkommen, dass Aufrufstack-Baumsegmente fehlerhaft verschmolzen werden,
weil die Fehlerbehebungsprozedur nicht garantieren kann, dass die
Bildung der Aufrufstack-Baumsegmente,
die höchstwahrscheinlich aufgrund
von entsprechungslosen Ein-/Ausstiegsereignissen am Anfang einer
Abtastperiode Fehler in den Darstellungen von Ausführungsflüssen enthalten,
korrekt erfolgt ist. Es kann also vorkommen, dass identische Aufrufstack-Bäume, die
in unterschiedlichen Kontexten generiert wurden, kombiniert werden.
Die Gesamtzeit, die in einer bestimmten Methode aufgewendet wird,
ist jedoch innerhalb des abgetasteten Kontexts, in dem die Zeitwerte
generiert wurden, korrekt.
-
Es
ist wichtig zu bedenken, dass die vorliegende Erfindung hier zwar
im Kontext eines voll funktionierenden Datenverarbeitungssystems
beschrieben wurde, dass dem Fachmann aber klar ist, dass der erfindungsgemäße Prozess
auch in Form eines computerlesbaren Datenträgers mit Instruktionen und
in verschiedenen Formen verteilt werden kann, und dass die vorliegende
Erfindung unabhängig
von der jeweiligen Art des tatsächlich
zur Verteilung verwendeten Signalträgers anwendbar ist. Beispiele
für computerlesbare
Datenträger
sind beschreibbare Datenträger
wie Disketten oder Festplatten, RAM, und CD-ROMs und Übertragungsmediem
wie digitale und analoge Verbindungen.