Abstrakte Verhaltensbeschreibung von CCM

Transcription

Abstrakte Verhaltensbeschreibung von CCM
Abstrakte Verhaltensbeschreibung von
CCM Softwarekomponenten
Diplomarbeit
von
Peter Süttner
3144238
geboren am 11. März 1984 in Bamberg
31. März 2011
Betreuer:
Dipl. - Inf. Sebastian Cech, Dipl. - Inf. Sebastian Götz
Verantwortlicher Hochschullehrer:
Prof. Dr. rer. nat. habil. Uwe Aßmann
Institut / Lehrstuhl:
Software und Multimediatechnik / Softwaretechnologie
Inhaltsverzeichnis
1 Einleitung
7
2 Das CoolSoftware Projekt
2.1 Motivation . . . . . . . . . . . . . . .
2.2 Projektskizze . . . . . . . . . . . . .
2.2.1 Komponentenmodell CCM . .
2.2.2 Vertragssprache ECL . . . . .
2.2.3 Laufzeitumgebung THEATRE
2.3 Aufgabenstellung und Einordnung . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3 Verwandte Arbeiten und Literatur
3.1 Verhaltensbeschreibungen . . . . . . . . . . . .
3.1.1 Theoretische Hintergründe . . . . . . . .
3.1.2 Verhalten: Beschreibungsformen und ihre
3.1.3 Diskussion im Kontext von CoolSoftware
3.2 Software und Energieverbrauch . . . . . . . . .
3.2.1 Einführung . . . . . . . . . . . . . . . .
3.2.2 State of the Art . . . . . . . . . . . . . .
3.2.3 Zusammenfassung . . . . . . . . . . . . .
4 Java und die Ressourcennutzung
4.1 Beschreibung der Systemumgebung . . .
4.1.1 Übersicht . . . . . . . . . . . . .
4.1.2 Die Hardware . . . . . . . . . . .
4.1.3 Das Betriebssystem . . . . . . . .
4.1.4 Die Java Virtual Machine (JVM)
4.2 Möglichkeiten zur Datenerhebung . . . .
4.2.1 Instrumentierung . . . . . . . . .
4.2.2 Sampling . . . . . . . . . . . . .
4.2.3 Profiling . . . . . . . . . . . . . .
4.2.4 Benchmarking . . . . . . . . . . .
3
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. . . . . . .
. . . . . . .
Gewinnung
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
13
13
14
15
18
21
24
.
.
.
.
.
.
.
.
27
27
28
31
34
37
37
39
43
.
.
.
.
.
.
.
.
.
.
45
46
46
48
52
58
63
64
66
67
70
5 JRUPI als konzeptuelles Framework
5.1 Ergebnisdefinition und Konsequenzen . . . . . .
5.1.1 Die Komponente als Blackbox . . . . . .
5.1.2 Erwartetes Ergebnis und Abhängigkeiten
5.2 Kontext und Entwurfskriterien . . . . . . . . . .
5.2.1 Kontext der Betrachtungen . . . . . . .
5.2.2 Formulierung der Entwurfskriterien . . .
5.3 Das JRUPI Architekturkonzept . . . . . . . . .
5.4 Der JRUPI Workflow . . . . . . . . . . . . . . .
5.5 Zusammenfassung umzusetzender Komponenten
.
.
.
.
.
.
.
.
.
6 Proof-of-Concept Implementierung
6.1 Instanziierung der Komponenten . . . . . . . . .
6.1.1 Profiler: SystemTap . . . . . . . . . . . . .
6.1.2 Instrumentierung: BTrace . . . . . . . . .
6.1.3 Datenaustauschformat: JSON . . . . . . .
6.1.4 Intelligente Datenanalyse: Eureqa . . . . .
6.2 Technische Umsetzung . . . . . . . . . . . . . . .
6.2.1 Brücke zwischen Codeartefakt und Profiler
6.2.2 Umsetzung der Profiler . . . . . . . . . . .
6.2.3 Umsetzung der JRUPI-Workbench . . . .
7 Evaluation
7.1 Fallbeispiel: Sortieralgorithmen . . . . .
7.1.1 Kurzvorstellung der Algorithmen
7.1.2 Setup der Testumgebung . . . . .
7.1.3 Auswertung der Ergebnisse . . . .
7.2 Flexibilität der Datenerhebung . . . . .
7.3 Kritische Bewertung . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
73
74
74
76
79
79
82
83
85
88
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
91
92
92
97
100
102
104
105
108
112
.
.
.
.
.
.
119
. 120
. 121
. 122
. 125
. 133
. 137
.
.
.
.
.
.
.
.
.
8 Zusammenfassung und Ausblick
139
8.1 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . 139
8.2 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
Literaturverzeichnis
146
Abbildungsverzeichnis
154
A CoolSoftware
A.1 CCM-Metamodell . . . . . . . . . . . . . . . . . . . . . . . .
A.2 Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
A.3 ECL für Beispiel . . . . . . . . . . . . . . . . . . . . . . . .
157
. 157
. 159
. 160
B Quellcode
163
B.1 Zentrale Bestandteile der Profiler . . . . . . . . . . . . . . . . 163
B.2 BTrace-Skript für Sortieralgorithmen . . . . . . . . . . . . . . 165
Kapitel 1
Einleitung
„We cannot rely on technological breakthroughs to be the silver bullet. We
need to be smarter in how we use what we already have available.“
[Gar07]
Energieeffizienz ist eines der Schlagworte, das in der Debatte um GreenIT immer wieder auftaucht. Kritische Stimmen behaupten, dass dadurch bei
gleichem Energieverbrauch lediglich mehr Rechenleistung oder mehr Speicherplatz erzielt wird. Effizienzsteigerung kann aber auch, im Sinne des einleitenden Zitats, als optimierte Verwendung der vorhandenen Ressourcen verstanden werden. In diesem Fall wird auch tatsächlich Energie eingespart.
Die Einsparung von Energie ist ein Thema, das zunehmend alle Bereiche
der Informations- und Kommunikationstechnik (IKT) betrifft. Das hat zum
Einen mit dem immensen Energiehunger bereits bestehender Infrastrukturen zu tun, zum Anderen aber auch, und das ist wohl noch bedenklicher, mit
dem enormen Wachstum der Branche auf globaler Ebene [HJMM09]. So unterschiedlich die Motivationen auch sein mögen, ob ökologisch, ökonomisch,
durch politischen Druck, zur Imageverbesserung oder schlichtweg um eine
Verlängerung der Batterielaufzeit bei mobilen Geräten zu erreichen, Eines
ist klar: Die Steigerung der Energieeffizienz ist ein erstrebenswertes Ziel.
Wie durch das einleitende Zitat verdeutlicht wird, wäre es naiv, sich
ausschließlich auf Verbesserungen von Seite der Hardwarekomponenten zu
verlassen. Die vorrangig eingesetzte Halbleitertechnik unterliegt natürlichen
Grenzen bezüglich des Einsparungspotenzials, das durch fundamentale Verbesserungen der Technologie erreicht werden kann [Gar07]. Um ein Maximum
an Energieeffizienz zu ermöglichen ist eine gesamtheitliche Betrachtung notwendig, die sowohl Hardware als auch Software einschließt. Nur durch die
Analyse des Zusammenspiels von Hard- und Software lässt sich eine effektive
Energieverwaltung zur Erreichung maximaler Energieeffizienz umsetzen.
7
8
KAPITEL 1. EINLEITUNG
Eine Frage der Auslastung
Die Verwendung von Virtualisierungstechniken in Rechenzentren, um eine
balancierte Lastverteilung zu erreichen, ist eine Möglichkeit. Durch Virtualisierung können mehrere Server auf einen Einzelnen konsolidiert werden, der
durchschnittlich eine höhere Auslastung besitzt. Damit wird das Problem
angegangen, dass viele Server, auch wenn sie quasi keine Arbeit verrichten,
eine beachtliche Menge Energie verbrauchen. Daraus folgt direkt eine weitere Möglichkeit: anstatt die vorhandene Hardware möglichst gleichmäßig und
hoch auszulasten, was nur innerhalb der Grenzen des Rechenzentrums möglich ist, erscheint die Forderung nach mehr Proportionalität zwischen Ressourcenauslastung und Energieverbrauch sinnvoll. Ist keine Proportionalität
gegeben, so ergibt sich die höchste Energieeffizienz bei einer Auslastung von
über 80%. Ein Wert, der weit über der typischen Betriebsregion (ca. 10-50%
bei Servern ohne Virtualisierung) liegt [BH07].
Das Ziel der Proportionalität erfordert eine gesamtheitliche Herangehensweise, die sowohl von Hardware, als auch von Software getragen wird. Eine
Vorreiterrolle spielen in diesem Zusammenhang die Kernkomponenten eines
jeden Rechnersystems: die Prozessoren. Getreu dem Sprichwort „Not macht
erfinderisch“, stammen die Innovationen meist aus dem Bereich der mobilen Endgeräte, mit ihrer chronischen Energieknappheit aufgrund begrenzter
Batteriekapazität, finden aber seid geraumer Zeit auch Einzug in Systeme
mit konstanter Stromversorgung. Moderne Prozessoren beherrschen Techniken zur dynamischen Anpassung von Taktrate und Versorgungsspannung
(Dynamic Voltage and Frequency Scaling (DVFS) [TWM+ 08]), um Energie
einzusparen. Zur effektiven Ausnutzung dieser Mechanismen ist eine softwareseitige Verwaltung nötig, die eine Anpassung entsprechend der Auslastung
realisiert (z.B. [PS06] unter Linux). Dem Vorbild der Prozessoren folgend,
können derartige Anpassungen auch für andere Hardwarekomponenten umgesetzt werden [HPIM+ 10].
Die Energieverwaltung, die sich um die dynamischen Anpassungen der
Hardwarekomponenten kümmert, ist typischerweise Aufgabe des Betriebssystems, da es die Kontrolle über alle Geräte besitzt [Tan07]. Eine effektive Verwaltung der heterogenen Ressourcen, die zudem über immer mehr
Betriebsmodi verfügen, könnte durch die Aufteilung in mehrere, eventuell
hierarchische organisierte, Ressourcenmanager erfolgen [Sax10]. Dies ist um
so sinnvoller, je größer die Systeme werden, da mit der Anzahl der zu verwaltenden Geräte die möglichen Energie- und Performancezustände um ein
Vielfaches steigen. Um den Ressourcenmanagern eine proaktive Energieverwaltung zu ermöglichen, benötigen sie Hinweise, welche Hardwareressourcen
in welchen Umfang von Software genutzt werden.
9
Ressourcennutzung als Indikator
Im Rahmen praktischer Untersuchungen, wie beispielsweise in [Fli01], konnte nachgewiesen werden, dass der Umfang der Ressourcennutzung direkten
Einfluss auf den Energieverbrauch hat und die Verwendung von Hardwarefeatures wie DVFS den Energiebedarf beachtlich senkt. Eine effektive Energieverwaltung ermöglicht jedoch eine weit höhere Einsparung, vor allem in
Anbetracht immer besserer Unterstützung seitens der Hardware. Dazu ist es
essentiell, den Ressourcenmanagern möglichst viele Informationen über das
Verhalten der Anwendungssoftware bezüglich der Ressourcennutzung zukommen zu lassen, um eine proaktive Energieverwaltung zu erleichtern. Da die
Ressourcennutzung offensichtlich sowohl direkten als auch indirekten Einfluss ausübt, zieht sich folgende These wie ein Leitmotiv durch die gesamte
Arbeit: Der Energieverbrauch eines Systems ist, in nicht vernachlässigbaren
Maße davon abhängig, ob und in welchem Umfang Hardwarekomponenten
von Software benutzt werden.
Um den Ressourcenmanagern die benötigten Hinweise zur Ressourcennutzung von Software bereitstellen zu können, muss die Ressourcennutzung
zunächst ermittelbar sein und anschließend in Form einer passenden Beschreibung zur Verfügung gestellt werden. Dabei sollte die Beschreibung so
abstrakt und kompakt wie möglich sein. Das Ziel der Arbeit besteht darin, dem Entwickler geeignete Methoden und Verfahren an die Hand zu geben, um den Ressourcenverbrauch einzelner Codeartefakten ermitteln und
ausdrücken zu können. Im Speziellen wird eine geeignete Methode zur Erhebung der Ressourcennutzungsdaten von Java-Codeartefakten, die von der
JVM (Java Virtual Maching) ausgeführt werden, entwickelt. Mit der JVM
muss eine weitere Abstraktionsebene überwunden werden, da sie essentielle
Bestandteile wie die Allokation und die Freigabe von Ressourcen fast gänzlich übernimmt. Dadurch wird zwar die Softwareentwicklung vereinfacht, in
der Konsequenz gestaltet sich aber eine detaillierte Datenerhebung über die
Ressourcennutzung schwierig und vorhandene Werkzeuge unterliegen etlichen Beschränkungen. So wie das Betriebssystem von der Komplexität der
Hardwarekomponenten abstrahiert, so abstrahiert die JVM weitgehend von
der Komplexität des Betriebssystems. Die Überbrückung dieser Abstraktionsebenen ist eine der großen Herausforderung dieser Arbeit.
Ein weiteres Problem besteht darin, dass Ressourcennutzungsdaten gewöhnlich plattformspezifisch sind. Daher reicht es im Allgemeinen nicht aus,
die Daten einmal am Entwicklersystem zu erheben und daraus eine Beschreibung abzuleiten. Aus diesem Grund ist es ein weiteres Ziel, dass die Lösung
auch am Zielsystem, und damit eventuell an einem Produktivsystem, anwendbar ist.
10
KAPITEL 1. EINLEITUNG
Kurzbeschreibung der Lösung
Zum Profiling (dem verwendeten Verfahren zur Datenerhebung) der Ressourcennutzung von Java-Anwendungen definiert diese Arbeit zunächst ein
konzeptuelles Framework, das Jrupi (Java Resource Usage Profiling Infrastructure) getauft wurde. Es umfasst zum Einen ein Architekturkonzept, das
zunächst plattformunabhängig beschreibt, wie das Profiling der Ressourcennutzung einer Java-Anwendung umgesetzt werden kann. Zum Anderen gibt
es dem Modellentwickler einen Workflow vor, wie eine Umsetzung von Jrupi
zu benutzen ist. Schließlich wird der Ansatz in Form einer Proof-of-Concept
Implementierung validiert.
Eine zentrale Rolle für die entwickelte Lösung spielen Verfahren zur dynamischen Ablaufverfolgung, die auch als dynamisches Tracing [Sun08] bezeichnet werden. Mit ihrer Hilfe lässt sich Tracing-Code an nahezu beliebigen Stellen, sogenannten Prüfpunkten, im Betriebssystem oder in Anwendungen einbringen (Instrumentierung). Dabei geht ihre Funktionalität weit
über das Einbringen einzelner Tracing-Statements hinaus. Sie sind in der
Lage, Kontextinformationen während der Laufzeit an den instrumentierten
Stellen aufzulösen und diese zu analysieren, zu filtern, zu aggregieren oder zusammenzufassen. Für die Programmierung der Prüfpunkte stehen mächtige
Skriptsprachen bereit, um die Daten in der genannten Art und Weise zu verarbeiten und Ausgaben zu erzeugen. Alles dynamisch während der Laufzeit
- ohne Rekompilieren oder Neustarten.
Verfahren zur Dynamischen Ablaufverfolgung sind von Natur aus sehr
betriebssystemspezifisch, wobei der bekannteste Verteter DTrace [Sun08]
für die Solaris Plattform (mit Ports nach BSD,OSX) ist. Für Linux wurde, aus Lizenzgründen, eine eigene (von DTrace inspirerte) Lösung namens
Systemtap [BPBS10] entwickelt, das in den Repositories fast aller größeren
Distributionen Einzug erhalten hat. Aufgrund der weiteren Verbreitung von
Linux wurde für die Proof-of-Concept Implementierung Systemtap gewählt.
Nun lassen sich mit SystemTap Profiler zur Datenerhebung auf Betriebssystemebene implementieren. Was noch fehlt ist eine genaue Zuordnung der Ressourcennutzung zu den Java-Codeartefakten, die gerade von der
JVM ausgeführt werden. Diese Zuordnung wird durch eine Instrumentierung
der entsprechenden Stellen in der Java-Anwendung erreicht. Die Proof-ofConcept Implementierung greift zu diesem Zweck auf Verfahren zur dynamischen Bytecode-Instrumentierung zurück, die den benötigten Code direkt
in die laufende Anwendung an zuvor festgelegte Stellen injizieren. Auch sie
lassen sich zur Laufzeit an die ausführende JVM binden.
Die aufgezeichneten Daten werden nach der Ausführung im Rahmen einer Offline-Analyse weiter verarbeitet (veredelt) und analysiert. Anschlie-
11
ßend können die auf diese Weise aufbereiteten Daten als Eingabe für weitere
Werkzeuge dienen. So können beispielsweise Verfahren der intelligenten Datenanalyse [Pae06] verwendet werden, um in den Daten nach mathematischen
Zusammenhängen - eine abstrakte Beschreibung - zu suchen. In Abbildung
1.1 ist der gesamte Vorgang schematisch dargestellt.
0: iconst_2
1: istore_1
2: iload_1
3: sipush 1000
6: if_icmpge
44
9: iconst_2
10: istore_2
11: iload_2
12: iload_1
13: if _icmpge
31
16: iload_1
17: iload_2
18: irem
19: ifne 25
22: goto 38
25: iinc 2, 1
28: goto 11
dyn.
dyn. BytecodeBytecodeInstrumentation
Instrumentation
Bytecode
JVM
Betriebssystem
Analyse
Analyse
(Offline)
(Offline)
Profiler
Profiler
Abbildung 1.1: Schematische Darstellung der Lösung
Während der Ausführung des Bytecodes (Zwischenrepräsentation) durch
die JVM instrumentieren die Profiler das Betriebssystem, um die Daten über
Ressourcennutzung zu sammeln, während gleichzeitig die aktuelle Stelle (in
der Ausführung) durch dynamische Bytecode-Manipulation „markiert“ wird.
Anschließend werden die Daten im Rahmen einer Offline-Analyse weiterverarbeitet.
Aufbau der Arbeit
In Kapitel 2 wird zunächst das CoolSoftware Projekt vorgestellt, in dessen
Rahmen die vorliegende Arbeit entstanden ist. Das erklärte Ziel von CoolSoftware ist die Optimierung der Energieeffizienz von IT-Infrastrukturen. Zu
diesem Zweck definiert es eine energiebewusste, komponentenbasierte Softwarearchitektur sowie eine Laufzeitumgebung zur dynamischen Optimierung.
Dazu wird für die konkreten Implementierungen von Softwarekomponenten
eine abstrakte Verhaltensbeschreibung bezüglich der Ressourcennutzung benötigt, wie sie bereits beschrieben wurde.
Daraufhin stellt Kapitel 3 eine Auswahl an Literatur und verwandter Arbeiten vor, die in Bezug zur gegebenen Aufgabenstellung stehen. Einerseits
geht das Kapitel allgemein auf Möglichkeiten zur Verhaltensbeschreibung von
Software ein und beschreibt Verfahren zur Gewinnung von Verhaltensmodel-
12
KAPITEL 1. EINLEITUNG
len. Andererseits widmet es sich dem Zusammenhang zwischen Software und
Energieverbrauch.
Da die Datenerhebung über die Ressourcennutzung einzelner Codeartefakte in Java eine relativ komplizierte Angelegenheit ist, werden in Kapitel 4
zunächst einige Grundlagen erläutert. Das umfasst zum Einen die betrachteten Teile der Systemumgebung (wie JVM und Betriebssystem), deren wichtigsten Konzepte erklärt werden, soweit sie zum Verständnis nötig sind. Zum
Anderen werden grundlegende Methoden zur Datenerhebung vorgestellt, die
bei der späteren Implementierung verwendet werden.
Nachdem die notwendigen Grundlagen erläutert wurden, wird in Kapitel 5
mit Jrupi ein konzeptuelles Framework zur Lösung der Aufgabe vorgestellt.
Erstens gibt es den Aufbau einer Infrastruktur vor, um detaillierte Daten
über die Ressourcennutzung von Java-Codeartefakten zu erheben und anschließend weiter zu verarbeiten. Zweitens enthält es eine Vorgehensbeschreibung (Workflow) für den Modellentwickler, die ihm Handlungsanweisungen
zur Benutzung der Infrastruktur vorgibt, um am Ende zu einer abstrakten
Beschreibung der Ressourcennutzung von Codeartefakten zu kommen.
Anschließend wird das konzeptuelle Frameweork Jrupi in Kapitel 6 mit
einer konkreten Umsetzung in Form einer Proof-of-Concept Implementierung
instanziiert. Sie zeigt, wie sich unterschiedliche Verfahren und Werkzeugen
kombinieren lassen, um eine effiziente und flexible Datenerhebung zu ermöglichen, und wie die Daten anschließend analysiert werden können, um ein
abstraktes Modell daraus abzuleiten.
In Kapitel 7 wird Proof-of-Concept Implementierung, und damit Jrupi,
anhand von Beispielen evaluiert. Hier treten diverse Sortieralgorithmen in
Bezug auf CPU-Nutzungsverhalten gegeneinander an und es wird gezeigt,
wie die Proof-of-Concept Implementierung spezielle Methode im laufenden
Eclipse bezüglich Netzwerknutzung profiliert.
Den Abschluss der Arbeit bildet Kapitel 8. Es fasst die wichtigsten Aspekte der Lösung zusammen und gibt einen Ausblick, welche zukünftigen Arbeiten als sinnvoll erachtet werden.
Kapitel 2
Das CoolSoftware Projekt
Die vorliegende Arbeit ist innerhalb des CoolSoftware Projektes1 angesiedelt,
welches Teil des CoolSilicon Spitzencluster2 ist, und durch das Bundesministerium für Bildung und Forschung (BMBF) finanziert wird. Daher skizziert
dieses Kapitel die wichtigsten Aspekte des Forschungsprojektes aus Sicht dieser Arbeit. Einerseits muss sich der entwickelte Ansatz in den vorgegebenen
Projektrahmen einfügen, andererseits aber auch von anderen Projektteilen
abgrenzen. Darüber hinaus sind Zielstellung sowie die bisherige Konzeption
und Umsetzung von CoolSoftware von entscheidender Bedeutung für die eigenen Entwicklungsentscheidungen. Alle Betrachtungen in den Folgekapiteln
sind vor dem Hintergrund der im CoolSoftware-Projekt definierten Zielen zu
sehen.
Das Kapitel gliedert sich in folgende Teile: Abschnitt 2.1 resümiert allgemein über die Motivation für das Projekt. Daraufhin wird im Abschnitt 2.2
skizzenhaft das CoolSoftware-Projekt selbst vorgestellt. Die Unterabschnitte
geben einen kurzen Abriss der Projektteile wieder, die von besonderer Relevanz für die späteren Erläuterungen sind. Den Abschluss bildet Abschnitt
2.3 mit der Einordnung der Aufgabenstellung.
2.1
Motivation
GreenIT - dieses Schlagwort kursiert seit mehreren Jahren innerhalb der
Branche. Aus „The Big Blue“ wird „The Big Green“, weil 3900 Server auf 30
Linux-Großrechner konsolidiert wurden3 . In der Tat wird dadurch ein erhebliches Einsparpotenzial beim Energieverbrauch von Rechenzentren verdeut1
www.cool-software.org
www.cool-silicon.de
3
www.ibm.com
2
13
14
KAPITEL 2. DAS COOLSOFTWARE PROJEKT
licht. Aber Steigerung der Energieeffizienz betrifft nicht nur dieses Gebiet.
In [Tan07] wird das Beispiel von Desktop-PCs aufgeführt. Unter der Annahme, dass jedes Gerät mit einem 200-Watt-Netzteil (wobei der durchschnittliche Wirkungsgrad 85% beträgt - 15% der zugeführten Energie werden also in Wärme umgewandelt) ausgestattet ist, würde der zeitgleiche Betrieb von 100 Millionen derartiger PCs einen Verbrauch 20.000 Megawatt
entsprechen. Diese Zahl entspricht etwa dem Gesamtausstoß von 20 mittelgroßen Kernkraftwerken. Durch eine Halbierung des Energieverbrauchs bei
den Geräten könnten dementsprechend 10 Kraftwerke eingespart werden.
Laut [HJMM09] betrug 2007 der weltweite Ausstoß an Treibhausgas, gemessen in CO2-äquivalentem Gas, etwa 45.000 Millionen Tonnen. Mit 820.000
Tonnen entfielen davon gerade einmal 2% auf die IT-Branche. So klein dieser
Wert auch erscheint, entsprach er dennoch der Menge an Gas, der durch den
weltweiten Flugverkehr verursacht wurde. Außerdem wird auf das Problem
hingewiesen, dass sich dieser Wert, trotz steigender Energieeffizienz, bis 2020
auf 3% vergrößern wird. Die Ursache liegt in der wachsenden Verbreitung
von IT-Technologien in den Schwellenländern, die selbst durch eine beständig steigende Leistungsfähigkeit nicht kompensiert werden könne. Daher ist
eine möglichst große Energieeinsparung aus ökologischer Sicht sehr erstrebenswert.
Ein weiterer Gesichtspunkt sind ökonomische Aspekte. In [HJMM09] wird
alleine innerhalb der EU ein Verbrauch von 40 Terrawattstunden, und dadurch Kosten in Höhe von 6 Milliarden Euro, für den Betrieb von Servern
und Infrastruktur angeführt. Eine Steigerung der Energieeffizienz führt also
zu einem wirtschaftlichen Nutzen im Sinne einer Kostenreduktion, die sich
positiv auf die Gewinn- und Verlustrechnung auswirkt. Einen Imagegewinn
bekommen Unternehmen quasi gratis dazu.
Das CoolSoftware-Projekt verfolgt den Ansatz, IT-Infrastrukturen in ihrer Gesamtheit hinsichtlich Energie zu betrachten. Dafür wird im Speziellen
untersucht, welchen Einfluss Software auf den Energieverbrauch hat. Schließlich ist es Software, die einerseits durch Inanspruchnahme Hardware auslastet, und andererseits die einzelnen Komponenten steuert. Grund genug, das
Energieverhalten als nicht-funktionale Eigenschaft in den Softwareentwicklungsprozess einzubeziehen.
2.2
Projektskizze
Das übergeordneten Ziel des CoolSoftware-Projektes besteht in der Optimierung der Energieeffizienz von IT-Infrastrukturen. Während sich die Bemühungen in der Vergangenheit meist auf einzelne Hardwarekomponenten
2.2. PROJEKTSKIZZE
15
konzentrierten, rückt nunmehr die Optimierung von und mit Software in den
Vordergrund. Dabei müssen sowohl Hard- als auch Softwarekomponenten in
die Betrachtungen einbezogen werden. Das Bestreben richtet sich auf die
Schaffung energieadaptiver, selbst-rekonfiguriernder Anwendungssysteme.
Dafür wurde der Ansatz des Energy Auto Tuning (EAT) [GWS+ 10a] entwickelt. Ein EAT-Softwaresystem ist durch zwei wesentliche Eigenschaften
gekennzeichnet:
• Eine energiebewusste, komponentenbasierte Softwarearchitektur, welche
durch das Cool Component Model (CCM) in Kombination mit der
Energy Contract Language (ECL) definiert ist.
• Eine Laufzeitumgebung, genannt THree Layer Auto Tuning Runtime
Environment (THEATRE).
CCM, ECL und THEATRE sind die drei Kernbestandteile des Projektes
und beschreiben zusammen die CoolSoftware-Architektur [GCW+ 10]. Daher
werden sie in den folgenden Unterabschnitten vorgestellt.
2.2.1
Komponentenmodell CCM
Um eine automatische Rekonfiguration in einen energieoptimalen Zustand zu
ermöglichen, wird für ein EAT-System eine spezielle Architekturbeschreibung
benötigt, die zusätzliche Eigenschaften hinsichtlich der spezifischen Anforderungen der Dimension Energie erfüllt. So reicht es nicht aus, lediglich den
strukturellen Aufbau der Software zu erfassen. Vielmehr ist es notwendig,
die komplette Infrastruktur mit einzubeziehen. Das bedeutet, dass eine explizite Verbindung zwischen den Bausteinen der Softwarearchitektur und der
Hardwareressourcen modellierbar ist. Zu diesem Zweck wurde das CCM als
ein zentraler Bestandteil der CoolSoftware-Architektur entwickelt, welches
im Detail in [CSG+ 10] spezifiziert ist.
Innerhalb des CCM stellt der Begriff der Komponente [Szy02] das Schlüsselkonzept dar. Aufbauend auf den zusätzlichen Voraussetzungen für EAT
wurden die Anforderungen an das Komponentenmodell wie folgt formuliert:
• Modellierung von Hard- und Software: Das CCM muss in der
Lage sein, sowohl Hard- als auch Software zu modellieren, um eine Betrachtung des Gesamtsystems zu erlauben. Dazu verwendet es in beiden
Fällen zur Beschreibung die zentralen Abstraktion der Komponente.
• Hierarchische Modellierung: Um die komplexen Strukturen von
Softwarekomponenten und der darunter liegenden Hardwareressourcen
16
KAPITEL 2. DAS COOLSOFTWARE PROJEKT
Varianten
Struktur
CCM
Verhalten
Abbildung 2.1: Zentrale Pakete im CCM
adäquat erfassen zu können, wird eine hierarchische Modellierung unterstützt. Einerseits wird dadurch die Wiederverwendbarkeit von Ressourcen und Softwarekomponenten gefördert. Andererseits führt ein
hierarchischer Aufbau zu einer reduzierten Komplexität, was der späteren Analyse zur Laufzeit zu Gute kommt.
• Quality-of-Service(QoS)-Eigenschaften: Als Basis für eine Optimierung zur Laufzeit müssen QoS-Parameter als nicht-funktionale Eigenschaft für Komponenten explizit definierbar sein.
• Variantenmodellierung: Jede Komponente innerhalb des CCM kann
in verschiedenen Implementierungsvarianten vorliegen. Der Unterschied
liegt beispielsweise in den QoS-Eigenschaften der angebotenen Dienste.
Daher weisen verschiedene Varianten einer Komponente für gewöhnlich
auch eine unterschiedliche Energiecharakteristik auf.
• Verhaltensmodellierung: Alle Komponenten müssen bezüglich ihres
Verhaltens beschreibbar sein. So besitzen Hardwarekomponenten typischerweise verschiedene Zustände (vergleiche Unterabschnitt 4.1.2), in
denen sie unterschiedlich viel Energie verbrauchen. Bei Softwarekomponenten hingegen dient das Verhaltensmodell der Beschreibung ob,
und in welchem Umfang, spezifische Ressourcen in Anspruch genommen werden.
Das CCM selbst ist durch ein geschichtetes Metamodell definiert. Darin
enthalten sind drei Pakete, die die Grundpfeiler des Modells bilden: Struktur(structure package), Verhaltens- (behavior package) und Variantenpaket (variant package), wie in Abbildung 2.1 dargestellt.
Im Folgenden werden die Kernkonzepte der drei Pakete vorgestellt. Alle
Worte, die in Schreibmaschinenschrift gedruckt sind, beziehen sich dabei
2.2. PROJEKTSKIZZE
17
auf Elementnamen des Metamodells. Die Metamodelle sind in reduzierter
Form in Anhang A.1 dargestellt.
Das Strukturpaket
Im Strukturpaket sind alle Elemente definiert, die für die strukturelle Modellierung hierarchischer IT-Infrastrukturen benötigt werden. Ein wichtiger
Aspekt ist die Vorgabe eines Typsystems. Die zentrale Abstraktion ist der
Komponententyp (ComponentType), für den drei weitere Spezialisierungen definiert sind: Benutzertyp (UserType), Softwarekomponententyp (SWComponentType) und Ressourcentyp (ResourceType). Dabei sind Softwarekomponententypen und Ressourcentypen in der Lage, Hierarchien zu bilden.
Zur Modellierung von Interaktionen zwischen einzelnen Komponenten
dienen Porttyp (PortType) und Portverbindungstyp (PortConnectorType).
Die Menge der Ports einer Komponente definiert ihre Schnittstelle nach außen, während ein Verbindungstyp jeweils zwei passende Porttypen miteinander verbindet. Zu unterscheiden sind hier angebotene (OUT), benötigte (IN)
sowie hybride (INOUT) Porttypen. Auf diese Weise lassen sich mögliche Verbindungen zwischen Komponenten definieren. Der Vertrag (Contract) einer
Komponente legt fest, welche Dienste von ihr angeboten beziehungsweise benötigt werden.
Mit Hilfe des Strukturpaketes lassen sich also Komponenten und ihre
Schnittstellen hierarchisch beschreiben, Verbindungen dazwischen definieren,
und Verträge schließen, die erfüllt werden müssen.
Das Verhaltenspaket
Um eine Entkopplung des Verhaltens einer Komponente von ihrer Struktur
zu erreichen, sind die Elemente zur Verhaltensbeschreibung in einem separaten Paket definiert. Ein wichtiges Konzept stellt in diesem Zusammenhang
die Verhaltensschablone (BehaviorTemplate) dar. Eine Verhaltensschablone beschreibt das Verhalten (einer Komponente) und besitzt eine Menge
von Kostenparametern (CostParameter). So werden beispielsweise konkrete Hardwareressourcen typischerweise mit Zustandsautomaten beschrieben,
deren Zustände und Zustandsübergänge mit Kostenparametern (wie Zeitoder Energieverbrauch) annotiert werden können. Eine konkrete Implementierungsvariante ist später dafür verantwortlich, die Kostenparameter an konkrete Werte zu binden. Verschiedene Workloads (die in einer eigenen DSL
beschreiben werden) lösen schließlich, zu einem späteren Zeitpunkt, das beschriebene Verhalten aus. Daher sind diese Konzepte von besonderer Bedeutung für die Simulation der Infrastruktur.
18
KAPITEL 2. DAS COOLSOFTWARE PROJEKT
Analog zu Ports bei Komponententypen besitzen Verhaltensschablonen
Pins, die ihre externe Schnittstelle definieren. Diese sind über das NavigatingElement mit dem zugehörigen Zustandsautomaten verbunden. Die Pins der
Schablone lassen sich mit den Ports einer Komponente verbinden, wodurch
ein Zusammenhang zwischen dem Aufruf der Schnittstelle der einen Komponente mit dem Verhalten einer anderen (z.B. einer Hardwareressource)
ausdrückbar ist.
Das Variantenpaket
Wie in Abbildung 2.1 bereits angedeutet ist, stellt das Variantenpaket das
Verbindungsglied zwischen Struktur- und Verhaltenspaket dar. Hier werden
Elemente zur Definition von konkreten Varianten einer Komponente definiert. Zwei zentrale Konzepte sind Komponente (Component) und Verhalten
(Behavior). Eine Komponente hat einen Komponententyp, und ein Verhalten hat eine Verhaltensschablone (aus Verhaltenspaket). Die Spezialisierung
einer Komponente entspricht den Möglichkeiten, die aus dem Typsystem des
Strukturpaketes bekannt sind. Des Weiteren verfügt eine Komponente analog über eine Menge von Ports und PortConnector mit entsprechenden Typ.
Eine konkrete Variante einer Komponente entsteht durch die Verbindung eines Komponententyps mit einer Verhaltensschablone und Bindung konkreter
Werte an die Kostenparameter. Zur Herstellung dieser Verbindung dient das
Link-Element, welches die Ports der Variante mit den Pins der Schablone
verbindet.
Der beschriebene Aufbau ermöglicht es, Varianten kompletter (Sub-)Systeme zu definieren. Solche komplexen Variationen können später von der
Laufzeitumgebung (THEATRE) verwendet werden, um alle Systemkonfiguration zu bewerten und eine geeignete auszuwählen, die den Bedürfnissen des
Benutzers bei gleichzeitiger Erreichung optimaler Energieeffizienz entspricht.
2.2.2
Vertragssprache ECL
Die ECL ist eine Kompositionssprache für CCM Komponenten mit speziellen
Fokus auf Quality of Service-(QoS-)Parameter, welche nicht-funktionale Eigenschaften als zusätzliches Kompositionskriterium definierbar machen. Wie
der Name bereits andeutet, ist es mit der Sprache möglich, Verträge (Contract) als Verbindungsglied zwischen angebotenen (provided) und benötigten (required) Diensten (Services) von Komponenten deklarieren. Dadurch
lassen sich die Abhängigkeitsbeziehungen zwischen konkreten Softwarekomponenten untereinander sowie Hard- und Softwarekomponenten (Varianten)
explizit festhalten. Für Details zur Sprache sei auf die ECL-Spezifikation
2.2. PROJEKTSKIZZE
19
[WCG+ 10] verwiesen. ECL basiert im Wesentlichen auf folgenden Konzepten:
• Definition von Qualitätscharakteristiken:
Eine Charakteristik dient als Maßeinheit, um eine spezifische Qualität ausdrücken zu können. So kann beispielsweise die Framerate eines
Videostroms als Qualitätscharakteristik definiert werden. Dabei besitzt
jede Charakteristik eine gewissen Domäne (domain), in der sie definiert
ist. Im Beispiel der Framerate wäre die Domäne Anzahl der Frames pro
Zeiteinheit (z.B. Sekunde), repräsentiert durch eine positive Ganzzahl.
• Beschreibung von Ressourcen:
ECL bietet außerdem Möglichkeiten um Hardwareressourcen beschreiben zu könnnen. Zur Definition einer Ressource können wiederrum
Qualitätscharakteristiken verwendet werden. Eine mögliche Ressource
wäre zum Beispiel ein Netzwerkgerät, welches als Qualitätscharakteristik die verfügbare Bandbreite aus der Domäne der rationalen Zahlen
mit der Einheit Menge an übertragenen Bits (z.B. KBit) pro Zeiteinheit
(z.B. Sekunde) definiert.
• Definition von Profilen für CCM Komponenten:
Profile sind das wichtigste Werkzeug, um verschiedene Komponenten
und ihre Abhängigkeiten untereinander beschreiben zu können. Auf
oberster Ebene werden die einzelnen Zustände (state), in denen sich
eine Komponente zu einem Zeitpunkt befinden kann, sowie Zustandsübergänge (transition) dazwischen, beschrieben. Dabei können Profile weiter nach ihrer Art in Hard- und Softwarekomponentenprofile
differenziert werden.
Im Falle einer Hardwarekomponente Netzwerk könnte ein Profil die Zustände Verbunden, Getrennt, und jeweils eine Transition vom einen Zustand in den anderen, definieren. Dabei werden einzelne Zustände durch
einen spezifischen Energieeffekt charakterisiert, der beispielsweise den
jeweiligen Energieverbrauch angibt. Die Beschreibung von Transitionen
hingegen erfolgt mit Hilfe von Event-Condition-Action(ECA)-Regeln,
die angeben, wann (und unter welchen Bedingungen) der Übergang erfolgt. Darüber hinaus besitzen auch Zustandsübergänge einen Energieeffekt. Mit ECL können also Hardwarekomponenten durch erweiterte
Zustandsmaschinen repräsentiert werden.
Das Profil einer Softwarekomponente stellt das zentrale Verbindungsglied zwischen allen bisherigen Beschreibungsformen dar. Um die Konzepte zu erläutern und ein Gefühl für die Sprache zu schaffen, zeigt
Listing 2.1 ein Beispiel.
20
KAPITEL 2. DAS COOLSOFTWARE PROJEKT
1 profile P l a y e r P r o f i l e for VideoPlayer {
2
state h i g h Q u a l i t y {
3
uses [ S e r v e r P r o f i l e . h i g h Q u a l i t y ] ;
4
provides [ c h a r a c t e r i s t i c frameRate = 3 0 ]
5
and [ c h a r a c t e r i s t i c image_width = 3 5 2 ]
6
and [ c h a r a c t e r i s t i c image_height = 2 8 8 ] ;
7
resources
8
[ c h a r a c t e r i s t i c bandwidth = 4 5 0 ] ,
9
[ c h a r a c t e r i s t i c cpu_usage = 6 6 . 7 ] ,
10
[ c h a r a c t e r i s t i c ram = 2 0 0 . 0 ] ;
11
}
12
state l o w Q u a l i t y {
13
uses [ S e r v e r P r o f i l e . l o w Q u a l i t y ] ;
14
provides [ c h a r a c t e r i s t i c frameRate = 1 0 ]
15
and [ c h a r a c t e r i s t i c image_width = 1 7 6 ]
16
and [ c h a r a c t e r i s t i c image_height = 1 4 4 ] ;
17
resources
18
[ c h a r a c t e r i s t i c bandwidth = 1 5 0 ] ,
19
[ c h a r a c t e r i s t i c cpu_usage = 2 5 . 0 ] ,
20
[ c h a r a c t e r i s t i c ram = 5 0 . 0 ] ;
21
}
22
t r a n s i t i o n any−state −> any−state {}
23
precedence h i g h Q u a l i t y , l o w Q u a l i t y ;
24 }
Listing 2.1: ECL Beispiel VideoPlayer aus [GCW+ 10]
Das Profil mit Namen PlayerProfile wird für eine Softwarekomponente namens VideoPlayer definiert. In diesem Fall besitzt der Player
zwei Zustände (lowQuality und highQuality) sowie eine bidirektionale Transition dazwischen. Innerhalb der Zustände kann angegeben werden, welche Teile eines anderen Profils benötigt (uses), welche Qualitätscharakteristiken angeboten (provides), und welche Hardwarekomponenten (resources) dabei in Anspruch genommen werden. Die Zustandsbeschreibungen implizieren weiterhin, dass die Komponente in
zwei Varianten, eine für geringe, und eine für hohe Qualität, vorliegen
muss.
Insgesamt beinhaltet das Profil alle nötigen Informationen, um festzustellen, wie Komponenten verteilt (deployed) und miteinander kombiniert (composed) werden können, bei gleichzeitiger Einhaltung gewisser QoS-Parameter. Daher können Profile auch als Einschränkungen
Constraints verstanden werden, die üblicherweise vom Komponentenentwickler festgelegt werden. Mit ihnen lassen sich sowohl Abhängig-
2.2. PROJEKTSKIZZE
21
Abbildung 2.2: Autonome Kontrollschleife eines Auto Tuning Systems, aus
[DDF+ 06] (neu gezeichnet)
keiten unter Softwarekomponenten, als auch Abhängigkeiten zwischen
Soft- und Hardwarekomponenten ausdrücken.
2.2.3
Laufzeitumgebung THEATRE
Mit den bisher vorgestellten Teilen der CoolSoftware-Architektur können
Komponenten (Hard- und Software) und ihre Abhängigkeiten untereinander zur Entwicklungszeit beschrieben werden. Die Architekturbeschreibung
enthält darüber hinaus zusätzliche Informationen, die für eine Abschätzung
des Energieverhaltens von Bedeutung sind. Um nun eine Optimierung des
Energieverbrauchs zu erlauben, müssen diese Informationen zur Laufzeit interpretiert und mit weiteren Informationen, die nicht statisch vorhanden sind,
kombiniert werden, um schließlich angemessen reagieren zu können. Das setzt
eine übergeordnete Autorität voraus, die in CoolSoftware als THree Layer
Auto Tuning Runtime Environment (THEATRE) bezeichnet wird.
Der Name leitet sich zum Einen durch den Einbezug von drei Schichten,
zum Anderen durch die Verfolgung eines Auto Tuning Ansatzes [DDF+ 06] ab.
Ein Auto Tuning System zeichnet sich durch eine autonome Kontrollschleife
aus, wie sie in Abbildung 2.2 dargestellt ist. Ein Schleifendurchlauf ist durch
vier Stufen charakterisiert:
• Sammeln (Collect) von Daten über das betrachtete System.
• Analysieren (Analyse) der gesammelten Daten.
• Entscheiden (Decide), welche Maßnahmen für ein Tuning nötig sind
(auf Grundlage der ausgewerteten Daten).
• Handeln (Act) - entspricht der aktiven Umsetzung der getroffenen
Entscheidungen.
22
KAPITEL 2. DAS COOLSOFTWARE PROJEKT
Users
Contextor
Energy Efficiency
Software Components
Local Energy
Manager 1.1
Local Energy
Manager 1.2
Local Energy
Manager 1
Global Energy
Manager
Local Energy
Manager 2
Resources (Hardware, OS, VM, …)
Local Resource
Manager 1
Local Resource
Manager 2.1
Global Resource
Manager
Local Resource
Manager 2
Abbildung 2.3: Architektur des THEATRE, aus [GWS+ 10b]
Ein System, das diese Schritte umsetzt, kann sich dynamsich (zur Laufzeit) und automatisch an immer neue Situationen anpassen. Hinsichtlich
Energieoptimierung muss ein derartiges System in der Lage sein, relevante Daten zu sammeln, das System bezüglich alternativer Konfigurationen zu
analysieren, Entscheidungen abzuleiten, und schließlich das System automatisch in einen energieoptimalen Zustand zu rekonfigurieren.
Zur Umsetzung eines Energy Auto Tuning (EAT) Systems wird eine spezielle Laufzeitumgebung benötig. Einerseits braucht sie Zugang zu relevanten
Information und muss diese sinnvoll auswerten können. Andererseits muss sie
in der Lage sein, entsprechende Rekonfigurationen praktisch durchzuführen.
Um diesen Aufgaben nachkommen zu können, erstreckt sich die THEATRE über drei Schichten. Die grob-granulare Architektur ist in Abbildung 2.3
dargestellt und wird im Folgenden, gegliedert nach den einzelnen Schichten,
erläutert.
• Nutzerschicht:
Das CoolSoftware-Projekt geht von der Annahme aus, dass dem Benutzer bei der Steigerung der Energieeffizienz eine besondere Bedeutung zukommt. Schließlich ist es der Benutzer, der weiß, welche QoSParameter er möchte beziehungsweise benötigt. Mit der Wahl der Parameter übt er also Einfluss auf den Energieverbrauch aus. Aufbauend
23
2.2. PROJEKTSKIZZE
auf diesen Gedanken wird Energieeffizienz als Kosten-Nutzen-Funktion
in [GWS+ 10b] wie folgt definiert:
wi ∗ N utzermetriki
N utzen
=
Energieef f izienz =
Energieverbrauch
Energieverbrauch
z.B. 0.7 ∗ Antwortzeit + 0.3 ∗ Auf lösung
=
Energieverbrauch
(2.1)
P
Durch Inanspruchnahme von Diensten induzieren Benutzer einen Workload auf Komponenten. Dabei ist der Nutzen für den Benutzer durch
seine Bedürfnisse (wahrnehmbare Qualitäten = Nutzermetrik), also den
QoS-Parametern, die unterschiedlich gewichtet sein können (w), bestimmt. Die Kosten hingegen äußern sich in Form des dafür benötigten
Energieverbrauchs. Um im Allgemeinen die Bedürfnisse von Benutzern
zu erfassen, sammeln Kontextoren (Contextor) (vgl. Abb. 2.3) Daten
über die Wünsche der Benutzer. Effizienz stellt demnach ein Optimierungsproblem der in Gleichung 2.1. angegebenen Funktion dar.
• Ressourcenschicht:
Für die Verwaltung der Hardwarekomponenten sind Ressourcenmanager (Resourcemanager) [GWS+ 10b] zuständig. Jede Ressource besitzt
einen eigenen, lokalen Manager (LRM), der Informationen zum aktuellen Zustand der Ressource liefert und deren Steuerung übernimmt. Bei
den gelieferten Informationen kann es sich beispielsweise um vorhandene Kapazitäten, Verfügbarkeiten oder Auslastungen handeln. Dabei
kennen LRM jeweils andere, adjazente LRM anderer Komponenten.
Schließlich werden die Informationen aller LRM im globalen Resourcemanager (GRM) aggregiert. Dieser verfügt damit über Wissen, das
die gesamte Infrastruktur abdeckt (z.B. welche Ressourcen gerade verfügbar sind und wie man sie erreichen kann). Darüber hinaus kann der
GRM von allen Komponenten ihren aktuellen Energiezustand erfragen.
• Softwarekomponentenschicht
Neben den Ressourcenmanagern existieren Energiemanager (Energymanager) [GWS+ 10b], deren grundsätzlicher Aufbau ähnlich ist. Auch
hier besitzt jede Softwarekomponente einen eigenen, lokalen Energiemanager (LEM), der wiederum andere adjazente LEM kennt. Jeder
LEM besitzt Wissen über die Komponente, für die er zuständig ist.
Dies umfasst sowohl bereitgestellte und benötigte Dienste, als auch In-
24
KAPITEL 2. DAS COOLSOFTWARE PROJEKT
formationen zum Einsetzen (Deployment), Umsiedeln (Redeployment)
und der Rekonfiguration einer Komponente.
Die oberste Instanz stellt der globale Energiemanager (GEM) dar, dem
alle validen Konfigurationen von Komponenten bekannt sind. In Zusammenarbeit mit dem GRM und dem Wissen seiner LEM ist er in der
Lage, eine optimale Konfiguration des Gesamtsystems bezüglich des
Energieverbrauchs abzuleiten.
Die Rekonfiguration (vgl. Act aus Abbildung 2.2) geschieht schließlich durch die Kooperation von Ressourcenmanagern und Energiemanagern. Nachdem durch den GEM analysiert und über die optimale
Konfiguration entschieden wurde, ist der GEM für die Verteilung der
Komponenten zuständig, während der GRM dafür sorgt, dass geeignete
Hardwarekomponenten in entsprechenden Energiezuständen bereitstehen.
2.3
Aufgabenstellung und Einordnung
Das CoolSoftware Projekt hat als Hauptziel die Optimierung der Energieeffizienz von IT-Infrastrukturen. Um den speziellen Bedürfnissen energieadaptiver, selbst-rekonfigurierender Anwendungssysteme im Rahmen des EATAnsatzes gerecht zu werden, müssen zwei Gesichtspunkte beachtet werden:
1. Zur Entwicklungszeit: Die Softwarearchitektur muss für eine Energieoptimierung ausgelegt sein, wofür das CCM und die ECL verantwortlich
sind.
2. Zur Laufzeit: Die Ausführungsumgebung muss die zusätzlichen Informationen der Softwarearchitektur mit Laufzeitinformationen kombinieren, um das System in einen energieoptimalen Zustand zu rekonfigurieren.
Wie in Abschnitt 2.2.1 erläutert wurde, wird das Verhalten von Hardwarekomponenten innerhalb des CCM mit Zustandsdiagrammen, sogenannten
Energy State Charts modelliert. Diese Modelle können die Ressourcenmanager, und damit auch der globale Energiemanager, des THEATRE verwenden,
um ihre Entscheidungen, beispielsweise über die Wahl einer passenden Hardwareressource, zu treffen.
Über das Verhalten von Hardwareressourcen hinaus wird auch eine Beschreibung des Verhaltens von Softwarekomponenten benötigt. Während sich
Hardwareressourcen über die zur Verfügung stehenden Energiezustände sowie
den möglichen Zustandsübergängen dazwischen beschreiben lassen, gestaltet
2.3. AUFGABENSTELLUNG UND EINORDNUNG
25
sich dieser Prozess bei Software deutlich schwieriger. Eine Verhaltensbeschreibung von Software im Kontext des CoolSoftware Projekt dient in erster Linie
dazu, Rückschlüsse auf den Energieverbrauch der darunter liegenden Hardware zu erlauben. Allerdings ist der Energieverbrauch der Hardware stark
davon abhängig, ob und auf welche Art und Weise Ressourcen von der Software verwendet werden.
Die vorliegenden Arbeit hat zur Aufgabe, die Lücke zwischen Codeartefakten und Ressourcennutzung zu schließen, und aus den gefundenen Zusammenhängen eine geeignete Verhaltensbeschreibung für CCM Komponenten
in Hinsicht auf ihren Energieverbrauch abzuleiten. Eine solche Beschreibung
kann von der Laufzeitumgebung verwendet werden, um beispielsweise eine geeignete Implementierungsvariante einer Komponente auszuwählen oder
über das optimale Deployment einer Komponente zu entscheiden.
Abhängig von der Art der Verhaltensbeschreibung gibt es zwei mögliche
Integrationsstellen. Ein Verhaltensmodell im klassischen Sinne (z.B. spezielles Zustandsdiagramm) könnte durch die Erweiterung des CCM Metamodells
ausgedrückt werden. Dazu müssten zusätzliche Elemente, die zur Beschreibung eines BehaviorTemplate für Softwarekomponenten nötig sind, im Verhaltenspaket ergänzt werden. Für alternative Modelle bietet sich aber auch
die ECL an. So könnte die ECL ohne Probleme dazu verwendet werden,
Heuristiken über die Ressourcennutzung auszudrücken.
Im Zusammenhang mit der Ermittlung der Ressourcennutzung von CCM
Komponenten ergeben sich einige besondere Herausforderungen:
1. Eine Bestimmung der Ressourcennutzung kann nur zur Laufzeit erfolgen.
2. Für die Implementierung von CCM Komponenten ist vorrangig die Programmiersprache Java vorgesehen, wodurch eine zusätzliche Abstraktionsebene in Form der Java Virtual Machine eingeführt wird.
3. Das Entwicklersystem ist selten auch das System, auf dem eine Komponente eingesetzt wird. Der Ressourcenverbrauch muss also auch für
das letztendliche Zielsystem ermittelbar sein.
26
KAPITEL 2. DAS COOLSOFTWARE PROJEKT
Kapitel 3
Verwandte Arbeiten und
Literatur
Sowohl der Einfluss von Software auf den Energieverbrauch als auch die Verhaltensbeschreibung von Software selbst waren in der Vergangenheit wichtiger Gegenstand in Forschung und Entwicklung. Dieses Kapitel fasst eine
Auswahl aus Literatur und aktuellen Entwicklungen in der Forschung (Stateof-the-Art) zusammen, die im Rahmen der vorliegenden Arbeit als bedeutsam
eingestuft werden.
Abschnitt 3.1 widmet sich den Möglichkeiten zur Verhaltensbeschreibung
von Software sowie deren Gewinnung. Nachdem eine Auswahl theoretischer
Grundlagen erläutert wurde, werden anschließend diverse Arbeiten zur Extraktion von Verhaltensbeschreibungen aus bestehender Software vorgestellt.
Abschließend erfolgt eine Diskussion der Verhaltensbeschreibung von Softwarekomponenten im Kontext von CoolSoftware. Daraufhin befasst sich Abschnitt 3.2 zunächst allgemein mit dem Zusammenhang zwischen Software
und Energieverbrauch und klärt anschließend, in einer State of the Art Analyse, wie der Einfluss von Software auf den Energieverbrauch aussehen kann.
3.1
Verhaltensbeschreibungen
Die Beschreibung eines Verhaltens findet typischerweise auf einer abstrakten
Ebene statt. Intuitiv denken Softwareingenieure dabei an Modelle. Allerdings
wird der Begriff Verhaltensmodell in der Softwaretechnik üblicherweise mit
konkreten Repräsentationsformen von Verhaltensmodellen assoziiert. Dieser
Abschnitt erläutert daher zunächst einige theoretische Grundlagen, um eine
allgemeinere Sichtweise auf Modelle zu eröffnen und das Vokabular für eine
angemessene Diskussion zur Verfügung zu stellen. Anschließend erfolgt eine
27
28
KAPITEL 3. VERWANDTE ARBEITEN UND LITERATUR
State-of-the-Art Analyse zu Verhaltensmodellen und ihrer Gewinnung. Zum
Abschluss werden die verschiedenen Modellarten hinsichtlich ihrer Eignung
für CoolSoftware diskutiert
3.1.1
Theoretische Hintergründe
Modelle sind allgegenwärtig. Vor allem im Ingenieurwesen und in der Forschung sind sie ein explizites Themengebiet. Im Ingenieurwesen helfen sie
oft, Artefakte zu erzeugen, indem sie Informationen über die Konsequenzen
der Erzeugung dieser Artefakte zur Verfügung stellen, bevor diese tatsächlich produziert werden. Die Wissenschaft liefert hingegen häufig Theorien,
die eine spezielle Form eines Modells darstellen [Lud03].
Der Modellbegriff
Eine allgemeingültige Definition des Begriffs Modell gestaltet sich schwierig,
wenn nicht gar unmöglich. Allerdings liefert die Allgemeine Modelltheorie
(AMT) eine theoretische Basis, die eine angemessene Diskussion über geeignete Modelle erlaubt. In der AMT nach Stachowiak [Sta73] werden folgende
Kriterien genannt, die Modelle von anderen Artefakten unterscheiden:
• Abbildungskriterium: Es existiert ein Original (Objekt oder Phänomen), das auf das Modell abgebildet wird. Dabei ist es nicht erforderlich, dass das abzubildende Objekt tatsächlich in der Realität
vorhanden ist.
• Reduktionskriterium: Nicht alle Eigenschaften des Originals werden
auf das Modell abgebildet. Durch Abstraktion, die auf einen gewissen
Zweck hingerichtet ist, wird die Informationsmenge auf eine handhabbare Größe reduziert. Daher kann das Modell auf Situationen reagieren,
mit denen das Original eventuell nicht umgehen kann [Lud03].
• Pragmatisches Kriterium: Das Modell ist nützlich, d.h. es kann
das Original zu bestimmten Zwecken ersetzen. Daher kommen Modelle
häufig zum Einsatz, wenn die Verwendung des Originals zu aufwändig
wäre oder oder schlichtweg nicht möglich ist.
Die Modellbildung
Der Prozess der Erstellung eines Modells wird in der AMT als Modellbildung
bezeichnet, die dem in der Softwaretechnik üblichen Begriff der Modellierung
entspricht. Ein Modell wird zu einen bestimmten Zweck von einem Subjekt
29
3.1. VERHALTENSBESCHREIBUNGEN
(dem Ersteller) für ein Original konstruiert, wie in Abbildung 3.1 dargestellt. Dabei ist es möglich, dass das Original selbst ein Modell ist. Sieht man
beispielsweise den Quellcode als Modell für die auszuführenden Schritte auf
Maschinenebene, so ist jedes Modell der Software ein Modell für ein Modell
(Quellcode) [Lud03].
Original
Original
Modell
Modell
Subjekt
Subjekt
Zweck
Zweck
Abbildung 3.1: Prozess der Modellbildung nach der AMT
Ein wichtiges Unterscheidungskriterium der AMT ist die Aufteilung in
deskriptive und präskriptive Modellierung. Erstere bezeichnet die Modellierung eines bereits existierenden (oder zukünftigen, aber nicht gestaltbaren)
Originals, während bei Letzterer ein zu schaffendes, gestaltbares Original
modelliert wird. In diesen beiden Kriterien lässt sich eine Korrelation zu den
Begriffen Forward Engineering und Reverse Engineering erkennen [CI90].
• Forward Engineering ist der traditionelle Prozess, von hohen Abstraktionsgraden und logischen, implementierungsunabhängigen Entwürfen, zu physischen Implementierungen zu kommen.
• Reverse Engineering ist der Prozess der Analyse eines Systems, um
Systemkomponenten und ihre Beziehungen zu identifizieren, sowie Repräsentationen des Systems in einer anderen Form oder auf einer anderen Abstraktionsstufe zu erstellen.
Nach diesen Definition hat die Modellierung im Rahmen eines Forward Engineering Ansatzes also einen präskriptiven Charakter, während eine Modellextraktion beim Reverse Engineering einer deskriptiven Modellbildung
entspricht.
30
KAPITEL 3. VERWANDTE ARBEITEN UND LITERATUR
Systemtheorie
Die Systemtheorie geht davon aus, dass grundlegende Konzepte in allen Systemen, so unterschiedlich sie auch anmuten mögen, zu finden sind. Als interdisziplinäre Herangehensweise bildet sie auch innerhalb der Informatik
die Grundlage zahlreicher Theorien und Modelle, wie beispielsweise für die
Theorie der Modellierung und Simulation [ZPK00]. Hier spielt vor allem die
mathematische Systemtheorie eine entscheidende Rolle.
Besonders im Zusammenhang mit Verhaltensbeschreibungen lohnt ein
Blick auf diese weit allgemeinere Theorie, da sie explizit zwischen der Struktur und dem Verhalten eines Systems unterscheidet:
• Struktur: Die interne Struktur (innerer Aufbau) eines Systems beinhaltet seinen Zustand, einen Zustandsübergangsmechanismus (Vorschrift,
wie aktueller Zustand durch Input in zukünftigen überführt wird) und
ein Mapping vom Zustand auf die Ausgabe.
• Verhalten: Das externe Verhalten (äußere Manifestation) eines Systems ist die Beziehung, die es zeitlich zwischen seinen Eingaben und
Ausgaben einführt. Das Ein- und Ausgabeverhalten besteht aus Paaren
von Datensätzen (input-output), die direkt am System oder an einem
repräsentativen Modell erhoben werden.
Abbildung 3.2 zeigt die grundlegenden Konzepte eines Systems aus einer
funktionalen Sichtweise (strukturelle und hierarchische Sichtweisen werden
hier nicht behandelt). Aus dieser erscheint das System als Blackbox, die nichts
über die interne Struktur beziehungsweise die einzelnen Elemente preis gibt.
Das System interagiert über Ein- und Ausgaben mit der Systemumgebung
und kann mehrere Zustände besitzen.
Eingabe
(input)
Zustand (state)
Ausgabe
(output)
Abbildung 3.2: Grundlegende Konzepte eines Systems
Die Systemanalyse versucht, das Verhalten eines (möglicherweise noch
nicht existierenden) Systems basierend auf dessen Struktur zu verstehen.
Unter der Annahme, dass über die interne Struktur des Systems nichts bekannt ist (Blackbox), wie in Abbildung 3.2, so bezeichnet Systeminferenz den
3.1. VERHALTENSBESCHREIBUNGEN
31
Versuch, die Struktur aus Beobachtungen am realen System (Quellsystem)
zu „raten“ [ZPK00].
Dabei werden typischerweise Ein- und Ausgaben erfasst, um anschließend
ein generatives System oder gar ein Struktursystem aus den beobachteten
Daten zu rekonstruieren. Ein generatives System versucht, die Beziehung
zwischen Ein- und Ausgangsdaten möglichst genau nach zu bilden. Der Prozess wird auch als Modellkonstruktion bezeichnet. Typische Ausdrucksformen
derartiger Modelle umfassen Anweisungen, Regeln, Gleichungen oder Constraints, die das Verhalten beschreiben.
3.1.2
Verhalten: Beschreibungsformen und ihre Gewinnung
Die Konzeption eines Verhaltensmodells, das in der Lage ist, vorhandene Softwarekomponenten im Detail zu beschreiben, ist nicht das Ziel dieser Arbeit,
da der Fokus auf ihrer Ressourcennutzung liegt. Nichtsdestotrotz weist der
Prozess der Modellextraktion, also die Gewinnung eines Verhaltensmodells
aus vorhandenen Softwareartefakten, eine gewisse Parallelität zur vorliegenden Problemstellung auf. Alle Ansätze greifen dazu, wenngleich dies nicht
immer explizit erwähnt wird, auf Verfahren der Programmanalyse zurück.
Verfahren zur Programmanalyse lassen sich in zwei Hauptbereiche unterscheiden:
• statische Programmanalyse beschäftigt sich mit der Untersuchung
des Quellcodes, um auf Eigenschaften eines Programms zu schließen,
ohne es auszuführen. Typische Vertreter sind Analysen bezüglich des
Kontrollflusses oder des Datenflusses.
• dynamische Programmanalyse bezeichnet alle Verfahren, die den
Quellcode zur Ausführung bringen, um anschließend auf gewisse Eigenschaften des Programms schließen zu können.
Da die relevanten Informationen zur Ressourcennutzung lediglich zur Laufzeit verfügbar sind, sind ausschließlich dynamische Verfahren von Interesse.
Modellextraktion
Endliche Zustandsautomaten (Finite State Automata (FSA)) stellen die mit
Abstand verbreitetste Form dar, um das Verhalten von Software zu repräsentieren. Modellextraktion bedeutet in diesem Fall, einen FSA auf der Grundlage zur Laufzeit gesammelter Daten zu „lernen“. Erst kürzlich erschien zu
32
KAPITEL 3. VERWANDTE ARBEITEN UND LITERATUR
diesem Thema eine ausgezeichnete1 Doktorarbeit von Dallmeier [Dal10]. Im
Folgenden werden diese und weitere artverwandte Arbeiten auf informale
Weise zusammengefasst (für formale Definitionen sei auf Kapitel 3 in [Dal10]
verwiesen).
Object Behavior Models Dallmeier führt verallgemeinernd den Begriff
des Softwareausführungsmodells (software execution model) ein, womit Modelle beschrieben werden, die das Verhalten eines Programms in ein Modell einfangen, welches automatisiert verarbeitet werden kann. Der Zweck
derartiger Modelle wird in ihrer Nützlichkeit für das Debugging sowie dem
Verständnis existierender Programme gesehen. Aufbauend auf einer guten
Literaturanalyse wird eine neue Modellart, die sogenannten Objektverhaltensmodelle (object behavior models), entwickelt.
Das zentrale Mittel zur Datenerhebung stellt eine Ablaufverfolgung der
Ausführung dar, die Ereignisse von Interesse aufzeichnet. Die Aufzeichnungen werden in sogenannten execution traces festgehalten, die aus einer Sequenz einzelner Trace-Einträge bestehen. Ein Trace-Eintrag verbindet erhobene Daten mit einem Zeitstempel. Das Problem der Modellextraktion wird
schließlich als das Lernen eines FSA aus den gesammelten execution traces
formuliert (analog zum Erlernen eines FSA in der Sprachtheorie).
Die Werte lokaler Felder sowie als Inspektoren (Inspector, Methoden mit
Rückgabewert und ohne Parameter) bezeichnete Methoden des Objekts werden verwendet, um die Zustände des FSA zu charakterisieren. Jeweils zu
Beginn und zum Ende eines Methodenaufrufs werden die Werte der Felder
sowie die Inspektoren festgehalten. Auf diese Weise können execution traces
relativ unkompliziert in Zustände und Transitionen, also einen FSA, umgewandelt werden. Abbildung 3.3 zeigt beispielhaft ein Objektverhaltensmodell
der Klasse Vector.
Der initiale Zustand wird mit start beschriftet und repräsentiert das Objekt direkt nach seiner Erzeugung. Zustandsänderungen ergeben sich bei diesen Modellen immer durch Methodenaufrufe. Die Entscheidung, Zustände der
Klasse Vector lediglich durch den einen Inspektor isEmpty() zu repräsentieren, setzt manuelles Eingreifen voraus. So besitzt die Klasse beispielsweise
noch ein lokales Feld size. Ein FSA, der diese Variable für die Zustandsbeschreibung verwendet, müsste für jeden Wert der Variable einen eigenen
Zustand definieren. Angenommen das Objekt hat 1000 Einträge gespeichert,
so hätte der FSA dementsprechend über 1000 Zustände, wodurch er nahezu
unbrauchbar würde. Dieser Sachverhalt wird gewöhnlich als state explosion
problem bezeichnet. Um dieses Problem zu adressieren, wurden sogenannte
1
Ernst-Denert-Preis
33
3.1. VERHALTENSBESCHREIBUNGEN
!remove()
add(),remove()
add()
start
isEmpty():true
isEmpty():false
remove(), clear(),
removeAll()
Abbildung 3.3: Ein Objektverhaltensmodell der Klasse Vector, neu gezeichnet aus [Dal10]
Abstraktionsfunktionen eingeführt, die die Werte eines Zustandes auf einen
anderen (abstrakteren) Zustand mit eingeschränkten Wertebereich abbilden.
Weitere Arbeiten Im Folgenden werden weitere Arbeiten, die ähnliche
Ergebnisse erzielen, sich aber im Vorgehen unterscheiden, kurz zusammengefasst.
• Extended Finite State Machines (EFSM) Der in [LMP08] von
Lorenzoli et al. vorgestellte Ansatz geht ähnlich vor wie die Arbeit
von Dallmeier. Es kommen wieder Tracing Methoden zum Einsatz, die
Methodenaufrufe, Parameter und Variablen zur Laufzeit protokollieren (interaction traces).Ein EFSM ist ein nicht-deterministischer Automat mit anonymen Zuständen, der Aufrufsequenzen von Methoden,
die mit zusätzlichen Daten annotiert sind, repräsentiert. Transitionen
stehen für einen Methodenaufruf, und können mit Parameter- und Variablenwerten sowie Prädikaten beschriftet sein. Zur vollständig automatischen Ableitung eines EFSM aus den Traces wurde der gk-tailInferenzalgorithmus als Erweiterung des k-tail-Algorithmus entwickelt.
Dazu leitet gk-tail Kriterien für das Zusammenfassen von Zuständen
ab. Um abstrakte Prädikate (Relationen aus einer Menge VariablenWert Paare) aus den Parameterwerten abzuleiten, kommt das Werkzeug Daikon [EPG+ 07], ein dynamischer Detektor für Invarianten, zum
Einsatz.
• Verhaltensmodell aus Kontextinformation Duarte [Dua07] stellt
einen Ansatz vor, der statische (Kontrollfluss) und dynamische Informationen (Trace) in Form von Kontextinformation zusammenführt.
34
KAPITEL 3. VERWANDTE ARBEITEN UND LITERATUR
Das Konzept des Kontext kombiniert einen Ausführungspunkt im Kontrollflussgraph und den Zustand, repräsentiert durch Werte der Variablen, des Systems. Kontexte sind verschachtelte Strukturen, die die
Extraktion von kausalen Zusammenhängen zwischen Aktionen im System unterstützen können. Die Modelle werden in Form eines Labelled
Transition Systems extrahiert.
• Interactive Grammar Inference Auch in [WBHS07] werden Traces zur Laufzeit generiert. Der Unterschied liegt in der interaktiven
Ableitung des Zustandsautomaten, unter Verwendung einer grammer
inference Technik. Interaktiv bedeutet, dass während der Konstruktion
Fragen für den Nutzer generiert werden, die als Systemtests interpretiert werden können.
• Markov Modelle Diese Art von Modellen geht davon aus, dass der
Zustandsautomat die Markov-Eigenschaft erfüllt. Kurz gesagt bedeutet das, dass jeder Folgezustand nur von Informationen des aktuellen
Zustandes abhängig ist. Dadurch kann der Automat mit wahrscheinlichkeitstheoretischen Informationen kombiniert werden. Ein Beispiel
hierfür ist [BRH04]. Hier werden die Transitionen mit Wahrscheinlichkeiten annotiert, die angeben, dass beispielsweise Zustand B mit der
Wahrscheinlichkeit x zum Zeitpunkt t+1 erreicht wird, wenn zum Zeitpunkt t der vorherige Zustand A war.
• Blackbox Ansatz Der Artikel [SMSS10] beschreibt eine andere Herangehensweise. Grundlegend verschieden ist die Annahme, dass von
einer Komponente nur das Interface bekannt ist, sie also als Blackbox vorliegt. Weiter wird davon ausgegangen, dass eine Komponente in verschiedenen Zuständen immer nur eine Untermenge ihrer Services unterstützt (Menge der aktiven Methoden). Diese Untermenge
charakterisiert den aktuellen Zustand der Komponente, da durch die
Blackbox-Annahme keine Variablen verfügbar sind. Durch bestimmte Methodenaufrufe verändert sich die Menge der aktiven Methoden.
Diese Aufrufe stellen die Beschriftung der Transitionen dar.
3.1.3
Diskussion im Kontext von CoolSoftware
Ein Ziel dieser Arbeit ist der Entwurf beziehungsweise die Auswahl eines Verhaltensmodells, um die Ressourcennutzung von Software zu charakterisieren.
Durch die Ausführung des Quellcodes wird eine bestimmte Nutzung unterschiedlicher Hardwareressourcen verursacht. Das Nutzungsmodell ist dem-
35
3.1. VERHALTENSBESCHREIBUNGEN
nach indirekt vom konkreten Quellcode und von spezifischen Hardwarekomponenten abhängig. Abbildung 3.4 verdeutlicht diesen Zusammenhang.
Ausführung
CCMCCMModell
Modell
RessourcenRessourcennutzungsmodell
nutzungsmodell
Quellcode
Quellcode
Hardware
Hardware
RessourcenRessourcenauslastung
auslastung
Abbildung 3.4: Problematik des Ressourcennutzungsmodells
Da der eigentliche Gegenstand der Modellierung die Ressourcennutzung
während der Ausführung ist, kann das Ressourcennutzungsmodell nicht (oder
nur mit unvertretbaren Aufwand) vor der Implementierung dem CCM-Modell
hinzugefügt werden. Das CCM-Modell muss demnach zunächst in Quellcode
umgesetzt und alle Funktionen implementiert werden, bevor das Ressourcennutzungsmodell erstellt werden kann, da die Beobachtungen während der
Ausführung erfolgen. Aus den Beobachtungen lässt sich schließlich ein Modell
ableiten und dem bestehenden CCM-Modell im Nachhinein hinzufügen.
Konkretisierung des Modells und der Modellbildung nach AMT
Eine besondere Bedeutung kommt dem Reduktionskriterium zu. Das Modell
sollte wirksam von der Komplexität des Quellcodes auf der einen Seite, und
der Komplexität der Hardwarekomponenten auf der anderen Seite, abstrahieren. Zur Beschreibung des gewünschten Ergebnisses werden die aus der
AMT bekannten Begriffen verwendet:
• Original: In diesem Fall ist das Original ein Phänomen, nämlich die
physische Ressourcenauslastung der Hardware, hervorgerufen durch die
Ausführung des Quellcodes einer CCM Komponente.
• Zweck: Das Modell soll eine Prognose über das Verhalten bezüglich
der Ressourcennutzung einer CCM Komponente während der Ausfüh-
36
KAPITEL 3. VERWANDTE ARBEITEN UND LITERATUR
rung erlauben. Das ermöglicht einen Verzicht auf Details (von Hardware und Software), die kaum oder gar keinen Einfluss auf die Nutzung
haben.
• Subjekt: Der Ersteller des Modells ist nicht zwangsweise der Entwickler der CCM Komponente. Diese Aufgabe kann auch von einem
Softwareingenieur übernommen werden, der für das Deployment oder
das Zusammenfügen der Komponenten verantwortlich ist.
Ein weiteres wichtiges Kriterium ist die Richtung der Modellbildung.
CCM Komponenten werden üblicherweise im Rahmen eines Forward Engineering Ansatzes entwickelt. Demnach führt der übliche Weg, im Sinne eines
Wasserfallmodells [Roy70], von hohen Abstraktionsstufen zur Implementierung. Dieses Vorgehen ist für die Bildung Ressourcennutzungsmodells nur
bedingt geeignet:
• Forward Engineering: Eine präskriptive Modellbildung lässt sich,
aufgrund der Abhängigkeit zu konkreter Hardware, nur schwer realisieren. Eine angemessene Vorhersage erfordert einen Simulationsansatz
[ZPK00], der wiederum detaillierte Modelle der Hardware voraussetzt.
Darüber hinaus müsste die komplette Systemumgebung (z.B. Betriebssystem) mit einbezogen werden. Dieser Ansatz erscheint für einzelne
wissenschaftliche Untersuchungen geeignet. In der Praxis ist er aber
eindeutig zu aufwändig.
• Reverse Engineering: Die deskriptive Modellbildung erlaubt eine
eher pragmatische Vorgehensweise. Den Ausgangspunkt bildet die Annahme, dass die Komponente bereits implementiert (Quell- oder Bytecode) vorliegt, und auf der Zielplattform eingesetzt ist. Durch Beobachtungen innerhalb ihrer Umgebung zur Ausführungszeit wird versucht,
ein Modell abzuleiten. Diese Vorgehen entspricht einer Systeminferenz.
Zusammenfassung
Die vorgestellten Arbeiten zur Modellextraktion leiten, aus zur Laufzeit gesammelten Informationen, ein Verhaltensmodell der Software ab. Während
das Vorgehen verwandt ist, liegt ein Unterschied in der Zielstellung vor:
Softwareausführungsmodelle sollen das Verhalten möglichst genau beschreiben, damit sie zum weiteren Erkenntnisgewinn über die Software verwendet
werden können. Diese Arbeit hingegen sucht nach einem möglichst einfachen
(abstrakten) Zusammenhang zwischen Quellcode, Hardware und Ressourcennutzung. Zweifellos hat der interne Aufbau (z.B. Kontrollfluss) der Software
3.2. SOFTWARE UND ENERGIEVERBRAUCH
37
Einfluss auf die Ressourcennutzung. Des Weiteren könnten wahrscheinlichkeitstheoretische Informationen helfen, die Modelle akkurater zu gestalten.
Dennoch: je mehr Informationen einfließen, umso komplexer wird das Modell, was der Zielstellung widerspricht. Abschnitt 5.1 erläutert genauer, was
in dieser Arbeit unter abstrakter Verhaltensbeschreibung verstanden wird.
.
3.2
Software und Energieverbrauch
„Smart power management is all about doing more with the resources we
have.“[Gar07]
Software hat entscheidenden Einfluss auf den Energieverbrauch eines Systems. Das Ziel dieses Abschnitts besteht darin, im Sinne einer State-of-theArt Analyse zu klären, wie dieser Einfluss aussehen kann. Das Thema ist
gleichermaßen komplex wie relevant. Schließlich gilt es zu zeigen, dass der
Energieverbrauch maßgeblich von der Art und Weise, wie Software mit den
direkten Energieverbrauchern, den verfügbaren Hardwarekomponenten, interagiert, abhängig ist.
Einleitend ein (naiver) Denkansatz: Ein moderner Prozessor besitzt derzeit mehrere 100 Millionen Transistoren. Selbstverständlich muss Energie zugeführt werden, um den Zustand dieser Transistoren zu verändern und damit
den Prozessor zum „rechnen“ zu bewegen. Je mehr Rechenoperationen von
einer Anwendung ausgehen, desto höher ist die Anzahl erforderlicher Schaltungen, und dadurch auch die benötigte Energiezufuhr. Wird eine berechnungsintensive Applikation (über einen längeren Zeitraum) ausgeführt, ist
dieser Effekt oft direkt in Form einer erhöhten Lüfterdrehzahl wahrnehmbar.
Kombiniert man diese Beobachtung mit dem Wissen, dass ein Prozessor 100%
Verlustleistung besitzt, d.h. die komplette aufgenommene Energie in Wärme
umgewandelt wird, so wird ein (erster) Zusammenhang zwischen Software
und Energieverbrauch deutlich.
Das Beispiel ist natürlich stark vereinfacht, trifft aber den Kern der Problematik: Es spielt eine Rolle, wie Hardwarekomponenten von Software verwendet werden. Um den Zusammenhang umfassender zu untersuchen, werden im Folgenden repräsentative Arbeiten vorgestellt, die sich dem Thema
Software und Energieverbrauch widmen.
3.2.1
Einführung
Die Art und Weise, wie Software Einfluss auf den Energieverbrauch nehmen
kann, ist mannigfaltig. Es sollte zunächst klar sein, dass Software nicht nur
38
KAPITEL 3. VERWANDTE ARBEITEN UND LITERATUR
Anwendungsprogramme, sondern auch das Betriebssystem umfasst. Nach Tanenbaum [Tan07] gibt es zwei grundsätzliche Ansätze, den Energieverbrauch
einzuschränken:
• Energieverwaltung durch das Betriebssystem: Da das Betriebssystem alle Geräte kontrolliert, muss es auch entscheiden, wann einzelne
Ressourcen den Wechsel in einen niedrigeren Energiezustand vollziehen. Energiezustände von Hardware werden später in Abschnitt 4.1.2
genauer erläutert.
• Eingeschränkter Betrieb: Diese Maßnahmen betreffen Anwendungsprogramme. Die Energieeinsparung ergibt sich aus einer Verringerung
der Geschwindigkeit, die mit Einschränkungen des Komforts für den
Benutzer einhergeht (vergleiche CoolSoftware Abschnitt 2.2.3). Als Beispiel wird von Tanenbaum die Arbeit von Flinn [FS99] angeführt.
Um Maßnahmen zur Steigerung der Energieeffizienz zu kategorisieren, ist
es hilfreich, Software in die bekannten Rollen Konsumenten (z.B. Anwendungen) und Erzeuger (Ressourcenmanager) einzuteilen [Sax10]. Aus dem
Studium der Literatur kann eine grobe Einteilung in Stufen, die aber keineswegs unabhängig von einander sind, erfolgen.
1. Die Software nimmt direkten Einfluss, so wie es das einleitende Beispiel
verdeutlichte. Ein effizienterer Algorithmus, der mit weniger Rechenoperationen auskommt, wird sich immer positiv auf den Energieverbrauch auswirken.
2. Die Software nimmt indirekt Einfluss, indem sie so entwickelt wird, dass
sie eine effiziente Ausnutzung der Energiezustände moderner Hardwarekomponenten erlaubt. Ein anschauliches Beispiel bietet das „race to
idle“ Prinzip [Gar07]. Demnach ist es energieeffizienter, den Prozessor
eine Menge Arbeit bei voller Geschwindigkeit verrichten zu lassen, um
anschließend möglichst lange im Zustand idle zu verweilen, als ihn bei
gedrosselter Geschwindigkeit ständig kleine Portionen Arbeit verrichten zu lassen.
3. Die Software fungiert als Ressourcenmanager, was (momentan) typischerweise die Rolle des Betriebssystem ist. Je komplexer die Infrastruktur ist, desto größer sind die Optimierungsmöglichkeiten hinsichtlich energieeffizienter Ressourcenverwaltung [Sax10].
3.2. SOFTWARE UND ENERGIEVERBRAUCH
3.2.2
39
State of the Art
Um den Energieverbrauch von Software zu bestimmen, muss es zunächst
möglich sein, selbigen in geeigneter Form zu ermitteln. Die Herausforderung
besteht unter Anderem darin, eine möglichst genaue Zuordnung zwischen
der Ausführung von Softwareartefakten und deren Verbrauch herzustellen.
Im Wesentlichen finden sich dazu zwei Verfahren:
• Messung: Messungen des Energieverbrauchs können beispielsweise mit
einem Digitalmultimeter (DMM) realisiert werden. Diesen Ansatz verfolgte auch Flinn [Fli01] bei der Entwicklung von PowerScope, das
ein statistisches Samplingverfahren verwendet. Dabei kommt jeweils
ein Rechner für das Profiling der Anwendung und einer für die Datensammlung zum Einsatz. Der Profilingrechner zeichnet periodisch
Daten über die aktuelle Anwendung auf (system monitor), während
der Datenrechner die Samplingdaten aus dem DMM speichert (energy
monitor). Das DMM ist zwischen die beiden Rechner geschaltet. und
dessen Zeitgeber gibt die Samplingraten vor. Durch eine Verbindung
des DMM (Triggereingang bzw Triggerausgang) mit dem Proflingrechner erfolgt die Synchronisation, indem es den Wert eines Inputpins
kippt, wodurch ein Interrupt ausgelöst wird, der den Systemmonitor
zur Datenaufzeichnung anstößt.
• Simulation: In [GSI+ 02] wurde ein auf Simulation basierender Ansatz,
genannt SoftWatt, entwickelt. Dabei werden alle Hardwarekomponenten sowie das Betriebssystem simuliert, um den Energieverbracuh
zu ermitteln. Das größte Problem bei derartigen Ansätzen ist das Aufsetzen der Infrastruktur. Da alle Hardwarekomponenten simulierbar
sein müssen, werden detaillierte und validierte Modelle benötigt. Derartige Ansätze werden auch häufig als complete system simulation bezeichnet. Die Simulationskosten steigen daher mit der Komplexität des
zu simulierenden Systems. Die Vorteile liegen in der Genauigkeit, und
dass der Simulator Daten zu Ereignissen liefern kann, die bei realer
Hardware nicht zugänglich sind.
Energieverbrauch Anwendungen
In [Fli01] untersuchte Flinn, wie die Entwurfsentscheidungen von Software den Energieverbrauch von Systemen beeinflussen. Es wurde gezeigt, wie
sich Anpassungen (Energy-aware Adpation) zur Laufzeit auf den Energieverbrauch auswirken. Für die Messungen kam das beschriebene PowerScope
zum Einsatz. Damit Anpassungen sinnvoll sind, müssen zwei Voraussetzung
erfüllt sein:
40
KAPITEL 3. VERWANDTE ARBEITEN UND LITERATUR
• Eine Änderung des Verhaltens der Anwendung muss zu Energieeinsparungen führen.
• Die Energieeinsparungen sollten vorhersagbar sein.
Im Zentrum der Untersuchungen stand der Zusammenhang zwischen qualitativ unterschiedlichen Daten und dem Energieverbrauch. Dazu wurden vier
Applikationen getestet. Eine davon war ein Videoplayer, der vier Videos unterschiedlicher Qualitäten (Größe und Höhe der verlustbehafteten Kompression bei der Framekodierung) über einen Netzwerkstream abspielt. Dabei
wurde nachgewiesen, dass sowohl die unterschiedlichen Qualitäten den Energieverbrauch signifikant beeinflussen, als auch dass eine Kombination mit
Energiesparstrategien der Hardware einen weiteren Gewinn bringt. Des Weiteren wurde damit gezeigt, dass eine höhere Qualität eine höhere Ressourcenauslastung zur Folge hat, wodurch wiederum der Energieverbrauch steigt. In
weiteren Arbeiten [FPS02] wurde noch der Zusammenhang zwischen Lokalität (remote oder lokale Ausführung von Softwareartefakten) und Energieverbrauch untersucht. Schließlich werden zur Vorhersage des Energieverhaltens
lineare mathematische Modelle verwendet, die auf Qualität und Größe der
Daten basieren.
Während bei Flinn Datenqualitäten im Vordergrund standen, untersuchten Fei et al. [FZJ08] gezielt die Auswirkungen von Veränderungen der Berechnungsqualitäten. Dazu wurde ebenfalls das Beispiel eines Videoplayers
verwendet, und verschiedene Parameter (Ditheringmethode, Framerate, Frameanzeigegröße) als QoS-Dimensionen definiert. Die Kombination unterschiedlicher QoS-Werte ergibt einen spezifischen QoS-Punkt, der mit Energiekosten
assoziiert ist. Auch hier konnte nachgewiesen werden, dass eine Verminderung
der Qualitäten zu signifikanten Energieeinsparungen führen kann. Zur Modellierung des Energieverbrauchs kommen Makromodelle zum Einsatz. Das
Ergebnis stellt ein Framework zur Steigerung der Energieeffizienz dar, das
die Nutzerwünsche und QoS-Parameter einbezieht.
Eine spezifischere Untersuchung stammt von Poess und Nambiar [PN08].
Sie entwickelten auf Grundlage vorhandener Ergebnisse des TPC-C Benchmark (ein Benchmark für OLTP Systeme) ein Energieverbrauchsmodell für
transaktionsorientierte Systeme. Das Modell ist relativ einfach zusammengestellt: für jede einzelne Komponente (CPU, Speicher, Festplatten, Serverchassis und Festplattengehäuse) wird der Energieverbrauch aus Spezifikationen (Peak) angenommen und für Gehäuse und Chassis ein prozentualer
Anteil, abhängig von den beherbergten Komponenten, sowie ein Overhead
angenommen. Um das Modell zu Testen wurden wieder Messungen am realen System (für das die Ergebnisse des Benchmark vorlagen) durchgeführt.
Bei der Auswertung wurde festgestellt, dass der Workload des TPC-C den
3.2. SOFTWARE UND ENERGIEVERBRAUCH
41
größten Einfluss auf den Energieverbrauch hat, da er hauptsächlich für die
Belastung der Komponenten verantwortlich ist.
Von Seo et al. [SMM08] wurde ein Framework zur Abschätzung des Energieverbrauchs, vorrangig verteilter und eingebetteter, auf Java-basierter Anwendungen entwickelt. Das Hauptziel besteht in der Ermöglichung fundierter
Entscheidungen, wann die Systemarchitektur angepasst werden sollte, um
den Energieverbrauch batteriebetriebener Geräte zu vermindern und damit
die Kerndienste einer Komponente länger anbieten zu können. Der Ansatz
bezieht das (verteilte) System als Ganzes mit ein, schätzt den Energieverbrauch aber auf Komponentenebene ab. Durch eine genaue Abschätzung
des Verbrauchs können passende Aktionen ausgeführt werden, um das System als Ganzes länger funktionstüchtig zu halten. Solche Aktionen umfassen
beispielsweise nicht benötigte oder entbehrliche Komponenten abzuschalten
(unload), die Umsiedlung energiehungriger Komponenten auf einen größeren
Host oder Komponenten,die häufig miteinander kommunizieren, auf denselben Host zu platzieren.
Die Energiekosten einer Komponente setzen sich aus Berechnungs- und Kommunikationskosten zusammen. Erstere umfassen zum Beispiel CPU-Nutzung,
Speicherzugriffe und IO, während Kommunikationskosten hauptsächlich durch
Netzwerkkomponenten verursacht werden. Die Berechnungskosten werden
auf Schnittstellenebene bestimmt, was im allgemeinen Fall Methodenaufrufen entspricht. Modelliert werden diese Kosten durch die Summierung der
Energiekosten für Bytecodes, native Methodenaufrufe und Monitore (diese
Begriffe werden in Abschnitt 4.1.4 erläutert). Für Kommunikationskosten
werden nur Verbindungen über das UDP (User Datagram Protocol) in Form
der übertragenen Datenmenge betrachtet.
Um an die erforderlichen Daten zu kommen wurde eine spezifische JVM (Kaffe 1.1.5) instrumentiert, die für Lern- und Forschungsfragen (clean room)
konzipiert wurde. Allgemein ist für den Ansatz immer erforderlich, dass dieselbe JVM, dasselbe Betriebssystem und dieselbe Hardware verwendet wird.
In einer folgenden Arbeit wurde von Seo et al.[SEP+ 09] auch noch der Einfluss unterschiedlicher Architekturstile auf den Energieverbrauch untersucht.
Energieverbrauch Betriebssysteme
Arbeiten zur Ermittlung des Energieverbrauchs von Betriebssystemen stammen ebenfalls meist aus dem Embedded Bereich, wobei die von Tan et
al. [TKTJ02] und Li et al. [LJ03] entwickelten Methoden als die relevantesten
eingeschätzt werden. Die Vorgehensweise der beiden fast zeitgleich entstandenen Arbeiten ist sehr ähnlich: der Energieverbrauch grundlegender Betriebssystemroutinen wird unter Verwendung von Testprogrammen bemessen und
42
KAPITEL 3. VERWANDTE ARBEITEN UND LITERATUR
anschließend daraus ein mathematisches Modell abgeleitet. Der Hauptunterschied liegt im angedachten Verwendungszeitpunkt der gewonnenen Modelle.
Im Folgenden werden beide Ansätze kurz vorgestellt und anschließend bewertet.
Bei Li et al. [TKTJ02] liegt der primäre Fokus auf der Entwicklung eines
Energiemodells, das zur Laufzeit zur Energieverwaltung verwendet werden
kann. Moderne Betriebssysteme werden als komplexe Software gesehen, die
in der Lage sind, eine heterogene Ressourcenlandschaft zu verwalten. Die
Komplexität dieser Software wird dabei hinter einer relativ simplen Schnittstelle versteckt, den Kernel-Diensten (operating system kernel services). Daher werden, zur Modellierung des Energieverhaltens des komplexen Betriebssystems, die auf Ebene dieser Schnittstelle zur Verfügung gestellten Dienste
betrachtet. Das Energieverhalten des Gesamtsystems ist schließlich, vereinfacht ausgedrückt, durch die Summe aller Dienste charakterisiert.
Für die Untersuchungen kam ein Simulator zum Einsatz, der in der Lage ist,
die komplette Systemumgebung zu simulieren. Dabei standen für die Hardwarekomponenten validierte Energiemodelle zur Verfügung und es wurde ein
spezifisches Betriebssystem simuliert. Auf dieser Testplattform wurden anschließend verschiedene Testprogramme mit unterschiedlichen Charakteristiken gefahren. Dazu erfolgte eine Einteilung der Benchmarks in zwei Gruppen.
Die eine zur Datenerhebung für die Modelle (Profiling), die andere um die
Exaktheit der Modelle zu validieren. Für jede Betriebssystemroutine wurde
die Energie sowie die Standardabweichung mit verschiedenen Benchmarks
gemessen.
Die Untersuchungen am Simulator zeigten, dass unterschiedliche Routinen
auch unterschiedlich viel Energie verbrauchen. Daher ist es nicht ausreichend,
den Energieverbrauch aller zu messen und einen Durchschnitt zu errechnen.
Vielmehr sollte jede einzeln bemessen werden.Des Weiteren zeigte sich bei
den Testprogrammen, dass unterschiedliche Benchmarks verschiedene Routinen unterschiedlich häufig aufrufen. Dadurch kommen auf Anwendungsebene
verschiedene Energieverteilungsmuster zustande. Ein weiteres wichtiges Ergebnis ist die Korrelation zwischen Performance und Energieverbrauch. Als
Maß für die Performance wurde die Menge an Instruktionen, die pro Taktzyklus (instructions per cycle (IPC)) vom Prozessor ausgeführt werden können,
herangezogen. Die Korrelation zwischen Energieverbrauch und IPC erweist
sich als linear und kann damit als Parameter (Korrelationsnummer) in die
Gleichungen des Energiemodells einfließen. Laut Evaluation waren die Modelle in der Lage, tatsächlichen Energieverbrauch mit einer durchschnittlichen
Abweichung von 6% berechnen können.
Die von Tan et al. [LJ03] entwickelten Makromodelle sollen dem Entwick-
3.2. SOFTWARE UND ENERGIEVERBRAUCH
43
ler bei Entwurfsentscheidungen unterstützen. Durch frühes Feedback über
den Energieverbrauch von Betriebssystemdiensten soll es ermöglicht werden,
dass der Entwickler die Auswirkungen der Architekturwahl auf das spätere
Energieverhalten bereits beim Entwurf beurteilen kann.
Das Betriebssystem wird als ein sehr großer Prozess gesehen, der mit den Prozessen der einzelnen Anwendungen interagiert. Anwendungen können über
Systemaufrufe (syscalls mit dem großen Prozess in Verbindung treten. Nach
einem solchen Funktionsaufruf springt die Kontrolle zum großen Prozess
über. Sobald die Funktion zurückkehrt, geht die Kontrolle wieder an die Anwendung über. Daher wird das Betriebssystem auch als multi-entry multi-exit
pair (MEME-P) bezeichnet. Das Betriebssystem wird als große Box aufgefasst, die von verschiedenen Pfaden, die sich im Inneren teilweise überlappen,
durchzogen ist. Im Gegensatz zu Li et al. wird zusätzlich zwischen impliziten
und expliziten Systemaufrufen unterschieden. Erstere umfassen im Wesentlichen Interrupts, Signalbehandlung sowie Scheduling, die bei der Ausführung
des Betriebssystems auftreten. Letztere sind die explizit ausgeführten Systemaufrufe.
Tan et al. gehen davon aus, dass der Quellcode des Betriebssystems zur Verfügung steht. Daraus soll der Entwickler zunächst eine Auswahl an expliziten
Systemaufrufen treffen. Die Auswahl erfolgt nach der erwarteten Aufrufhäufigkeit beziehungsweise nach der Relevanz für einen bestimmten Architekturstil. Daraufhin sollte für jeden ausgewählten Pfad ein Testprogramm erstellt werden. Die Testprogramme der einzelnen Funktionen werden dann,
wie zuvor bei Li et al., im Simulator ausgeführt. Dadurch lässt sich der Energieverbrauch pro Funktionsaufruf bestimmen. Da Systemaufrufe überladen
sein können, wird anschließend der durchschnittliche Energieverbrauch für jeden Pfad ermittelt. Ebenso wird der Energieverbrauch der impliziten Aufrufe
durch Simulation spezifischer Testprogramme bestimmt. Das entsprechende
Makromodell zum Energieverhalten des Betriebssystems wird anschließend
durch Verschmelzung von impliziten und expliziten Charakteristiken abgeleitet. Derselbe Simulator, der zur Bestimmung des Energieverbrauchs der
einzelnen Pfade verwendet wurde, wird verwendet um den Energieverbrauch
von Anwendungen zu bestimmen. Die Ergebnisse wurden abschließend mit
den durch das Makromodell vorhergesagten Verglichen. Dabei wurde eine
Genauigkeit von etwa 5% festgestellt.
3.2.3
Zusammenfassung
In diesen Abschnitt wurden unterschiedliche Arbeiten vorgestellt, die sich mit
dem Zusammenhang zwischen Software und Energieverbrauch beschäftigten.
Die Verfahren zur Ermittlung des Energieverbrauchs lassen sich dabei in Si-
44
KAPITEL 3. VERWANDTE ARBEITEN UND LITERATUR
mulation und Messung unterscheiden. In den Untersuchungen konnte nachgewiesen werden, dass die Ressourcennutzung von Software einen großen Einfluss auf den Gesamtverbrauch an Energie nehmen kann. Weitere Arbeiten,
wie beispielsweise [Gar07, Sax10], setzen den Fokus darauf, welches Verhalten bei Software zur Verschwendung von Energie des Systems führen kann.
In beiden Arbeiten wird beispielsweise Polling (das periodische Abfragen von
Zuständen) als negatives Kriterium genannt, da es die Hardwarekomponenten davon abhält, in einen niedrigeren Energiezustand zu wechseln.
Die vorgestellten Arbeiten zur Ermittlung der Energiecharakteristik von
Betriebssystem basierten beide auf einen Simulationsansatz. Zusammenfassen lieferten sie folgende Erkenntnisse.
• Es ist prinzipiell möglich, den Energieverbrauch auf Ebene einzelner
Systemdienste hinreichend genau zu bestimmen.
• Ein Makromodell kann dazu verwendet werden, das Energieverhalten
eines komplexen Systems mit ausreichender Genauigkeit zu beschreiben.
• Um den genauen Energieverbrauch zu bestimmen, sind spezialisierte
Simulatoren für spezifische Hardware und Betriebssysteme nötig.
• Es besteht eine nachgewiesene Korrelation zwischen Performance und
Energieverbrauch. Diese kann ausgenutzt werden, um die Modelle akkurater zu gestalten.
• Verschiedene Anwendungen verwenden oft nur eine Untermenge aller
zur Verfügung stehenden Systemaufrufe, und zudem einige davon recht
häufig. Dadurch entsteht eine individuelle Energiecharakteristik.
Kapitel 4
Java und die
Ressourcennutzung
An dieser Stelle erscheint eine Wiederholung des Leitmotivs der Arbeit angebracht: Der Energieverbrauch eines Systems ist, in nicht vernachlässigbaren
Maße davon abhängig, ob und in welchem Umfang Hardwarekomponenten von
Software benutzt werden. Der Nachweis der Richtigkeit dieser These erfolgte
in Abschnitt 3.2 unter Berufung auf Untersuchungen, die im Rahmen anderer Arbeiten durchgeführt wurden. Im Sinne der Überschrift dieses Kapitels
können wir den Zusammenhang auch kürzer formulieren: Das Verhalten von
Softwareartefakten bezüglich ihrer Ressourcennutzung ist von entscheidender
Bedeutung für den Energieverbrauch.
Das letztendliche Ziel ist ein generatives System, das auf Grundlage von
Beobachtungen am realen System, die Beziehung zwischen Ein- und Ausgangsdaten nachbildet. In unserem speziellen Fall sind die Ausgangsdaten
die Daten über die Ressourcennutzung, für die ein Zusammenhang mit Codeartefakten hergestellt werden muss. Um zu sinnvollen Daten über das reale
System zu kommen, muss dessen Funktionsweise zunächst verstanden werden. Die Grundlagen dafür schafft dieses Kapitel.
Das Kapitel besteht aus zwei Teilen: Beschreibung der Systemumgebung
in Abschnitt 4.1 und Möglichkeiten der Datenerhebung in Abschnitt 4.2. Im
ersten Teil werden wichtige Eigenschaften eine Computersystems, von Hardware bis zum Java-Quellcode, behandelt, die bei der Datenerhebung von Bedeutung sind. Um sich ein Bild von der tatsächlichen Ressourcennutzung zu
machen, muss der Entwickler Kenntnisse dieser Eigenschaften besitzen. Diese
Arbeit stellt die Methodiken und Werkzeuge zur Verfügung, um an die benötigten Daten zu kommen und sie zu verarbeiten, kann aber nicht das Denken
für den Entwickler übernehmen. Zieht er beispielsweise ein zeitgebundene
Metrik zur Bestimmung der CPU-Nutzung heran, sollte auch der aktuel45
46
KAPITEL 4. JAVA UND DIE RESSOURCENNUTZUNG
le Performancezustand des Prozessors beachtet werden. Der zweite Teil beschäftigt sich zunächst mit grundlegenden Möglichkeiten der Datenerhebung.
Sie bilden die Grundlage für die JRUPI und geben ebenfalls dem Entwickler
wichtige Hinweise.
Insgesamt betrachtet ist das Kapitel ist eine Kombination aus Hintergrundinformationen, Literaturanalyse, Definitionen und Best Practices, die
aus zahlreichen Quellen zusammengetragen wurden. Es formt einen zentralen
Teil, da alle Konzept- und Entwurfsentscheidungen der vorliegenden Arbeiten
diesem Kapitel aufbauen.
4.1
Beschreibung der Systemumgebung
Die Analyse der Abhängigkeiten zwischen Java-Codeartefakten und Ressourcennutzung ist ein wichtiger Bestandteil dieser Arbeit. Dabei wird unter dem
Begriff Codeartefakt folgendes verstanden:
• Was? Komponenten, Klassen bzw Objekte und Methoden
• Wie? vorliegend in Quell- oder Bytecode
• Wann? statisch oder dynamisch (zur Laufzeit)
Zwischen den beschriebenen Codeartefakten in Java, und einer physischen
Ressourcennutzung liegen mehrere Ebenen, die auf verschiedene Weise Einfluss nehmen. Allgemein wird im Folgenden von der Systemumgebung gesprochen. Neben den Einflüssen, die sie ausübt, wird sie auch zur Erhebung
der erforderlichen Daten benötigt. Daher ist eine gewisse Kenntnis über die
Funktionsweise der einzelnen Ebenen notwendig. Die Beschreibung wichtiger
Eigenschaften und Bestandteile ist die Aufgabe dieses Abschnitts.
4.1.1
Übersicht
Eine Besonderheit bei der Analyse von Java-Codeartefakten stellt zweifelsohne die Tatsache dar, dass der Quellcode nicht direkt in ein plattformspezifisches Format kompiliert wird. Stattdessen wird der Quelltext vom Compiler
in die kompakte und plattformunabhängige Bytecodedarstellung übersetzt,
die anschließend von einer geeigneten Laufzeitumgebung ausgeführt werden
kann. Im Gegensatz zum Bytecode ist die Laufzeitumgebung, Java Virtual
Machine (JVM) genannt, plattformspezifisch [MH10]. Dank der Offenlegung der JVM-Spezifikation [LY99] existieren auch verschiedene Implementierungen der JVM von Drittanbietern. Da die Ausführung des Bytecodes
47
4.1. BESCHREIBUNG DER SYSTEMUMGEBUNG
komplett in der Verantwortung der JVM liegt, stellt sie eine weitere Abstraktionsebene dar. In Abbildung 4.1 sind die Ebenen der Systemumgebung, wie
sie hier betrachtet werden, schematisch dargestellt.
Java Anwendung
Java Virtual Machine
Betriebssystem
Hardware
Ziel
Aufbau
Abbildung 4.1: Schematische Darstellung des betrachteten Gesamtsystems
Aus der Grafik sind folgende Schichten zu erkennen: Hardware, Betriebssystem, Java Virtual Machine (JVM) und schließlich die eigentliche Java-Anwendnung; die Betrachtung weiterer Abstraktionsebenen, wie Midddleware-Systeme und Virtualisierung, stehen nicht im Fokus
dieser Arbeit. In der gegebenen Reihenfolge werden die einzelnen Schichten auch in den folgenden Unterabschnitten behandelt (rechts: Aufbau). Die
folgenden Ausführungen stellen weder eine Einführung noch eine detaillierte Behandlung der jeweiligen Themen dar. Vielmehr nehmen sie sich einige
wichtige Aspekte heraus und erläutern diese beispielhaft.
Es sei darauf hingewiesen, dass jede dieser Ebenen sehr variabel ist. Die
Herausforderung wird daher sein, ein möglichst ausgewogenes Verhältnis zwischen plattformspezifisch und allgemeingültig zu erreichen. Dabei sind beispielsweise folgende Fragen von Bedeutung: Sollten spezielle Funktionen spezifischer Hardwarekomponenten zur Datenerhebung verwendet werden? Können Daten unabhängig vom Betriebssystem gesammelt werden? Kann die
JVM einfach durch eine andere Implementierung ausgetauscht werden?
48
4.1.2
KAPITEL 4. JAVA UND DIE RESSOURCENNUTZUNG
Die Hardware
Das Ziel dieses Unterabschnitts ist es, einige wichtige Eigenschaften von
Hardwarekomponenten bezüglich ihres Energieverhaltens aufzuzeigen.Für die
Ziele der vorliegenden Arbeit sind die Details der Hardwarekomponenten
weitgehend irrelevant. Es werden auch keine spezifischen Hardwareeigenschaften, wie Hardware Performance Counter (HPC) [UGV08], für die Datenerhebung genutzt, da sie keine einheitliche Schnittstelle besitzen und von
Komponente zu Komponente unterschiedlich sind. Würden sie durch den
Nachteil des Fehlens einer einheitlichen Schnittstelle für einen allgemeingültigen Ansatz nicht disqualifiziert, so wären sie eine effiziente Datenquelle.
Viele Hardwarekomponente bieten inzwischen diverse Betriebsmodi, auch
als Energie- oder Performancezustände bezeichnet, in denen sie unterschiedlich viel Energie benötigen, indem sie beispielsweise ihre Performance einschränken. Sie sind ein erster Schritt in Richtung einer allgemeineren Forderung: mehr Proportionalität zwischen Ressourcennutzung und Energieverbrauch. Die Auswirkungen einer verbesserten Proportionalität sind in Abbildung 4.2 dargestellt.
Abbildung 4.2: Energieeffizienz bei schlechter Proportionalität (links) und
besserer Proportionalität (rechts), aus [BH07]
Die Grafik stellt gegenüber, wie sich die Energieeffizienz bei schlechter
Proportionalität und besserer Proportionalität in Bezug auf die Ressourcennutzung verändert. Es ist deutlich erkennbar, dass sich eine Verbesserung der
Proportionalität direkt in einer Steigerung der Energieeffizienz niederschlägt.
Ist keine Proportionalität gegeben, so ergibt sich die größte Energieeffizienz
bei einer Auslastung von über 80%. Das liegt aber weit über den typischen
Betriebsregionen (10-50% bei Servern) der meisten Rechnersysteme [BH07].
4.1. BESCHREIBUNG DER SYSTEMUMGEBUNG
49
CPU (Central Processing Unit)
Die CPUs oder Prozessoren stellen die Schlüsselressourcen eines jeden Computersystems dar [MMG06]. Dabei reicht das Spektrum von einzelnen Einkernprozessoren (wie beispielsweise in Netbooks oder Smartphones) über einzelne Mehrkernprozessoren (wie beispielsweise in Notebook- und Desktopsystemen) bis zu großen Serversystemen mit einer hohen Anzahl von Mehrkernprozessoren. Zudem können Prozessoren in Verteilten Systemen auch gemeinsam benutzt werden. Allerdings benötigt dennoch jeder Teilnehmer eine eigene, lokale CPU. Ein Beispiel hierfür wären Thin-Clients, die laut [HJMM09]
eine Renaissance im Zusammenhang mit Green-IT erleben könnten.
Das ACPI (Advanced Configuration and Power Interface) stellt bezüglich Energiemanagement den wichtigsten Industriestandard dar. Nach dessen
Spezifikation in [HPIM+ 10] liegt die Hoheit über die Energieverwaltung beim
Betriebssystem, während es Aufgabe der Hardwarehersteller ist, die benötigiten Funktionen und Schnittstellen für ihre Komponenten zur Verfügung
zu stellen. ACPI selbst kann als zusätzliche Abstraktionsschicht zwischen
Betriebssytem und darunter liegender Firm- und Hardware gesehen werden.
Allgemein wird als Grundlage für das Energiemanagement ein Zustandsmodell verwendet. Die höchste Abstraktionebene stellen die globalen Energiezustände des Systems (Global System Power States) dar. Der jeweils aktuelle Zustand wird unter Anderem durch die aktuellen Zustände der einzelnen
Komponenten bestimmt, die jeweils durch separate Zustandsautomaten beschrieben werden. Dabei kommt der Zustandsbeschreibung der CPU eine
besondere Rolle (als Schlüsselressource) zu.
In Abbildung 4.3 ist das Zustandsmodell eines Prozessors nach ACPI
Definition dargestellt. Der umgebende Zustand G0 gibt an, dass das System am Arbeiten (Working) ist. Innerhalb dieses globalen Zustands kann
sich die CPU in verschiedenen Energiezuständen (C0,C1,C2,C3,...,Cn)1 befinden, wobei C0 einem aktiven Zustand, in dem der Prozessor Instruktionen
ausführt, entspricht. Die verbleibenden Energiezustände (C1,...,Cn) entsprechen verschiedenen Sleep-Modi (es werden keine Instruktionen zu dieser Zeit
ausgeführt), in denen die CPU weniger Enerige verbraucht und geringere
Abwärme erzeugt.
Der aktive Zustand C0 weist darüber hinaus einige interessante Eigenschaften auf. So sind innerhalb dieses Energiezustands zusätzlich unterschiedliche Performancezustände Px definiert. Der Prozessor führt zwar Instruktionen aus, aber (unter Umständen) mit gedrosselter (Throttling=Drosselung)
Performance. Dadurch ergibt sich wiederum ein Einsparpotenzial sowohl bei
Energieverbrauch als auch bei erzeugter Abwärme, während die CPU den1
C0 bis C3 sind fest vorgegeben, können aber beliebig bis Cn erweitert werden
50
KAPITEL 4. JAVA UND DIE RESSOURCENNUTZUNG
G0
G0 =
= Working
Working
Performance
Performance
State
State Px
Px
C1
C1
C0
C0
C2
C2
Throttling
Throttling
C3
C3
Abbildung 4.3: Processor Power States, vereinfacht aus [HPIM+ 10]
noch Arbeit verrichtet. Die unterschiedlichen Performancezustände werden
durch eine dynamischen Anpassung von Taktrate und Versorgungsspannung
(Dynamic Voltage and Frequency Scaling (DVFS) [TWM+ 08] erreicht und
müssen softwareseitig verwaltet werden (wie beispielsweise mit [?]).
Für das Betriebssystem Linux existiert für Intel-Prozessoren das Werkzeug PowerTOP [Gar07], um Anwendungen zu identifizieren, die den Prozessor davon abhalten, in einen kleineren Energie- beziehungsweise Performancezustand zu wechseln. Von Interesse ist an dieser Stelle die Anzeige der
Energie- und Performancezustände selbst. Abbildung 4.4 zeigt die Ausgabe
des von PowerTOP für eines der in dieser Arbeit verwendeten Testsysteme.
Im vorliegenden Fall wird auf der linken Seite angezeigt, wie lange die
CPU innerhalb der vergangenen fünf Minuten anteilig in den verschiedenen Energiezusänden (C0,C1,C2, und C6) verweilte. Auf der rechten Seite
werden die Performancezustände, geordnet nach Taktfrequenz, mit prozentualen Anteilen dargestellt. Zum Zeitpunkt der Aufnahme befand sich das
System im Batteriebetrieb, wodurch auch eine aggressivere Drosselungsstrategie (Throttling) zur Anwendung kam. Dieser Umstand ermöglicht PowerTOP außerdem eine Abschätzung (basierend auf den gesammelten Daten der
vergangenen fünf Minuten) der restlichen Batteriebetriebszeit.
4.1. BESCHREIBUNG DER SYSTEMUMGEBUNG
51
Abbildung 4.4: Ausgabe von Powertop für Intel(R) Core(TM)2 Duo CPU
(P7350), Rechner im Batteriebetrieb
Weitere Geräte
Neben der CPU führt die Verwendung weiterer Hardwarekomponenten zu
einem höheren Energieverbrauch des Systems. In der ACPI Spezifikation
[HPIM+ 10] werden diese Komponenten einheitlich als Geräte (Devices) behandelt. Für Geräte werden, ähnlich wie zuvor bei den Prozessoren, ebenfalls
Energie- (D0,D1,...,Dn) und Performancezustände definiert. Energiezustände
können auch bei Geräten durch Zustandsautomaten dargestellt werden (vergleiche Verhaltensschablone und Zustandsautomaten für Hardwareressourcen
im CCM, Unterabschnitt 2.2.1). Für Performancezustände werden folgende
Beispiele genannt, wobei die Abstufungen bei den einzelnen Geräten jeweils
mit der Höhe des Energieverbrauch korrelieren:
• Festplatten stellen Stufen mit maximalen Durchsatz bereit.
• Flüssigkristallbildschirme (LCD (Liquid Chrystal Display)) können mehrere Helligkeitsstufen zur Verfügung stellen.
• Grafikkarten können Performance zwischen 2D und 3D Darstellungsmodus skalieren. Zudem besitzen Computersysteme häufig einen separaten Mikroprozessor, die GPU (graphics processing unit), die auf
Berechnungen der grafischen Ausgabe spezialisiert ist. Viele Modelle
bieten Möglichkeiten zur Anpassung der Taktfrequenz, ähnlich dem
Hauptprozessor.
• Audiosysteme mit mehreren Lautstärkestufen.
• Arbeitsspeicher-Controller, die mehrere Stufen an Speicherdurchsatz
durch Drosselung (Throttle) der maximalen Bandbreite bereitstellt.
52
KAPITEL 4. JAVA UND DIE RESSOURCENNUTZUNG
All diese Maßnahmen zielen darauf ab, mehr Proportionalität zwischen
der Ressourcennutzung und dem Energieverbrauch zu erreichen. Allerdings
sind sie (noch) nicht bei allen Geräten optimal umgesetzt. Aufgrund der breiten Akzeptanz der ACPI-Spezifikation und der zunehmenden Notwendigkeit
des Energiesparens kann aber zukünftig von weiteren Verbesserungen ausgegangen werden. Sind auf Hardwareseite die Voraussetzungen durch verschiedene Energiezustände erfüllt, und bietet die Software Möglichkeiten zur
effektiven Steuerung, so steigt die Energieeffizienz.
In diesem Zusammenhang nimmt der Hauptprozessor bisher eine Vorreiterrolle ein. Da er typischerweise unter den Komponenten den höchsten
Energieverbrauch aufwies, war er auch Hauptgegenstand zahlreicher Optimierungen. Dank Techniken wie DVFS und breiter Unterstützung durch Betriebssysteme nimmt er heute nicht mehr zwangsweise diese Rolle ein [BH07].
4.1.3
Das Betriebssystem
Das Betriebssystem ist eine Softwareschicht, die vorhandene Geräte verwaltet und Benutzerprogrammen eine einfache Schnittstelle zur Hardware bietet. Nach [Tan07] kann das Betriebssystem aus zwei Blickwinkeln betrachtet
werden:
• Top-down-Sicht Das Betriebssystem stellt den Benutzer eine geeignete Schnittstelle zur Hardware zur Verfügung. In dieser Sichtweise ist es
eine erweiterte beziehungsweise virtuelle Maschine, die leichter zu programmieren ist als die darunterliegende Hardware. Das Betriebssystem
versteckt also die Komplexität der Hardware vor dem Programmierer.
• Bottom-up-Sicht Bei dieser Sichtweise ist es die Aufgabe des Betriebssystems, eine geordnete und kontrollierte Zuteilung (Allokation)
der Hardwareressourcen an die darum konkurrierenden Prozesse zu gewährleisten. Daher ist das Betriebssystem ein Ressourcenmanager.
Bereits während der ersten Recherchen zu dieser Arbeit wurde klar, dass
zur Analyse der Ressourcennutzung einer Anwendung kein Weg am Betriebssystem vorbei führt. Das liegt in der einzigartigen Aufgabe, die es erfüllt,
begründet. In gewisser Weise kann es als Vermittler zwischen Hardware und
restlicher Software gesehen werden. Im Allgemeinen besitzen Benutzerprogramme keine Informationen über spezifische Hardwarekomponenten, sondern verwenden die zentralen Abstraktionen, die ihnen das Betriebssystem
zur Verfügung stellt. Um konkrete Informationen zur Ressourcennutzung zu
erhalten ist es daher notwendig, sich an das Betriebssystem zu wenden. Abbildung 4.5 visualisiert den Zusammenhang.
53
4.1. BESCHREIBUNG DER SYSTEMUMGEBUNG
BenutzerBenutzerprogramme
programme
BetriebsBetriebssystem
system
HardwareHardwarekomponenten
komponenten
Abbildung 4.5: Das Betriebssystem zwischen Hardware und Software
Bei der Analyse der Ressourcennutzung einer Anwendung sind gerade
die Daten zu den konkreten Hardwarekomponenten interessant, vor deren
Komplexität das Betriebssystem abstrahiert.
Die Wahl des Betriebssystems
Da die benötigten Informationen typischerweise nur vom Betriebssystem bezogen werden können, ist für die spätere Proof-of-Concept Implementierung
eine Festlegung auf ein spezifisches System unumgänglich. Für diese Arbeit
wurde, aus den folgenden Gründen, eine strategische Entscheidung für Linux
getroffen:
• Linux besitzt mit SystemTap [BPBS10] eine Umsetzung des dynamischen Tracing, das für die Implementierung von besonderer Bedeutung
ist. Es ist ein Pandon zu Konkurrenz und Vorreiter DTrace [MMG06],
welches für die Systeme Solaris, BSD und OSX verfügbar ist. Eine
vergleichbare Alternative für Windows ist nicht bekannt.
• Unter den verbleibenden Systemen (dynamisches Tracing ist ein Ausschlusskriterium) genießt Linux zweifelsfrei die größte Verbreitung und
Vielseitigkeit. So ist es in verschiedensten Varianten auf Geräten von
Smartphones, Laptops, Servern bis hin zu Supercomputern zu Hause.
Daher könnte es auch, im größeren Zusammenhang mit CoolSoftware,
als einheitliche Grundlage für die Optimierung ganzer Infrastrukturen
dienen.
• Für Linux steht der komplette Quellcode des Systems zur Verfügung.
Das sorgt für eine Transparenz der inneren Funktionsweise, die vor allem in Kombination mit dynamischen Tracing-Verfahren sehr mächtig
ist. Ist das System hingegen eine „proprietäre Blackbox“ (Formulierung
von Bryan Cantrill), schränkt das die verfügbaren Möglichkeiten erheblich ein.
SystemTap wird kontinuierlich weiterentwickelt und hat mittlerweile den
Sprung in die Repositories aller größeren Distributionen geschafft, so dass es
54
KAPITEL 4. JAVA UND DIE RESSOURCENNUTZUNG
meist relativ unkompliziert über die Paketverwaltung nachgerüstet werden
kann. Als Entwicklungssystem kam in der vorliegenden Arbeit überwiegend
Fedora 13 (mit Kernel 2.6.34.7-66.fc13.i686) zum Einsatz.
Der Rest dieses Unterabschnitts widmet sich einigen wichtigen Eigenschaften von Betriebssystemen und ist dabei weitgehend auf Linux ausgerichtet. Die erläuterten Informationen stammen vorrangig aus [Tan07], [Lov10],
[BC00] und [ET10], sowie allgemeinen Beobachtungen in der Praxis bei der
Implementierung.
Grundlegende Konzepte
Es liegt in der Natur von Betriebssystemen, dass sie sehr komplex sind. Daher
wäre es ein sinnloses Unterfangen, die genaue Funktionsweise in einem kurzen Abschnitt erläutern zu wollen. Stattdessen werden grundlegende Konzepte
anhand einiger Schlagworte erläutert. Sie wurden gezielt ausgewählt, weil sie
im Zusammenhang mit der Datenerhebung beziehungsweise der Funktionsweise von SystemTap von Bedeutung sind.
Kern- und Benutzermodus: Die Unterscheidung zwischen Kern- und
Benutzermodus ist hardwareseitig durch den Prozessor umgesetzt und dient
dem Schutz des Betriebssystems. Alle Benutzerprogramme werden gewöhnlich im Benutzermodus ausgeführt und können nur über eine wohldefinierte
Schnittstelle (Systemaufrufe) Kerneldienste in Anspruch nehmen. Wird solch
ein Dienst aufgerufen, geht die Kontrolle an das Betriebssystem über, das den
Dienst im Auftrag des Benutzerprogramms im Kernmodus ausführt, und das
Ergebnis anschließend zurück liefert. Nachdem die Anfrage abgearbeitet wurde, schaltet der Kernel die Hardware wieder in den Benutzermodus zurück.
Monolithischer Kernel: Unix-ähnliche Betriebssysteme wie Linux verfügen typischerweise über einen monolithischen Kernel. Das bedeutet, dass der
komplette Kern als ein einziger, großer Prozess implementiert ist, der beim
Systemstart in den Arbeitsspeicher geladen wird. Als Konsequenz verwendet
der komplette Kern einen einzelnen Adressraum, wodurch die Kommunikation innerhalb des Kerns trivial ist (alles kann alles aufrufen). Zudem können
alle Kernelfunktionen im Kernmodus ausgeführt werden, was häufige Kontextwechsel erspart.
Den Gegenentwurf stellt der sogenannte Microkernel (z.B. Windows XP,
Vista, 7) dar, bei dem die Funktionalitäten des Kerns in mehrere getrennte Prozesse aufgespalten wird, was wiederum den Kommunikationsaufwand
(message passing) erhöht. Jeder Prozess besitzt dementsprechend einen eigenen Adressraum und kann unter Umständen im nicht-privilegierten Benut-
4.1. BESCHREIBUNG DER SYSTEMUMGEBUNG
55
zermodus ausgeführt werden. Durch die Modularisierung vereinfacht sich die
Struktur und der Kernel selbst kann relativ klein gehalten werden.
Kernelmodule: Monolithische Kernel sind nicht unumstritten, da dieses
Design wenig Struktur besitzt. Linux schafft durch sogenannte Kernelmodule
Abhilfe, die den Vorteil von Microkerneln auch im monolithischen Linuxkernel erlauben, ohne dass die Performance darunter leitet. Ein Kernelmodul ist
einen Objektdatei, die eine Menge Funktionen beinhaltet, und dynamisch zur
Laufzeit in den Kernel geladen werden kann. Kernelmodule arbeiten gewöhnlich auf den oberen Schichten des Kerns und werden beispielsweise für die
Implementierung von Gerätetreibern oder Dateisystemen genutzt. Im Gegensatz zu Microkerneln werden Module nicht als spezieller Prozess ausgeführt,
sondern laufen vollständig im privilegierten Kernmodus und können daher
(ohne zusätzlichen Kommunikationsaufwand) wie statisch-verlinkte Funktionen ausgeführt werden. Dieser Ansatz versucht also das Beste aus beiden
Welten zu vereinen. Das Konzept der dynamisch ladbaren Kernelmodule ist
vor allem im Zusammenhang mit der Funktionsweise dynamischer Tracingverfahren von Bedeutung.
Prozesse: Prozesse sind fundamentale Abstraktion innerhalb eines jeden
Betriebssystems, da sie von einem laufenden Programm abstrahieren. Häufig
wird auch von der Instanz eines Programms in der Ausführung oder dem
Ausführungskontext eines Programms gesprochen. Eine effektive Prozessverwaltung ist eine der wichtigsten Aufgaben, die ein Betriebssystem erfüllt.
Aus Sicht eines Prozesses hat er die alleinige Herrschaft über alle Ressourcen, das heißt er kann alle Ressourcen verwenden, mit denen der Linuxkernel
umgehen kann. Zur Verwaltung aller Prozesse verwendet Linux eine zentrale Datenstruktur (task_struct), die auch als Prozessdeskriptor bezeichnet
wird. Jeder Prozess wird durch diese Struktur repräsentiert, die alle wichtigen
Informationen über die Prozessstruktur beinhaltet: Prozess-ID (pid), Adressraum, aktueller Prozesszustand, geöffnete Dateien sowie Informationen zu
den Threads, um nur einige Beispiele zu nennen. Die möglichen Zustände, in
denen sich ein Prozess befinden kann, sind in Abbildung 4.6 dargestellt.
Jeder individuelle Prozess besitzt einen eigenen Zustand, der sich während
seiner Ausführung ändert. Möglich Zustände sind [ET10]:
• Task_Running Hier handelt es sich genau genommen um zwei Zustände, deren Übergänge zwei weitere wichtige Eigenschaften verdeutlichen. Der Prozess ist entweder bereit (links) zur Ausführung (liegt
auf der run queue) oder wird gerade von einem Prozessor ausgeführt
(laufend; rechts).
56
KAPITEL 4. JAVA UND DIE RESSOURCENNUTZUNG
existierende
existierendeTask
Task
ruft
ruftfork()
fork()auf
auf
und
erstellt
und erstellt
neuen
neuenProzess
Prozess
Task
Taskist
istbeendet
beendet
Task spaltet sich
Task von Scheduler
ausgewählt
TASK_RUNNING
TASK_RUNNING
TASK_RUNNING
TASK_RUNNING
(bereit)
(bereit)
(laufend)
(laufend)
Task mit höherer
Priorität
Ereignis taucht auf und Task
wird aufgeweckt und zurück
auf die Warteschlange gesetzt
TASK_INTERRUPTIBLE
TASK_INTERRUPTIBLE
oder
oder
TASK_UNINTERRUPTIBLE
TASK_UNINTERRUPTIBLE
(wartend)
(wartend)
Task schläft und wartet auf
gewisses Ereignis
Abbildung 4.6: Prozesszustände in Linux , nach [Lov10]
• Task_Interruptible In diesem Zustand ist der Prozess suspendiert
und wartet auf ein bestimmtes Ereignis. Ein typisches Beispiel hierfür
ist das Warten auf einen Interrupt durch die Tastatur (Empfang eines
Signals).
• Task_Uninterruptible Dieser Zustand ist ähnlich dem Task_Interruptible Zustand, jedoch reagiert der Prozess nicht auf Signale.
Ein typisches Beispiel hierfür ist das Warten auf Festplatten E/A.
Die Übergänge zwischen den Zuständen bereit und laufend führen uns
zu einer weiteren Kernkomponente des Betriebssystems: dem (CPU) Scheduler. Dessen Aufgabe besteht in der Koordination zwischen den rechnenden Ressourcen (Prozessoren) und den zu berechnenden Tasks (Prozesse und
Threads). Es liegt also in seiner Verantwortung, wie die auszuführenden Prozesse auf die Prozessoren verteilt werden. Der aktuelle Algorithmus zur Zuteilung eines Prozesses, den Linux verwendet, besitzt eine Komplexität von
O(1). Das heißt, er verhält sich konstant, egal wie viele Prozesse verwaltet
4.1. BESCHREIBUNG DER SYSTEMUMGEBUNG
57
werden müssen [ET10]. Hat der Scheduler den nächsten Prozess für eine CPU
bestimmt, so wechselt dessen Zustand von bereit zu laufend. In umgekehrter
Richtung ist es aber auch möglich, z.B. wenn ein Prozess höherer Priorität
wartet, einen laufenden Prozess vom Prozessor zurück auf die Warteschlange
zu legen. Dieses Vorgehen wird als Präemption bezeichnet und ist selbst dann
möglich, wenn Prozesse im privilegierten Kernmodus arbeiten (häufig auch
als präemptiver Kernel bezeichnet).
Präemptive Betriebssysteme verwenden für das Scheduling oft einen Zeitscheibenansatz. Dabei bekommt der laufende Prozess eine Zeitscheibe zugewiesen und das Scheduling zum nächsten wartenden Prozess wird nach
Ablauf der Zeitspanne erzwungen. Traditionell wurde dafür eine feste Dauer
(z.B. 10ms) angenommen, was zur Folge hatte, dass hundert mal pro Sekunde
ein Interrupt zur Durchsetzung des Scheduling feuerte (fixed tick). Ab Version
2.6.21 des Linux Kernels arbeitet der Scheduler nicht mehr statisch, sondern
überwacht eine Liste von Tasks die darauf warten, in den Zustand laufend
zu wechseln. Befindet sich das System im Zustand idle, so hält es Ausschau
nach dem nächsten erwarteten Ablauf eines Timers (einer Anwendung) und
programmiert den Timer-Interrupt, zu diesem Zeitpunkt zu feuern (tickless
kernel) [Gar07]. Das hat vor allem Auswirkungen in Bezug auf die Energieverwaltung: Da die CPU nicht periodisch Interrupts bearbeiten muss, kann
das System (vor allem die CPU selbst) länger in niedrigen Engergiezuständen
verweilen, wenn es tatsächlich nichts zu tun gibt.
Threads: Ein Thread (Ausführungsfaden) ist eine Ausführungseinheit innerhalb eines Prozesses und läuft parallel zu anderen Threads, die zum selben
Prozess gehören. Threads unterscheiden sich hauptsächlich in dem Punkt von
Prozessen, dass sie untereinander dieselben Ressourcen teilen und auf demselben Adressraum arbeiten. In Linux sind Threads auf eine spezielle Weise
implementiert: aufgrund der weitgehenden Ähnlichkeit zu Prozessen werden
sie auch wie jeder Standardprozess umgesetzt. Sie besitzen, im Gegensatz zu
anderen Betriebssystemen wie Solaris oder Windows, weder eine eigene Scheduling-Semantik noch spezielle Datenstrukturen. Oft werden Threads auch
als Light Weight Process (LWP) bezeichnet und haben, in Sachen Performance, den Vorteil bei der Erstellung, dass Ressourcen nicht kopiert werden
müssen. Durch Threads wird ein quasi-paralleler Ablauf bei einem Prozessor
beziehungsweise echte Parallelität bei mehreren Prozessoren ermöglicht. Alle Ausführungen zum Scheduling von Prozessen besitzen (bei Linux) daher
auch Geltung für Threads.
58
KAPITEL 4. JAVA UND DIE RESSOURCENNUTZUNG
4.1.4
Die Java Virtual Machine (JVM)
„Java is a lovely multi-purpose hammer, but not every problem is a nail!“
[MH10]
Laut einer aktuellen Umfrage2 ist Java die verbreitetste Programmiersprache bei IT-Dienstleistern. Einer der Hauptgründe für den Erfolg ist in der
Plattformunabhängigkeit von Java-Anwendungen zu sehen, die durch zwei
Konzepte ermöglicht wird:
• Der Quellcode wird zunächst in eine andere Repräsentation übersetzt:
Den kompakten und plattformunabhängigen Bytecode.
• Der Bytecode wird anschließend von einer plattformspezifischen JVM
augeführt. Damit wird das Portabilitätsproblem in die JVM ausgelagert. Die Anwendung selbst kann daher auf jeder Plattform, für die
eine geeignete JVM existiert, ausgeführt werden.
Da die JVM die komplette Ausführung überwacht, steuert und für den finalen
Übergang zum nativen Code zuständig ist, kann die Bytecoderepräsentation
eines Programms ohne erneute Kompilierung auf verschiedensten Architekturen ausgeführt werden. In der vorliegenden Arbeit wird die JVM als zweite
wichtige Abstraktionsebene betrachtet und soll daher im Folgenden erläutert
werden. Für detailliertere Erklärungen sei das Buch [MH10], das alle wichtigen Konzepte abdeckt, und auch als Grundlage für diesen Unterabschnitt
dient, empfohlen. Neben Details zur JRockit Implementierung, einer der
wichtigsten JVMs im Serverbereich, deckt es auch allgemeine Eigenschaften
von JVMs sehr gut ab. Eine weitere wichtige Informationsquelle ist selbstverständlich die JVM Spezifikation [LY99].
Bytecodeformat
Bytecode klingt zunächst nach low-level, ist aber lediglich eine andere Repräsentationsform, die man sich als serialisierten Quellcode vorstellen kann
[MH10]. Er ist eine kompakte und portable Form einer strukturierten Programmbeschreibung. In Listing 4.1 und Listing 4.2 sind der Quell- und der
Bytecode einer Methode gegenübergestellt. Die Bytecode-Repräsentation einer Methode bildet der sogenannte Frame, der aus zwei Teilen besteht: Zum
Einen beinhaltet er eine Menge indizierter Speicherplätze (Slots) für lokale
Varibalen, zum Anderen den Operandenstack. Letzterer besteht aus Werten,
2
Quelle: www.heise.de, Titel: Umfrage: Java ist verbreitetste Programmiersprache bei
IT-Dienstleistern, 25.02.2011
4.1. BESCHREIBUNG DER SYSTEMUMGEBUNG
59
die als Operanden für diverse Bytecode-Instruktionen dienen und nach dem
LIFO (last in first out) Prinzip abgerufen werden. Die JVM arbeitet also
wie ein Kellerautomat. Bytecode-Instruktionen dienen entweder dem Transfer zwischen lokalen Variablen und Operandenstack (ILOAD,ISTORE) oder
arbeiten auf dem Operandenstack (IADD, IRETURN).
74
75
76
77
78
79
1
/*
*
* anderer Code
*
*/
80
81
82
83
84
2
3
4
5
6
7
public int add ( int x , int y ) {
int z = x + y ;
return z ;
}
Listing 4.1: Quellcode
8
9
10
11
public add ( II ) I
L0
LINENUMBER 82 L0
ILOAD 1
ILOAD 2
IADD
ISTORE 3
L1
LINENUMBER 83 L1
ILOAD 3
IRETURN
Listing 4.2: Bytecode
Die beiden Parameter der Methode add werden zunächst in die Slot 1
und Slot 2 als lokale Variablen gespeichert. In Zeile 4 und Zeile 5 werden die
beiden lokalen Variablen x und y auf den Operandenstack geladen ILOAD,
anschließend addiert (IADD - Operation kombiniert die Operanden und legt
das Ergebnis zurück auf den Stack) und das Ergebnis in die lokale Variable
z (Slot 3) geschrieben (ISTORE). Dieser Vorgang entspricht dem Statement
in Zeile 82 des Quellcodes. Zur Umsetzung der Rückgabeanweisung in Zeile
83 wird zunächst die Variable z auf den Stack geladen (Zeile 10) und anschließend zurückgegeben (Zeile 11). Die Instruktion IRETURN nimmt dabei
die Variable z vom Stack und terminiert die Ausführung der Methode.
Jede Bytecode-Instruktion besteht aus einem Opcode, einer festen Anzahl
Argumenten zur Festlegung des Instruktionsverhaltens, und einer optionalen
Menge von Operanden variabler Länge. Ein Opcode wird durch eine mnemonisches Symbol (wie IADD) identifiziert und besitzt den Wert eines Bytes
(daher existieren maximal 256 Opcodes). Das verdeutlicht die Kompaktheit
des Bytecodeformats, da pro Instruktion typischerweise lediglich zwischen
ein und drei Bytes benötigt werden [MH10].
Der Bytecode zu einer Java-Klasse kann mit dem Konsolenwerkzeug javap,
das Bestandteil des JDK (Java Development Kit) ist, in Kombination mit
dem Parameter -c ausgegeben werden. Einen komfortableren Vergleich ermöglicht beispielsweise die Bytecode View für Eclipse, die auch für das Beispiel in Listing 4.2 verwendet wurde.
60
KAPITEL 4. JAVA UND DIE RESSOURCENNUTZUNG
Arbeitsweisen von JVMs
Die beschriebene Bytecode-Repräsentation ist plattformunabhängig und kann
daher von der physischen CPU nicht ausgeführt werden. Es liegt in der Verantwortung der JVM den Bytecode in nativen Code umzuwandeln, der spezifisch für eine bestimmte Hardwarearchitektur (z.B. x86 Assembler oder Maschinencode) ist. Zur Umsetzung bieten sich prinzipiell zwei Möglichkeiten
an [MH10]:
• Interpretation In der Spezifikation [LY99] ist die JVM komplett als
Zustandsautomat beschrieben, der den gesamten Ausführungsstatus eines Java-Programms emuliert. Der Bytecode wird also vollständig interpretiert, wodurch eine Übersetzung in nativen Code überflüssig wird.
• Just-In-Time Compilation (JIT) Mit JIT wird der Vorgang bezeichnet, Codeartefakte (typischerweise Methoden) in nativen Code zu
übersetzen, kurz bevor sie ausgeführt werden. Die JVM kompiliert den
auszuführenden Bytecode in nativen Code für die jeweilige Plattform
und ruft ihn anschließend auf.
Frühe Implementierungen der JVM waren meist reine Bytecode-Interpreter.
Das machte die Portierung der JVM auf eine andere Plattform relativ simpel,
da der Code-Generator der JVM lediglich neu kompiliert werden musste. Der
große Nachteil bestand in der bescheidenen Performance, die der Interpretation geschuldet ist, und noch heute der Programmiersprache Java nachgesagt
wird. Dabei hat sich die Situation entscheidend geändert, da immer ausgefeiltere Codegenerierungsstrategien entwickelt wurden:
• statische Kompilierung Der komplette Bytecode wird direkt in nativen Code übersetzt. Diese Vorgehensweise bringt eine große Performancesteigerung, führt aber zum Verlust fast aller anderen Vorteile,
die Java bietet: Plattformunabhängigkeit, automatisches Speichermanagement und die dynamischen Eigenschaften (wie Codeersetzung zur
Laufzeit) sind nicht mehr gegeben.
• vollständige JIT-Kompilierung Diesen Weg hat die JRockit Implementierung der JVM eingeschlagen: jede angetroffene Methode wird
direkt mit dem JIT-Verfahren kompiliert. Damit wird, im Gegensatz
zur statischen Kompilierung, die Kompilierzeit zum Laufzeitfaktor. Der
größte Nachteil dieser Methode liegt in der langsamen Codegenerierung,
die daher der Hauptgegenstand von Optimierungen ist. Dazu werden
die Methoden bei der Ausführung bewertet und in die Kategorien hot,
4.1. BESCHREIBUNG DER SYSTEMUMGEBUNG
61
für häufig verwendete Methoden mit hohem Einfluss auf die Performance, und cold, für selten verwendete Methoden mit geringem Einfluss
auf die Performance, eingeteilt. Zu Beginn der Ausführung kommt für
alle Methoden eine schnelle, aber dafür wenig optimierte, Kompilierung
(quick and dirty) zur Anwendung, um herauszufinden, welche Methoden hot beziehungsweise cold sind. Die Bewertungen der Methoden
werden im Laufe der Ausführung immer weiter verfeinert. Ab einem
gewissen Schwellenwert wird die Qualität der Kompilierung (die Implementierung kennt des JIT-Compiler kennt verschiedene Qualitätsstufen) gesteigert und dadurch native Code mit höherer Optimierung
erzeugt. Da eine erneute Kompilierung zunächst zu einer Erhöhung
der Gesamtzeit führt, ist es wichtig, nur bei Methoden zu optimieren,
die den Aufwand durch häufige Aufrufe mit kürzerer Ausführungszeit
rechtfertigen.
• Mixed mode interpretation In dieser Mischform zwischen Interpretation und JIT-Verfahren werden zu Beginn alle Methoden interpretiert. Sobald eine Methode als hot bewertet wird, erfolgt eine JITKompilierung in effizienteren nativen Code. Methoden die als cold eingestuft wurden, werden weiterhin interpretiert. Auch bei diesem Verfahren ist der Bewertungsalgorithmus von entscheidender Bedeutung.
Mit einer mixed mode interpretation arbeitet beispielsweise der HotSpot-Compiler, der in der Sun JVM zum Einsatz kommt.
In der Praxis sind heute nur die letzten beiden Verfahren von Bedeutung.
Ihr Vorgehen wird auch als adaptive Codegenerierung bezeichnet, da eine
schrittweise Optimierung zur Laufzeit stattfindet. Aufgrund der dynamischen
Natur von Java sind diese Ansätze mit einem erhöhten Verwaltungsaufwand
für den generierten Code (bookkeeping) verbunden, da nativer Code durch
Änderungen in der Umgebeung (adaptive Runtime) eventuell nicht mehr valide ist [MH10]. Die vorgestellten Verfahren sind für die Datenerhebung und
für das Aufsetzen von Benchmarks relevant und werden in Abschnitt 4.2
wieder aufgegriffen.
Weitere wichtige Eingenschaften
JVMs, die mit den beschriebenen Verfahren arbeiten, sind überaus komplexe
Softwaresysteme, deren Konzepte nicht in einen einzelnen Unterabschnitt zusammengefasst werden können. An dieser Stelle werden daher einige Aspekte
angesprochen, die für die vorliegende Arbeit relevant sind.
62
KAPITEL 4. JAVA UND DIE RESSOURCENNUTZUNG
Threads Java-Code wird in Threads ausgeführt. Auch hier besteht das
Ziel in einer quasi-parallelen Ausführung beziehungsweise echten Parallelität
mehrerer Aktivitäten. Intern besitzen Java-Threads folgenden Aufbau: jeder
Thread besitzt einen Ausführungsstack, der aus Frames besteht, die einen
Methodenaufruf repräsentieren. Bei jedem Aufruf einer Methode wird der
neue Frame auf den Stack gelegt (push) und bei Rückkehr wieder herunter genommen (pop), wodurch die Ausführung bei der aufrufenden Methode
fortgesetzt werden kann. Auch an dieser Stelle greift die JVM das Konzept
des Kellerautomaten auf. Zur Synchronisation zwischen Threads werden sogenannte Monitore eingesetzt.
Ein weiterer wichtiger Punkt ist die Implementierung der Threads. Früher wurden häufig eigene Thread-Implementierungen (Green Threads) in der
JVM verwendet, die innerhalb eines Threads im Betriebssystem ausgeführt
wurden. Da dieses Vorgehen aber diverse Nachteile hat (Verwaltungsaufwand, Komplexität, eigener Scheduler, Deadlocks bei nativen Code), und
die Semantik sehr ähnlich ist, werden heute üblicherweise BetriebssystemThreads zur Implementierung von java.lang.Thread verwendet. Zumindest alle Server-Implementierungen von JVMs (wie JRockit und HotSpot)
verwenden ein 1:1-Mapping [MH10].
Dieser Zusammenhang ist von fundamentaler Bedeutung: bei der Datenerhebung zur Ressourcennutzung auf Betriebssystemebene können Betriebssystem-Threads anstelle von Java-Threads beobachtet werden. Damit ist auch
der Scheduler des Betriebssystems dafür verantwortlich, die Ausführung der
Java-Threads zu steuern.
Deamon-Threads Die JVM benötigt während der Ausführung zur Erfüllung ihrer Aufgaben mehrere Threads, die im Hintergrund laufen (housekeeping). Diese werden, wie es von Unix-ähnlichen Betriebssystemen bekannt
ist, als Deamon bezeichnet. So existieren zum Beispiel separate Threads,
die sich um Signalverarbeitung (signal handling), Speicherverwaltung (garbage collection (GC)) oder JIT-Kompilierung kümmern. Wird die JVM auf
Prozessebene betrachtet, so führen diese Threads zu einer Erhöhung der Ressourcennutzung, die nicht direkt abhängig vom aktuellen Verhalten des gerade ausgeführten Code ist. Dieser Umstand sollte bei Messungen beachtet
werden.
Garbage Collection (GC) Die JVM besitzt ein automatisches Speichermanagement. Das bedeutet im Wesentlichen, dass sich die Technik der GC
automatisch um die Entsorgung nicht mehr benötigter Referenzen kümmert,
und damit der entsprechende Speicherplatz wieder freigegeben wird. Der Ent-
4.2. MÖGLICHKEITEN ZUR DATENERHEBUNG
63
wickler muss ich also nicht explizit um die Speicherverwaltung kümmern. In
Java ist es die Aufgabe des Garbage Collector, der als Thread im Hintergrund (Deamon) arbeitet, den Speicher freizugeben. Zu welchem Zeitpunkt
die GC aktiv wird, ist zum Einen vom Verhalten der Anwendung zur Laufzeit
abhängig, zum Anderen aber auch von spezifischen Einstellungen der JVM
[MH10].
Für die GC ist es teilweise erforderlich, die Ausführung der Anwendung
zu unterbrechen, wodurch gewisse Messergebnisse verfälscht werden können.
Zudem bringt die GC zusätzlichen Overhead, der eine Erhöhung der Ressourcennutzung des JVM-Prozesses zur Folge hat. Um das erste Problem zu
umgehen bietet es sich an, mehrere Messungen durchzuführen und beispielsweise eine Standardabweichung zu berechnen. Im Zusammenhang mit der
höheren Ressourcennutzung des JVM-Prozesses infolge der Ausführung von
Hintergrund-Threads bietet es sich an, die Ressourcennutzung auf ThreadEbene zu untersuchen.
Eignung von Java Wie das einleitende Zitat verdeutlichte, ist Java für
viele Domänen einsetzbar, aber nicht für alle geeignet. Bei Problemen, die
direkten Zugriff auf Hardwarekomponenten erfordern, sollte besser zu Alternativen wie C oder C++ gegriffen werden [MH10], da das Konzept der Verwendung einer JVM offensichtlich diesem Anwendungsgebiet widerspricht.
Konkret bedeutet das für die vorliegende Arbeit, dass Java kein geeignetes
Werkzeug zur Erhebung von Daten über die physische Ressourcennutzung
ist. Im größeren Zusammenhang mit CoolSoftware ist Java daher auch nicht
zur Implementierung der Ressourcenmanager geeignet.
4.2
Möglichkeiten zur Datenerhebung
Der erste Abschnitt dieses Kapitels widmete sich vorrangig der Beschreibung
der Systemumgebung, mit besonderen Fokus auf die beiden Abstraktionsebenen Betriebssystem (Linux) und JVM. In der Top-Down-Sicht nach Tanenbaum wird das Betriebssystem als eine virtuelle Maschine gesehen, die von
der Komplexität der Hardware abstrahiert. Analog dazu ist die JVM, und
hier steckt der Begriff direkt im Namen, eine virtuelle Maschine, die vom
Betriebssystem abstrahiert.
Während sich die Abstraktionsschichten für den Softwareentwickler im
Allgemeinen positiv in Form einer verminderten Komplexität auswirken, erschwert diese doppelte Schichtung die Datenerhebung: die Kluft zwischen
der physischen Ressourcennutzung, die auf niedriger Ebene stattfindet, und
64
KAPITEL 4. JAVA UND DIE RESSOURCENNUTZUNG
Codeartefakten in Java, die auf höchster Ebene in Quellcode vorliegen, ist
sehr breit - und ihre Überbrückung eine der großen Herausforderungen.
Dieser Abschnitt führt in einige grundlegende Verfahren ein, die zur Datenerhebung benötigt werden. Des Weiteren existieren viele Werkzeuge, sowohl
für Java als auch für Betriebssysteme, die interessante Daten zu Anwendungen liefern. Aus Platzgründen wird hier auf eine konkrete Vorstellung verzichtet und die Werkzeuge lediglich genannt. Für Werkzeuge im Umfeld von
Solaris und Linux bieten [MMG06, Sun08, ET10, BPBS10] eine gute Übersicht und Einführung.
4.2.1
Instrumentierung
Die Instrumentierung ist ein grundlegendes Verfahren, um Daten über eine Anwendung während der Laufzeit zu erheben, und wird typischerweise
bei der Programmanalyse eingesetzt. In vielen Arbeiten wird der Begriff so
selbstverständlich in einem gewissen Kontext verwendet, dass eine allgemeine
Festlegung meist ausbleibt. Daher wird in dieser Arbeit folgende Definition
eingeführt:
• Intrumentierung: Das Einbringen zusätzlicher Anweisungen in eine
Anwendung, die nicht ihrem eigentlichen Zweck dienen, sondern zusätzliche Informationen über die Anwendung zur Laufzeit liefern.
Dabei werden zur Umsetzung der Instrumentierung nicht zwangsweise
fortgeschrittene Techniken benötigt. Das simple Einfügen einer println Anweisung, um den Wert einer Variablen zur Laufzeit auszugeben, ist manchmal
vollkommen ausreichend [MH10]. In diesem Fall dient die Instrumentierung
dem Debugging. Weitere Anwendungsgebiete sind beispielsweise Profiling,
Logging, Monitoring oder Tracing 3 . Listing 4.3 zeigt ein Beispiel, wie durch
Instrumentierung die Ausführungszeit einer Java-Methode bestimmt wird.
public int ggT ( int x , int y ) {
long start = System . nanoTime () ;
int t ;
while ( y > 0) {
t = x % y;
x = y;
y = t;
}
long end = System . nanoTime () ;
Profiler . logMethod ( " ggT " , end - start ) ;
return x ;
}
1
2
3
4
5
6
7
8
9
10
11
12
Listing 4.3: Instrumentierung zur Ermittlung der Ausführungszeit (Java)
3
siehe auch Javadoc zur Schnittstelle java.lang.instrument
4.2. MÖGLICHKEITEN ZUR DATENERHEBUNG
65
Direkt nach Eintritt in die Methode ggT wird die aktuelle Systemzeit (in
Nanosekunden) in die Variable start gespeichert (Zeile 2). Nachdem alle
Statements der Methode abgearbeitet (außer return) sind, wird erneut die
aktuelle Zeit in die Variable end gespeichert (Zeile 9). Die Ausführungszeit
lässt sich nun einfach durch Subtraktion (end-start) ermitteln und kann
beispielsweise an eine weitere Komponente weitergegeben werden (Zeile 10).
Da die zusätzlichen Anweisungen in den Zeilen 2, 9 und 10 nichts zur eigentlichen Funktionalität (die Berechnung des ggT) beitragen, werden sie als
Instrumentationscode bezeichnet.
Prinzipiell gibt es verschiedene Möglichkeiten, den Instrumentationscode
in die Anwendung einzubringen. Im Fall von Java können die Folgenden
Techniken unterschieden werden:
• Instrumentierung des Quellcodes: Die simpelste Form der Instrumentierung ist die direkte Modifikation des Quellcodes, entweder durch
manuelles oder (semi-automatisches) Einfügen der Anweisungen. Ein
Beispiel zeigte bereits Listing 4.3.
• Instrumentierung bei Übersetzung: Durch die Verwendung eines
speziellen Compilers (oder spezifischer Compileroptionen) können zusätzliche Anweisungen bei der Übersetzung in den resultierenden Bytecode eingepflegt werden. So kann beispielsweise AspectJ, eine Umsetzung des Aspect-Oriented Programming (AOP) [KLM+ 97] Paradigmas
für Java, verwendet werden, um zusätzlichen Code an Methodeneingängen und Methodenausgängen zu positionieren. Genau genommen
verwendet AspectJ einen sogenannten Weaver, der die beschriebenen
Aspekte in den Quellcode „webt“, und die neue Version anschließend
vom Compiler übersetzen lässt.
• Instrumentierung des Bytecodes: Wie in Abschnitt 4.1.4 gezeigt
wurde, ist der Bytecode lediglich eine andere Repräsentationsform von
Quellcode. Die nachträgliche Manipulation dieser Repräsentation wird
als Bytecode Engineering bezeichnet, worauf sich mehrere Frameworks
wie extscJavaAssist[Chi04] BCEL [The06] und ASM[Bru07] spezialisiert haben.
• Instrumentierung zur Laufzeit: Die Instrumentierung zur Laufzeit
erfordert logischerweise die Manipulation des Bytecodes, da zu diesem
Zeitpunkt kein Quellcode mehr verfügbar ist. In Java erfolgt eine derartige Instrumentierung typischerweise, wenn eine Klasse vom Class
Loader geladen wird. Inzwischen existiert zu diesem Zweck eine eigene
Schnittstelle (java.lang.instrument) bei Java. Die Implementierung,
66
KAPITEL 4. JAVA UND DIE RESSOURCENNUTZUNG
in Form eines Agent, liegt in der Verantwortung des Entwicklers, so
dass zum Beispiel die erwähnten Frameworks zur Bytecodemanipulation verwendet werden können.
4.2.2
Sampling
Das Sampling ist eine weitere Form der Datenerhebung. Im Gegensatz zur
Instrumentation wird die Anwendung selbst dabei nicht verändert, sondern
Daten periodisch an einer anderen Stelle erhoben. In Unterabschnitt 3.2.2
wurde mit PowerScope [Fli01] bereits ein derartiges Verfahren vorgestellt.
Dabei wurde ein Digitalmultimeter (DMM) verwendet, das während der Programmausführung in einem gewissen Takt Energiedaten aufzeichnete. Der
Takt wird auch als Samplingrate bezeichnet. Aufgrund der periodischen Aufzeichnung der Daten ist Sampling ein statistisches Verfahren, das eine Annäherung an die Realität darstellt. Da auch beim Sampling in der Literatur
keine einheitliche Definition existiert, wird für diese Arbeit eine allgemeine
Definition gegeben:
• Sampling: Eine periodische Datenerhebung, vorgegeben durch eine Samplingrate, die außerhalb der unveränderten Anwendung stattfindet und
statistisch akkurate Daten liefert.
Bei der Instrumentation kann es vorkommen, abhängig von Menge und
Art der zusätzlich eingebrachten Anweisungen, dass sich das Verhalten der
Anwendung verändert (Heisenberg-Effekt), was beim Sampling nicht passieren kann. Dafür gelten Samplingverfahren aufgrund ihrer statistischen Natur
als ungenauer. Samplingverfahren können in zwei Kategorien eingeteilt werden [JF04]:
• Time Based Sampling (TBS): Dabei wird ein Taktgeber verwendet, der in einem festen Intervall (z.B. 100Hz) beispielsweise den Befehlszähler der CPU ausliest
• Event Based Sampling (EBS) Bei dieser Variante wird das Auftreten eines gewissen Ereignisses (z.B. Interrupt) als Auslöser der Datenaufzeichnung herangezogen.
Manche Verfahren greifen auf Betriebssystemebene (unter Linux) auf den
Timer-Interrupt des Scheduler (system tick) als Taktgeber zurück. In früheren Versionen des Scheduler (fixed tick) entsprach das Vorgehen eher einem
TBS, während es sich mit Einführung des (tickless kernel) eher dem EBS
zuordnen lässt.
4.2. MÖGLICHKEITEN ZUR DATENERHEBUNG
67
Eine sehr beliebte Datenquelle für das Sampling sind Hardware Performance Counter (HPC) [UGV08], die meist in einer bestimmten Frequenz
ausgelesen werden. Das größte Problem bei der Verwendung von HPC ist,
dass keine einheitliche Schnittstelle existiert und sie von jeder Hardwarekomponente unterschiedlich (in Anzahl und Art) umgesetzt werden. Daher ist
ihre Verwendung nur für spezielle Konfigurationen geeignet. Zwar existieren
Bibliotheken, die das Auslesen der Werte für spezielle Hardwarekomponenten
erlauben [TW04], sie sind aber nicht immer verfügbar und müssten für jede Hardwarekonfiguration neu zusammengestellt werden. Würden sie durch
diese Nachteile nicht disqualifiziert, so wären sie eine optimale Datenquelle
zur Ermittlung der Ressourcennutzung.
4.2.3
Profiling
Auch für Profiling ist es schwierig, eine allgemeingültige Definition zu finden.
Je nach Anwendungsgebiet und Ziel werden die Aufgaben dieser Tätigkeit
verschieden beschrieben. Daher wird erneut der Versuch unternommen, eine
generische Definition für diese Arbeit zu geben:
• Profiling Eine Tätigkeit, bei der Werkzeuge (Profiler) zum Einsatz
kommen, die zur Datenerhebung auf die Verfahren Instrumentierung,
Sampling oder eine Kombination aus beiden zurückgreifen, um zur Laufzeit gewisse Aspekte einer Anwendung zu untersuchen.
Aufbauend auf den vorgestellten Möglichkeiten zur Datenerhebung unterscheidet man bei Profilern im Allgemeinen zwischen zwei Profilingtechniken:
• invasiv Ein Profiler gilt als invasiv, wenn er zur Datenerhebung auf
Verfahren der Instrumentation zurückgreift.
• nicht-invasiv Ein Profiler gilt als invasiv, wenn er Samplingverfahren
zur Datenerhebung verwendet.
Am häufigsten ist der Begriff Profiling im Zusammenhang mit Performance anzutreffen. So kann ein Profiler als Werkzeug zur Analyse von Performanceund Stabilitätsproblemen gesehen werden [MN07]. In der Tat ist das Gebiet
der Performanceanalyse eng verwandt mit der Ressourcennutzung. Daher
wird für diese Arbeit eine allgemeinere Defintion angenommen, die zwar auch
die Performance umfasst, sich aber nicht auf sie beschränkt. Das Ziel besteht
darin, durch Analyse ein Profil der Ressoucennutzung einer Anwendung zu
erstellen. Daher ist Profiling das Mittel der Wahl, und das Werkzeug wird
als Profiler bezeichnet.
68
KAPITEL 4. JAVA UND DIE RESSOURCENNUTZUNG
Java-Profiler Die Performance von Anwendungen ist eine der wichtigsten nicht-funktionalen Anforderungen. Daher existieren für Java zahlreiche
Werkzeuge (Profiler) zur Analyse von Performance- und Stabilitätsproblemen. Sie untersuchen das Laufzeitverhalten, Nebenläufigkeit und Speicherverhalten der GC [MN07]. Bekannte Repräsentanten sind der Netbeans Profiler4 , Eclipse TPTP5 , JProfiler6 , JXInsight7 oder das JRockit Mission Control
Center [MH10].
• Das Problem: Soweit bekannt ist, arbeiten alle Profiler mit den Möglichkeiten, die die JVM zur Beobachtung einer Anwendung bietet. So
wird häufig die JVMTI Schnittstelle verwendet, um eine Bytecodeinstrumentierung zur Laufzeit durchzuführen und beispielsweise Ausführungszeiten zu bestimmen. Die Profiler erheben die Daten also innerhalb der JVM, was zum Zwecke der Performanceanalyse auch ausreichend ist. Das hat aber zur Konsequenz, dass diese Werkzeuge gar nicht
die Möglichkeit besitzen, eine physische Ressourcennutzung zu bestimmen. Daher sind Speicher und CPU typischerweise das einzige, was
an Ressourcennutzung erinnert. Doch auch hier ist Vorsicht geboten:
Speicher bezieht sich auf die interne Speicherverwaltung der JVM und
nicht auf den physischen Arbeitsspeicher. Auch CPU Informationen
müssen kritisch betrachtet werden. So verwendet das Werkzeug hprof
(eine Proof-of-Concept Implementierung von Sun) zur Bestimmung des
CPU Nutzungsprofils ein zeitliches Profil, das auf Zeitmessungen der
Methoden beruht. Die Methode wird aber innerhalb eines Java-Threads
ausgeführt, der wiederum 1:1 auf einen Betriebssystem-Thread gemapped ist. Mit einem präemptiven Scheduler ist die Methode daher nicht
immer ihre komplette Ausführungszeit auf der CPU, so dass diese Methodik nur innerhalb der JVM Sinn ergibt, aber wenig über die tatsächliche physische Auslastung der CPU aussagt.
Aus diesen Gründen ist es nötig, den Ressourcenverbrauch außerhalb der
JVM zu bestimmen. Genauer gesagt, sollte die Ressourcennutzung des JVM
Prozesses und seiner Threads analysiert werden.
Verhältnis zu Diagnose und Monitoring In der Praxis werden neben
Profilern häufig auch Diagnose- und Monitoring-Tools unterschieden [MN07].
4
http://netbeans.org/features/java/profiler.html
http://www.eclipse.org/tptp/
6
http://www.ej-technologies.com/products/jprofiler/overview.html
7
http://www.jinspired.com/products/jxinsight/
5
4.2. MÖGLICHKEITEN ZUR DATENERHEBUNG
69
• Zur Diagnose kommen Performance- und Lasttest-Tools zum Einsatz,
um festzustellen, ob eine Anwendung Anforderungen an Antwortzeitverhalten, Durchsatz, parallele Nutzer und Stabilität gerecht wird. Die
Anwendung wird von außen, dass heißt im Rahmen eine BlackboxTests, untersucht. Der Hauptunterschied besteht darin, dass weniger
Details und dafür mehr Systemmetriken mit Einfließen .
• Monitoring hingegen entspricht einer Überwachung des Produktivsystems zur Laufzeit. Java Monitoring Tools überwachen dazu spezifische
Java Komponenten und integrieren sich häufig in vorhandene Monitoring Infrastrukturen. Im Allgemeinen wird Monitoring über einen
längeren Zeitraum (oder ständig) zur Überwachung des Betriebs und
Fehleridentifikation eingesetzt.
Bezüglich der Überwachung anderer Systemressourcen entsprechen Monitoring und Diagnose eher dem Ziel dieser Arbeit. Allerdings passen ihre
Aufgaben nicht mit der geforderten Verbindung zum Quellcode zusammen.
Daher, und weil die Konzepte ähnlich sind, wird im Folgenden immer von
Profiling gesprochen.
Weitere Werkzeuge Für Betriebssysteme existiert eine Vielzahl unterschiedlicher Monitoring-Tools, um die Performance des Systems zu überwachen. Eine gute Übersicht bieten [MMG06] für Solaris und [ET10] für Linux.
In dieser Arbeit sind lediglich Letztere von Interesse. Da beide Systeme ihren
Ursprung in Unix haben, sind die Werkzeuge meist unter gleichen Namen,
aber in verschiedenen Implementierungen vorhanden.
Bei Linux stützen sich derartige Werkzeuge hauptsächlich auf das virtuelle
proc Dateisystem, dessen Aufgabe nicht die Datenspeicherung ist. Stattdessen bietet es eine zentrale Schnittstelle zum Linux-Kernel, um ihn beobachten und verändern zu können. Der Name Dateisystem stammt daher, dass
aktuelle Zustände für den Administrator beziehungsweise für Werkzeuge in
Form von Dateien erscheinen, die ganz normal ausgelesen werden können.
Werkzeuge wie vmstat oder top [ET10] lesen spezifische Dateien, die von
Interesse für die Aufgabe des Werkzeugs sind, ein. Anschließend werden die
Informationen gefiltert oder neu berechnet und für eine übersichtliche Ausgabe aufbereitet. Das Einlesen und Weiterverarbeiten der Informationen ist
jedoch zeitaufwändig und dementsprechend teuer. Daher eigenen sich diese
Werkzeuge besonders, um sich einen schnellen Überblick zu verschaffen oder
Langzeitinformationen zu sammeln. [Lev03]
70
KAPITEL 4. JAVA UND DIE RESSOURCENNUTZUNG
4.2.4
Benchmarking
Benchmarking ist eine fundamentale Methode zur Messung der Performance einer Anwendung. Da Performance eine überaus wichtige Eigenschaft ist,
wird in [MH10] vorgeschlagen, dass Benchmarking während der Entwicklung, parallel zu Regressionstests, stattfinden sollte. Also immer dann, wenn
größere Änderungen im Code vorgenommen wurden, soll die Erfüllung der
Performanceanforderungen durch Performance Regression Testing validiert
werden. Der Aufbau einer Infrastruktur für das Benchmarking dient also der
Qualitätssicherung.
Um die Performance von Codeartefakten überhaupt messen zu können,
müssen sie zunächst zur Ausführung gebracht werden. Im Kontext von Benchmarking wird die Komponente, die den betreffenden Code aufruft, als Treiber
bezeichnet. Das kann im einfachsten Fall eine main Methode sein, die die zu
untersuchenden Methoden mit Eingabedaten aufruft. Es ist die Aufgabe des
Treibers, einen Workload in den Benchmark zu injizieren. Optimal sind zu
diesem Zweck Treiber, die extern vorliegen, und somit leicht auch auf anderen Rechnern verwendet werde können. Bei der Erstellung eines Treibers ist
darauf zu achten, dass die Erstellung der Testdaten außerhalb der Messungen
geschieht.
Benchmarking wird an dieser Stelle behandelt, weil auch zur Ermittlung der Ressourcennutzung die Anwendung zur Ausführung gebracht werden muss. Viele der Regeln des Benchmarking besitzen auch beim Profiling
der Ressourcennutzung Gültigkeit. Leider ist Erstellung von aussagekräftigen Benchmarks eine komplexe Angelegenheit, bei der viel beachtet werden
muss, um nicht fehlerhafte Rückschlüsse zu ziehen. Vor allem beim vermessen von Java-Anwendungen ergeben sich, aufgrund der komplizierten JITKompilierungs- und Optimierungsverfahren, etliche Schwierigkeiten [KA05].
Wie der Benchmark umgesetzt wird, der später für das Profiling der Ressourcennutzung eingesetzt wird, liegt vollständig in der Verantwortung des
Entwicklers. Um einzelne Codeartefakte zu untersuchen, bieten sich sogenannte Micro-Benchmarks an. Sie bestehen nur aus einer geringen Menge
Code und sind einfach zu schreiben, da sie nur einen gewissen Teil der Funktionalität (z.B. eine Auswahl an Methoden) messen. Micro-Benchmarks bilden das Rückgrat des Performance Regression Testing und dienen häufig dem
Vergleich verschiedener Algorithmen [KA05, MH10].
Im Folgenden werden einige Best Practices zum (Micro-)Benchmarking
von Java-Anwendungen angegeben. Für weiterführende Literatur sei auf [Cor10]
[MH10] und [KA05] verwiesen.
• Datenerzeugung: Die Testdaten, die während der Ausführung vom
Treiber verwendet werden, sollten nach Möglichkeit außerhalb des Sys-
4.2. MÖGLICHKEITEN ZUR DATENERHEBUNG
71
tems erzeugt werden. Vor allem sollte die Datenerzeugung nicht mit
in die Messungen einfließen, außer die Datenerzeugung selbst ist der
Gegenstand der Untersuchungen.
• Inlining: Der JIT-Compiler nimmt bei monomorphen Methoden häufig eine Inline-Optimierung, also die Ersetzung von Methodenaufrufen
mit den Statements des Methodenrumpfs, vor. Java hat allerdings polymorphen Charakter, das heißt dass dynamisch zur Laufzeit aufgrund
vorhandener Typinformationen entscheiden wird, welche Implementierung einer Methode zu verwenden ist. Wie erläutert wurde, sammelt
JVM ständig Informationen über Methoden. Wird eine polymorphe
Methode immer nur monomorph verwendet, kann in diesem Fall auch
Inlining eingesetzt werden. Zu Beginn gilt für alle Methoden die Annahme, dass sie monomorph sind, da noch keine weiteren Informationen
vorliegen. Wird später doch festgestellt, dass die betroffene Methode
polymorph verwendet wird, so muss das Inlining wieder rückgängig
gemacht werden, was Zeit beansprucht und die Ausführungszeit der
Methode unter Umständen verlangsamt. Dieses Problem kann innerhalb durch eine Aufwärmphase umgangen werden. Bei Erstellung des
Treibers sollte zudem vermieden werden, die zu messenden Methode
über ein Interface aufzurufen.
• Aufwärmphase: Wie zuvor erläutert wurde, bringt die JIT-Kompilierung
einige komplexe Optimierungen beim Übergang in den nativen Code
ins Spiel. Je häufiger eine Methode (hot) aufgerufen wird, desto mehr
Optimierungen werden bei der Kompilierung durchgeführt. Daher sollte der JVM vor den Messungen die Möglichkeit gegeben werden, die
Methoden angemessen zu optimieren. Das kann beispielsweise durch
wiederholtes Aufrufen innerhalb einer Aufwärmphase geschehen. Wird
eine Methode nur einmal vom Treiber aufgerufen und bemessen, ist
das Ergebnis für gewöhnlich nicht repräsentativ, da sie nicht optimiert
wurde oder bei HotSpot lediglich interpretiert wird.
• Dead-Code-Elimination: Es sollte darauf geachtet werden, dass der
Code, der zum Messen und zur Ausführung der Methoden verwendet
wird, zumindest scheinbar etwas sinnvolles macht. Das hat den Hintergrund, dass, sollte von der JVM festgestellt werden, dass Codeartefakte kein Ergebnis produzieren, oder dass das Ergebnis nicht weiter
verwendet wird, sie die betreffenden Stellen komplett eliminiert (DeadCode-Elimination). Daher ist darauf zu achten, dass einzelne Testfälle
nicht komplett von der JVM wegoptimiert werden, weil beispielsweise
das Ergebnis der Methodenausführung nicht weiter verwendet wird.
72
KAPITEL 4. JAVA UND DIE RESSOURCENNUTZUNG
• Mehrfache Messungen: Gerade bei Java-Anwendungen, bei denen
die JVM komplexe Optimierungsstrategien einsetzt, sollte immer mehrfach gemessen werden, um eine Aussage über die Qualität der Daten,
beispielsweise durch Ermittlung einer Standardabweichung oder Varianz der Messwerte, zu ermöglichen.
Kapitel 5
JRUPI als konzeptueller
Rahmen zur Bestimmung der
Ressourcennutzung
Im vorangegangenen Kapitel wurde die Systemumgebung und einige ihrer
Besonderheiten, sowie die Möglichkeiten zur Datenerhebung innerhalb dieser
Umgebung beschrieben. Vor allem wurden dabei bereits Schwierigkeiten im
Zusammenhang mit der JVM herausgearbeitet, Daten über die Ressourcennutzung von Java-Anwendungen zu erheben.
Einerseits existieren viele verschiedene Profiler, um die Performance von
Java-Anwendungen zu untersuchen. Sie eignen sich allerdings nicht zur Ermittlung der physischen Ressourcennutzung, da sie nur innerhalb der JVM
arbeiten. Andererseits existieren viele Werkzeuge, um Ressourcennutzung auf
Prozess- oder Threadebene zu beobachten. Mit ihnen kann das Verhalten des
JVM-Prozesses auf Systemebene analysiert werden. Sie haben aber wiederum
keine Verbindung zu den aktuell ausgeführten Codeartefakten.
Es fehlen also geeignete Werkzeuge, um Daten über die Ressourcennutzung spezifischer Java-Codeartefakte zu erheben. Die Schließung dieser Lücke
ist die Aufgabe von Jrupi (Java Resource Usage Profiling Infrastructure).
Dabei handelt es sich zunächst um ein konzeptuelles Framework, das zwei
Aufgaben erfüllt. Erstens gibt es den Aufbau einer Infrastruktur vor, um
detaillierte Daten über die Ressourcennutzung von Java-Codeartefakten zu
erheben. Anschließend können diese Daten weiterverarbeitet werden, um eine
abstraktere Beschreibung daraus abzuleiten. Zweitens enthält es eine Vorgehensbeschreibung für den Modellentwickler, die ihm Handlungsanweisungen
zur Benutzung der Infrastruktur vorgibt, um am Ende zu einer abstrakten
Beschreibung der Ressourcennutzung von Codeartefakten zu kommen. Zur
Datenerhebung nutzt Jrupi ein einheitliches, modulares Verfahren, das sich
73
74
KAPITEL 5. JRUPI ALS KONZEPTUELLES FRAMEWORK
sowohl während der Entwicklung als auch am Produktivsystem einsetzen
lässt, ohne dass Komponenten verändert werden müssen.
Es ist die Aufgabe dieses Kapitels, die Konzepte hinter Jrupi zu erläutern. In Abschnitt 5.1 wird zunächst das erwartete Ergebnis näher spezifiziert
und auf resultierende Folgen für den Modellerstellungsprozess eingegangen.
Daraufhin grenzt Abschnitt 5.2 die betrachteten Teile der Systemumgebung
genauer ein und formuliert darauf aufbauend Entwurfskriterien für Jrupi.
In Abschnitt 5.3 wird das Architekturkonzept vorgestellt und Abschnitt 5.4
beschreibt den Ablauf (Workflow) für den Modellersteller. Abschließend werden in Abschnitt 5.5 die Komponenten zusammengefasst, die bei einer Implementierung von Jrupi durch geeignete Werkzeuge oder Implementierungen
instanziiert werden müssen.
5.1
Ergebnisdefinition und Konsequenzen
Das Ziel dieses Abschnitts besteht darin, genauer zu klären, was in dieser
Arbeit unter dem Begriff abstrakte Verhaltensbeschreibung verstanden wird.
In Unterabschnitt 3.1.3 erfolgte bereits eine Eingrenzung des Modells und der
Modellbildung, die sich an der Allgemeinen Modelltheorie (AMT) orientierte.
Dabei wurde festgelegt, dass das zu modellierende Original ein Phänomen,
nämlich die physische Ressourcennutzung, hervorgerufen durch die Ausführung von Codeartefakten, ist. Weil das Phänomen lediglich zur Laufzeit beobachtbar, und zudem von spezifischen Hardwarekomponenten abhängig ist,
kommt aus pragmatischen Gründen nur ein Reverse Engineering Ansatz für
die Modellbildung in Frage.
5.1.1
Die Komponente als Blackbox
Das Ziel ist die Gewinnung einer Verhaltensbeschreibung einer CCM Komponente in Bezug auf ihre Ressourcennutzung. In diesem Zusammenhang stellt
die Wahl einer geeigneten Abstraktionsstufe für die Beschreibung eine wichtige Entscheidung dar. Dabei ist der Zweck des Modells zu beachten. Es sollte
eine möglichst einfache (im Sinne von Rechenaufwand) Prognose über die
Ressourcennutzung einer CCM-Komponente erlauben, die von Ressourcenoder Energiemanagern verwendet werden kann, um ihre Konfigurationsentscheidungen zur Laufzeit zu treffen. Ist die Abstraktionsstufe zu hoch, so ist
die Prognose unter Umständen zu ungenau. Wird sie zu klein gewählt, so ist
sie zur Laufzeit eventuell nicht verwendbar, da ihre Auswertung beziehungsweise Berechnung zu viel Zeit und Ressourcen in Anspruch nimmt.
Es ist eine der grundlegenden Eigenschaften von Komponenten, dass sie
5.1. ERGEBNISDEFINITION UND KONSEQUENZEN
75
mit ihrer Umgebung nur über eine wohldefinierte Schnittstelle kommunizieren. Daher können sie relativ simpel mit anderen Komponenten mit derselben
Funktionalität, aber einer anderen Implementierung, ausgetauscht werden. In
CoolSoftware wird dieser Umstand genutzt, um beispielsweise Varianten mit
unterschiedlichen Servicequalitäten anbieten zu können. Daher erscheint es
sinnvoll, das Verhalten auf Ebene der öffentlichen Schnittstelle einer CCMKomponente pro Variante zu bestimmen. Die Variante einer Komponente
wird deshalb als Blackbox aufgefasst. Um diesen Sachverhalt mit Begriffen
der Systemtheorie zu beschreiben, ist die Variante das zu beschreibende System aus einer funktionalen Sichtweise. Ihre Eingabe besteht aus den Parametern der angebotenen Services und die Ausgabe äußert sich in Form
einer physischen Ressourcennutzung. Das Modell wird in Form eines generativen Systems beschrieben, das versucht, den Zusammenhang zwischen den
genannten Ein- und Ausgaben (externe Verhalten) möglichst exakt nachzubilden. Da angenommen wird, dass über die interne Struktur nichts bekannt
ist (Blackbox), entspricht die Modellbildung einer Systeminferenz. In Abbildung 5.1 werden diese Zusammenhänge durch ein Beispiel verdeutlicht.
Ressourcennutzung
{
'methodname': sort,
'CPU' : [50, 0],
'Disk':{ 'dev':sda1,
'R':800,
'W':920 }
}
Ressourcennutzung
{
'methodname': sort,
'CPU' : [70, 0],
'Disk':{ 'dev':sda1,
'R':2132,
'W':3234 }
}
Parameter
Beobachtungen
während der
Ausführung
{
'type' : Array,
'size' : 2000,
'generic' : { 'type' :String,
'length' : 4}
}
<<Port>>
<<component>>
Quicksort
Parameter
{
'type' : Array,
'size' : 5000,
'generic' : { 'type' :String,
'length' : 8}
sort()
}
interner Zustand
konkret
abstrakt
abstrakte Beschreibung
Nutzungsverhalten
z.B,
mathematisches Modell
Abbildung 5.1: Verhaltensbeschreibung: abstrakt aus konkret
Das Beispiel zeigt die Variante Quicksort (rechts) einer Sortierkompo-
76
KAPITEL 5. JRUPI ALS KONZEPTUELLES FRAMEWORK
nente. Sie besitzt einen Port namens sort(), der als Parameter ein zu sortierendes Array entgegen nimmt. Aufgrund der zahlreichen Gemeinsamkeiten zwischen Objektorientierung und Komponentenparadigma, wird ein Port
immer in Form einer Methode implementiert sein. Daher werden die beiden
Begriffe in dieser Arbeit austauschbar verwendet. Das zu sortierende Array
ist demnach die Eingabe des betrachteten Systems (Variante) und die resultierende Ressourcennutzung zur Durchführung der Aufgabe ist die Ausgabe,
wobei alle Beobachtungen zur Laufzeit durchgeführt werden. Jeder konkrete
Eingabedatensatz ist daher genau einem konkreten Ausgabedatensatz zuzuordnen, so dass sich jeweils Paare bestehend aus beliebig vielen Werten bilden lassen. Aus den konkreten Wertepaaren muss schließlich eine abstrakte
Verhaltensbeschreibung abgeleitet werden. Zu diesem Zweck bietet sich ein
mathematisches Modell an, da es eine kompakte und formale Methode der
Modellierung bietet, um das generative System zu beschreiben.
5.1.2
Erwartetes Ergebnis und Abhängigkeiten
„The only conceivable way of unveiling a black box,
is to play with it“ [Tho83]
Die Betrachtung der Komponente als Blackbox bringt einige Konsequenzen
mit sich, die sich auf das Ergebnis auswirken und die Abhängigkeiten des
Ergebnisses festlegen. Zunächst abstrahiert diese Betrachtungsweise vom internen Zustand sowie vom internen Aufbau der Komponente. Als Eingabe in
das System kommen daher nur die Parameter der zu bemessenden Methode in
Frage. Die physische Ressourcennutzung stellt die Ausgabe des betrachteten
Systems dar und resultiert aus der Ausführung der Anweisungen im Rumpf
der Methode. Sie ist durch konkrete Werte, wie beispielsweise geschriebener Bytes auf die Festplatte, die zur Ausführungszeit beobachtet werden,
gegeben. Das gesuchte Modell soll den beschriebenen Zusammenhang durch
mathematische Gleichungen beschreiben und ist daher ein mathematisches
Modell. Die Gleichungen haben dabei die folgende Form:
id
Heinheit
= f (P)
(5.1)
Dabei steht H für eine Art von Hardwarekomponente (wie CPU, Festplatte
oder Netzwerkschnittstelle) und P für die Parameter der Methode, die für
die Nutzung der Hardwarekomponente relevant sind. Über den optionalen
Index id kann die Nutzung einer spezifischen Hardwarekomponente über
die Gerätekennung (z.B. sda1 oder sdb1 für Festplatten) angegeben werden,
was beispielsweise zur Unterscheidung der verwendeten Netzwerkschnittstelle
5.1. ERGEBNISDEFINITION UND KONSEQUENZEN
77
sinnvoll ist. Der Index einheit ist ebenfalls optional und gibt eine zusätzliche
Einheit (z.B. Bytes) an, sollte sie aus dem Kontext der Hardwarekomponente
nicht klar sein.
Das Verhalten eines Ports (respektive einer Methode) einer Komponente
erfolgt durch eine Menge Gleichungen, die die Nutzung aller von ihr verwendeten Hardwareressourcen beschreiben. Durch eine derartige Beschreibung
für alle Ports einer Komponente ist, aufgrund der Annahme einer Blackbox,
das abstrakte Verhalten der Komponente spezifiziert.
Abhängigkeit zu Parametern
Die Menge P der relevanten Parameter stellt bei den Betrachtungen eine
gewisse Problematik dar. Um die Ableitung eines mathematischen Modells
zu ermöglichen, sollten die relevanten Parameter in Form numerischer Daten
beschrieben sein.
Im Beispiel von Abbildung 5.1 ist der Parameter der sort() Methode ein
generisches Array, der in dieser Form keine mathematische Beschreibung erlaubt. Vielmehr kommt es auf den Inhalt des Arrays an, wie etwa die Anzahl
der Elemente oder die durchschnittliche Länge der Strings, die als Sortierkriterium dienen. Diese Informationen stecken implizit im Array, sind aber für
gewöhnlich auf Schnittstellenebene nicht sichtbar. Es werden also zusätzliche
Daten über die die Eingabedaten, sogenannte Metadaten, benötigt. Welche
Metadaten für die Ressourcennutzung relevant sind lässt sich nicht verallgemeinern. Das liegt in der objektorientierten Natur von Java begründet, da als
Parameter einer Methode beispielsweise Referenzen auf beliebige Objekte, die
wiederum Referenzen auf andere Objekte halten, verwendet werden können.
Daher ist es auf Schnittstellenebene nicht entscheidbar, welche Metadaten
von Bedeutung sind. Allerdings kann durch eine kontrollierte Ausführung
mit bekannten Testdaten anhand der Beobachtungen der Ergebnisse auf die
Relevanz einzelner Metadaten geschlossen werden. So ist bei der Sortierung
beispielsweise eine Korrelation zwischen CPU-Nutzung und Größe des Arrays
erkennbar.
Während bei der Sortierung die Abhängigkeit zu der Größe des Arrays
relativ klar ist, müssen die relevanten Parameter bei unbekannten Komponenten anhand der Datenaufzeichnungen zur Ressourcennutzung und einer
Parameteranalyse ermittelt werden. Durch eine kontrollierte Ausführung mit
bekannten Testdaten sollte sich ein Zusammenhang, sofern dieser überhaupt
besteht, zwischen Aufbau der Testdaten und Ressourcennutzung herstellen
lassen. Somit können relevante Metadaten ermittelt werden.
Sind die Metadaten, wie im gegebenen Beispiel bekannt, so kann das Verhalten bezüglich der Ressourcennutzung einer Methode in Abhängigkeit der
78
KAPITEL 5. JRUPI ALS KONZEPTUELLES FRAMEWORK
Parameter beschrieben werden. Das Verhalten der sort() Methode könnte
beispielsweise folgendermaßen spezifiziert werden:
cputicks = f (size)
(5.2)
sda1
diskbyte
= f (size, length)
(5.3)
Die relevanten Parameter können sich je nach Hardwaretyp unterscheiden. So ist bei Sortierkomponenten beispielsweise die Länge der Strings für
die CPU-Nutzung (vergleiche Evaluation, Kapitel 7) meist vernachlässigbar.
Wird das sortierte Array jedoch auf die Festplatte gespeichert, so macht die
Länge der Strings sehr wohl einen großen Unterschied aus. Die mathematischen Gleichungen, die das Ergebnis darstellen, sind eine heuristische Annäherung der zu erwartenden Ressourcennutzung. Um sie effektiv einsetzen
zu können, sollten sie mit Erfahrungswerten über die Parameter kombiniert
werden. Auch die Kontextoren, die bei CoolSoftware Daten über die Nutzerbedürfnisse sammeln, könne hilfreiche Informationen dazu liefern.
Um eine Funktion in Abhängigkeit der Parameter (beziehungsweise ihre
Metadaten) ableiten zu können, müssen diese für die Analyse zur Verfügung
stehen. Die einfachste, jedoch aufwändigste Variante, ist die Einbringung von
Hand durch den Modellersteller bei der Auswertung. Um Metadaten automatisch während der Datensammlung verfügbar zu machen, bietet sich eine
Expansion der Parameterliste der Methode um die zusätzlichen numerischen
Daten an. So können auch diese automatisch mitprotokolliert werden. In Listing 5.1 ist ein Beispiel zur Parameterexpansion für die sort() Methode
angegeben.
1
2
// normale Me t h o d e n s i g n a t u r
public T [] sort ( T [] in ) ;
3
4
5
// um Metadaten expandierte Parame terliste
public T [] sort ( T [] in , int arraySize , int meanS ortLengt h ) ;
Listing 5.1: Expansion der Parameterliste von sort())
Da die Änderung der Schnittstelle einer Komponente im Nachhinein nicht
immer möglich ist, kann die Expansion der Parameterliste auch extern, z.B.
im Treiber, realisiert werden. Statt der eigentlichen Methode wird ein Wrapper oder Decorator [GHJV95] für die Messungen verwendet, dessen Parameterliste um die benötigten Metadaten expandiert ist, und der die eigentliche
Methode ganz normal aufruft.
Der verwendete Blackbox Ansatz zur Verhaltensbeschreibung stellt einen
Kompromiss dar. Einerseits vermindert er die Komplexität erheblich, da (im
5.2. KONTEXT UND ENTWURFSKRITERIEN
79
Gegensatz zu den vorgestellten Softwareausführungsmodellen) kein state explosion Problem auftreten kann und keine komplizierte Beschreibung des
internen Kontrollflusses benötigt wird. Andererseits ist es gerade der interne Kontrollfluss, der Einfluss auf die Ressourcennutzung nehmen kann. Eine
einfache if-Anweisung kann dafür sorgen, dass Anweisungen, die physische
Ressourcennutzung verursachen würden, gar nicht ausgeführt werden. Demgegenüber kann ein Blackbox Ansatz aber auch für beliebige Komponenten
verwendet werden, für die kein Quellcode vorhanden ist. So können mit diesem Verfahren auch Komponenten von Drittanbietern innerhalb der eigenen
Infrastruktur vermessen werden. Da insgesamt die Argumente dafür überwiegen, wird generell in Jrupi ein Blackbox Ansatz verwendet. Zugunsten
von Flexibilität und Laufzeitkomplexität wird eventuell ein Stück weit auf
Genauigkeit verzichtet.
5.2
Kontext und Entwurfskriterien
In diesem Abschnitt wird der Kontext der Betrachtungen genauer umrissen.
Er legt fest, welche Teile der Systemumgebung (vergleiche Abschnitt 4.1), für
Jrupi interessant sind. Den zweiten Teil bildet die Formulierung der Entwurfskriterien für Jrupi. Sie ergeben sich zum großen Teil aus den speziellen
Eigenschaften, die die betrachtete Systemumgebung aufweist.
5.2.1
Kontext der Betrachtungen
Der Kontext definiert die Arbeitsumgebung für Jrupi, in der die Daten erhoben werden. Abbildung 5.3 zeigt eine schematische Darstellung der betrachteten Abstraktionsebenen und ihrer wichtigsten Komponenten, die den Kontext für Jrupi vorgeben. Auf unterster Ebene (Hardware) sind die einzelnen
Hardwarekomponenten, die zusammen das physische Rechnersystem bilden,
zu finden. Wie im vorherigen Kapitel bereits erläutert wurde, werden für die
Messungen keine spezifischen Hardwarefeatures, wie HPCs verwendet. Der
Mangel an Standardisierung in diesem Bereich überwiegt leider den Vorteilen der effizienten Datensammlung. Bei den Hardwarekomponenten ist es, für
den größeren Zusammenhang, wichtig, die diversen Energie- und Performancezustände im Zusammenhang mit Ressourcennutzung und Proportionalität,
wie sie in Unterabschnitt 4.1.2 erläutert wurden, im Hinterkopf zu behalten.
Die zentrale Abstraktionsebene ist durch das Betriebssystem gegeben.
Es befindet sich zwischen der Hardware auf der einen Seite, und den Benutzerprogrammen, repräsentiert durch einzelne Prozesse, auf der anderen
Seite. Um spezifische Hardwarekomponenten anzusprechen, verwendet das
80
KAPITEL 5. JRUPI ALS KONZEPTUELLES FRAMEWORK
Javaanwendung
Prozessebene
Komponente
X
Komponente
Y
Java Virtual
Machine
Anwendung
X
JNI
Komponente
usw
Anwendung
Y
Anwendung
usw
API (syscalls) | Ausnahmebehandlung | Interrupts
Betriebssystem
Prozessmanagement
Speichermanagement
Block-I/Omanagement
Netzwerkmanagement
usw
Netzwerkgeräte
usw
Gerätetreiber
Hardware
Prozessoren
(CPUs)
Arbeitsspeicher
Speichergeräte
Abbildung 5.2: Kontext der Betrachtungen
Betriebssystem Gerätetreiber1 . Die Treiber arbeiten dabei im Kernmodus
und sind für Linux üblicherweise in Form von Kernelmodulen implementiert.
Da Treiber hardwarespezifisch sind, werden sie in dieser Arbeit nicht für
die Datenerhebung genutzt. Innerhalb eines Betriebssystems existieren für
gewöhnlich weitere zentrale Abstraktionen, die manchmal auch als Subsysteme bezeichnet werden. So bietet Linux beispielsweise ein virtuelles Dateisystem, das eine zusätzliche Abstraktionsschicht zwischen Prozessen und unterschiedlichen Dateisystemarten einführt, indem es ein einheitliches Objektmodell zur Verfügung stellt. Weiterhin besitzt ein Betriebssystem typischerweise
Subsysteme, die sich um Speichermanagement, Block-I/O-Management und
Netzwerkmanagement kümmern. Als zentraler Bestandteil des Prozessmanagements wurde im vorigen Kapitel bereits die Funktionsweise des Scheduler
erläutert, der die Zuteilung zwischen Prozessen und CPUs verwaltet. Die
einzelnen Subsysteme sind meist selbst in mehrere Ebenen unterteilt und
arbeiten bei Linux durch relativ komplexe Aufrufstrukturen zusammen (zur
Erinnerung: im Linuxkernel kann jede Funktion von jeder Stelle aufgerufen
werden, vergleiche Abschnitt 4.1). Daher wird an dieser Stelle nicht auf nähere Details eingegangen. Es sei lediglich angemerkt, dass sie aufgrund ihrer
1
genau genommen sind Gerätetreiber Software, die meist mit einem Controller, der eine
vereinfachte Stelle zum Gerät bietet, kommuniziert [Tan07]
5.2. KONTEXT UND ENTWURFSKRITERIEN
81
zentralen Lage innerhalb der Aufrufstrukturen des Kerns, im Zusammenspiel
mit dem aktuellen Kontext eine wertvolle Datenquelle darstellen.
Innerhalb der Systemumgebung wurde die JVM als weitere Abstraktionsebene eingeführt, die zwischen Betriebssystem und Codeartefakten steht. Aus
Sicht des Betriebssystems ist die JVM lediglich ein weiterer Prozess neben den
anderen Anwendungen, die im System laufen. Daher wurde in Abbildung 5.3
die JVM der Prozessebene zugeordnet. Aus der Sicht von Prozessen gibt es
nur drei Wege, um mit dem Linuxkernel zu kommunizieren: Systemaufrufe
(API, syscalls), Ausnahmebehandlung und Interrupts. In der JVM steht
zu diesem Zweck das JNI (Java Native Interface) zur Verfügung. Es erlaubt
den Aufruf von nativen Code aus der JVM heraus. Eine Überwachung der
Systemaufrufe bietet sich ebenfalls an, um Informationen über die Ressourcennutzung zu bekommen. Da sie die zentrale Schnittstelle zum Kern darstellen, müssen sie von Anwendungen verwendet werden, wenn diese Zugriff auf
Hardwarekomponenten benötigen. Das bringt allerdings mehrere Probleme
mit sich. Zum einen existieren mehrere hundert verschiedene dieser Systemaufrufe, die je nach Parameter einen anderen Weg durch die Aufrufstrukturen
im Kern einschlagen. Daher ist ihr Verhalten nicht-deterministisch und kann
nicht vereinheitlicht werden. Zum Anderen stehen Systemaufrufe auf oberster
Abstraktionsstufe. Es steht daher nicht immer ausreichend Kontextinformationen zur Verfügung, um auf die tatsächliche Nutzung physischer Geräte
Rückschlüsse ziehen zu können.
Die letzte, und damit höchste, Abstraktionsebene der Betrachtungen stellen die zu analysierenden Java-Anwendungen dar, deren Ausführung von der
JVM als Laufzeitumgebung gesteuert und überwacht wird. JVMs sind typischerweise komplexe Softwaresysteme, die zahlreiche Optimierungen, vor allem beim Übergang zu nativen Code, während der Ausführung durchführen. Damit soll der Geschwindigkeitsnachteil, den Java aufgrund seiner dynmaischen Natur besitzt, ausgeglichen werden. Es ist überaus wichtig, diese
Optimierungen im Zuge des Benchmarking beziehungsweise Profilings zu beachten, um zu aussagekräftigen Ergebnissen zu kommen (vergleiche Unterabschnitt 4.2). Im Kontext von CoolSoftware liegen Java-Anwendungen in Form
von CCM-Komponenten, die über ihre definierten Schnittstellen (Ports) miteinander kommunizieren, vor. Da zur Implementierung der Ports Java zum
Einsatz kommt, kann die Jrupi das Profiling von CCM-Komponenten ebenfalls auf Methodenebene erfolgen, da sie verwendet werden, um Ports zu
implementieren.
82
KAPITEL 5. JRUPI ALS KONZEPTUELLES FRAMEWORK
5.2.2
Formulierung der Entwurfskriterien
Aufbauend auf den bisherigen Betrachtungen wurden für Jrupi die folgenden
Entwurfskriterien definiert:
• Portabilität Aufgrund der direkten Abhängigkeit zu den verwendeten Hardwarekomponenten, sollte es möglich sein, dasselbe Profiling
auf verschiedenen Rechnern durchführen zu können. Als Voraussetzung
wird lediglich angenommen, dass Linux als Betriebssystem verwendet
wird.
• Einsatz am Produktivsystem Eine Datenerhebung sollte auch am
Produktivsystem durchführbar sein, ohne größere Änderungen durchführen zu müssen. Dazu gehört auch, dass die Funktionalität dynamisch, ohne Neustart und Unterbrechungen des Systems oder der Anwendung, zur Verfügung gestellt werden kann.
• Austauschformat Die erhobenen Daten sollten in einem flexiblen
Austauschformat gespeichert werden, das leicht weiterverarbeitet werden kann.
• Visualisierung der Daten Zur Betrachtung und weiteren Auswertung der Daten sollten diese in geeigneter Form dargestellt werden.
• Analyse der Daten Eine Voranalyse der Daten sollte statistische Verfahren wie die Ermittlung einer Standardabweichung, um die Qualität
der erhobenen Daten zu überprüfen, ermöglichen.
• Flexibilität Die einzelnen Profiler sollten ohne größere Umstände durch
andere Implementierungen ersetzt werden können.
• Erweiterbarkeit Neue Profiler, Auswertungs- und Visualisierungskomponenten müssen sich leicht hinzufügen lassen.
• Modularität Die Forderung nach eine modularen Aufbau folgt direkt
aus der benötigten Flexibilität. Zudem garantiert Modularität einfache
Erweiterbarkeit.
• Automatisierung Zeitaufwändige Aufgaben, wie die Erstellung von
Instrumentationen, sollte weitestgehend automatisiert werden. Dazu
bietet sich eine Codegenerierung an.
5.3. DAS JRUPI ARCHITEKTURKONZEPT
5.3
83
Das JRUPI Architekturkonzept
Jrupi ist kein Framework, das in Form einer Bibliothek oder Ähnlichem
vorliegt, und einfach verwendet werden kann. Vielmehr gibt es einen Rahmen vor, der festlegt, wie durch die Kombination unterschiedlicher Techniken, Methodiken und Werkzeuge eine sinnvolle Aussage über das Ressourcennutzungsverhalten von Java-Codeartefakten getroffen werden kann. Eine
konkrete Implementierung von Jrupi muss die einzelnen, lose gekoppelten
Bestandteile des Frameworks durch geeignete Werkzeuge und Implementierungen instanziieren.
Die Aufteilung der Systemumgebung in mehrere Abstraktionsebenen spiegelt sich auch in der Architektur der Jrupi wieder. Das Betriebssystem ist
die niedrigste Abstraktionsebene und dient der eigentlichen Datengewinnung
über die Ressourcennutzung. Der JVM-Prozess, der als Laufzeitumgebung
der Java-Anwendungen dient, ist der eigentliche Auslöser der Ressourcennutzung. Zur genaueren Zuordnung der Daten zu spezifische Codeartefakten
erfolgt eine Instrumentierung der CCM-Komponente. In Abbildung 5.3 ist
die konzeptuelle Architektur von Jrupi dargestellt.
Der Gegenstand der Untersuchungen ist eine konkrete Implementierung
(Variante) einer CCM-Komponente. Da eine Datenerhebung nur zur Laufzeit
möglich ist, wird eine Ausführungsumgebung für die Komponente benötigt.
Ihre Aufgabe ist es, die zu bemessenden Methoden der Komponente, unter Verwendung sinnvoller Daten als Parameter, zur Ausführung zu bringen.
Ähnlich dem Benchmarking, kann diese Funktionalität in Form eines externen Treibers implementiert werden. Die genaue Umsetzung liegt vollständig
in der Verantwortung des jeweiligen Modellerstellers, sollte aber unter Beachtung der in Unterabschnitt 4.2.4 erläuterten Best Pracitces erfolgen.
Ein weiterer, zentraler Bestandteil von Jrupi ist die Umsetzung einer
geeigneten Instrumentierung. Sie dient der Markierung der jeweiligen Codeartefakte im aktuell ausgeführten Bytecode. Es ist ihre Aufgabe, eine Verbindung zwischen Codeartefakt und der Datenerhebung auf Betriebssystemebene herzustellen. Um eine Zuordnung von Ressourcennutzungsdaten zu
spezifischen Codeartefakten zu ermöglichen, sind zwei Vorgehensweisen denkbar:
• Protokollierung von Start- und Endzeit der Ausführung eines Codeartefakts und nachträgliche Zuordnung zu den Ressourcennutzungsdaten
des JVM-Prozesses.
• Kommunikation mit den Profilern, um ihnen mitzuteilen, welches Codeartefakt gerade ausgeführt wird.
84
KAPITEL 5. JRUPI ALS KONZEPTUELLES FRAMEWORK
CCM
Komponente
Ausführungsumgebung
Instrumentation
Java Virtual
Machine
Betriebssystem
CPU
Profiler
Netzwerk
Profiler
Dateisystem
Profiler
...
Analyse-Framework
Datendarstellung
(visuell)
Datenauswertung
(statistisch)
Datenexport
(Werkzeugformate)
Intelligente
Datenanalyse
cpu=f  x , y 
Abbildung 5.3: Übersicht Konzept
Zur technischen Umsetzung der Instrumentierung können die in Unterabschnitt 4.2.1 beschriebenen Verfahren verwendet werden.
Profiler sammeln Daten über die Ressourcennutzung des JVM-Prozesses,
seiner Threads, sowie der restlichen Systemumgebung, während die JavaAnwendung durch die JVM ausgeführt wird. Für jede zu überwachende Hardwarekomponente existiert ein Profiler, der eine oder mehrere relevante Stellen
(Subsysteme) im Betriebssystem überwacht und dabei Daten über die Nutzung der jeweiligen Hardwarekomponenten sammelt. Der modulare Aufbau
trägt zur Skalierbarkeit bei, da der Overhead durch das separate Zu- und
Abschalten einzelner Profiler minimiert werden kann. Des Weiteren ermög-
5.4. DER JRUPI WORKFLOW
85
licht die Modularität, dass neue oder geänderte Implementierungen einfach
hinzugefügt werden können.
Die Profiler stellen die gesammelten Ressourcennutzungsdaten in Form
eines flexiblen Austauschformats bereit. Zur weiteren Auswertung können
die Daten vom Analyse-Framework eingelesen werden. Es bietet Funktionalität zur Datendarstellung, Datenauswertung und zum Datenexport. Die
Weiterverarbeitung der Daten durch das Analyse-Framework erfolgt nach
der Ausführung, was häufig auch als Offline-Analyse (oder post mortem) bezeichnet wird. Auch diese Maßnahme dient der Reduktion des Overheads
während der Datenerhebung. Die Hauptaufgabe des Analyse-Framework ist
eine statistische Vorauswertung der Daten durchzuführen. Zum Einen um
ihre Qualität zu bestimmen, zum Anderen um die Menge an Daten zu reduzieren. Anschließend können die Daten durch ein anderes, externes Werkzeug
weiterverarbeitet werden. Einerseits erfüllt das Analyse-Framework die Funktion eines Filters, andererseits ist es eine Abstraktionsebene für spezifischere
Werkzeuge.
Diese Werkzeuge dienen der (halb-)automatischen Weiterverarbeitung der
Daten, um einen mathematischen Zusammenhang abzuleiten. Die Wahl eine
geeigneten Werkzeugs liegt in der Verantwortung des Modellerstellers. In Abbildung 5.3 wird verallgemeinernd der Begriff Intelligente Datenanalyse
verwendet, da derartige Werkzeuge häufig auf Verfahren der Künstlichen Intelligenz zurückgreifen.
5.4
Der JRUPI Workflow
Es wurde bereits mehrfach angedeutet, dass der Modellersteller nicht zwangsläufig der Entwickler der CCM-Komponente ist. Der Grund dafür ist, dass
das Entwicklersystem gewöhnlich nicht das System ist, auf dem die Komponente später tatsächlich eingesetzt wird. Das Profiling von Codeartefakten
liefert aber Ressourcennutzungsdaten, die charakteristisch für die verbauten
Hardwarekomponenten sind. Es ist daher wahrscheinlich, dass Komponenten, die auf einem anderen System, mit einer anderen Hardwarekonfiguration eingesetzt werden, auch eine andere Ressourcennutzung aufweisen. Es
ist zudem denkbar, dass Komponenten von Drittanbietern ohne Quellcode
erworben werden. Der Blackbox Ansatz ermöglicht zwar das Vermessen der
Komponente, doch ist es oft nicht erstrebenswert (Kosten und Geheimhaltung), Dritte innerhalb der eigenen Infrastruktur arbeiten zu lassen.
Die Notwendigkeit, das Ressourcennutzungsmodell im Rahmen eines Reverse Engineering Ansatzes zu konstruieren, verlangt eine gewisse Anpassung
des Vorgehensmodells. CCM-Komponenten werden zunächst modelliert und
86
KAPITEL 5. JRUPI ALS KONZEPTUELLES FRAMEWORK
anschließend implementiert. Daraufhin kommt Jrupi zum Einsatz, um Daten
über die Ressourcennutzung zu erheben und ein mathematisches Modell zu
konstruieren, das die Ressourcennutzung der Komponente auf Interfaceebene spezifiziert. In Abbildung 5.4 ist dieser Vorgang detaillierter in Form eine
Flussdiagramms dargestellt, um den Modellentwickler eine konkrete Folge
von Handlungsanweisungen zu geben.
START
ja
CCM
Komponente
vorhanden?
START
2.
nein
2.1.Methoden
2.1.Methoden
auswählen
auswählen
1.CCM
1.CCMKomponente
Komponente
entwickeln
entwickeln
Profiling am
Produktivsystem?
2.Ausführungs2.Ausführungskontext
kontextaufsetzen
aufsetzen
ja
nein
3.System
3.System
ausführen
ausführen
4.Instrumentation
4.Instrumentation
laden
laden
2.2.Microbenchmarks
2.2.Microbenchmarks
entwickeln
entwickeln
5.Profiler
5.Profiler
starten
starten
2.3.Instrumentation
2.3.Instrumentation
entwickeln
entwickeln
ENDE
2.
Ressourcennutzungsdaten
6.Daten
6.Daten
analysieren
analysieren
7.Modell
7.Modell
ableiten
ableiten
ENDE
Abbildung 5.4: Ablauf für Modellersteller als Flussdiagramm
Im Folgenden werden die einzelnen Aktivitäten, die vom Modellersteller
auszuführen sind, um zu einer abstrakten (mathematischen) Verhaltensbeschreibung einer Komponente zu gelangen, schrittweise erläutert:
1. CCM Komponente entwickeln: Es liegt in der Natur des Reverse Engineering, dass die Komponente zunächst vorhanden sein muss.
5.4. DER JRUPI WORKFLOW
87
Idealerweise wird sie selbst entwickelt, könnte aber auch von anderen
Anbietern geliefert werden.
2. Ausführungskontext aufsetzen: Das Aufsetzen eines geeigneten Ausführungskontext ist die aufwändigste und zugleich schwierigste Aufgabe, die der Modellersteller selbst durchführen muss. Zunächst muss eine
Auswahl an Methoden, für die die Erstellung eines Modells auch sinnvoll ist, getroffen werden. Ist die Komponente in Quellcode vorhanden,
kann diese Auswahl auch interne Methoden umfassen. Ansonsten wird
sie gleich der öffentlichen Schnittstelle der Komponente sein. Eventuell
können gewöhnliche Java-Profiler dem Modellentwickler Hinweise geben, für welche Methoden sich ein Profiling lohnt. Je nachdem ob das
Profiling am Produktivsystem erfolgt oder nicht, müssen gegebenenfalls Microbenchmarks, wie sie in Abschnitt 4.2 beschrieben wurden,
erstellt werden. Da diese Microbenchmarks sowieso benötigt werden,
ist es besser, sie bereits während der Entwicklung der Komponente
parallel zu erstellen. Dabei können sie beispielsweise für Performance
Regression Testing verwendet werden, wie es auch in [MH10] gefordert wird. Um die Verbindung zwischen Codeartefakt und Profiler zu
ermöglichen, müssen auf jeden Fall Instrumentationen für alle zu beobachtenden Methoden entwickelt werden. Da die Instrumentierungen
sich für verschiedene Methoden meist wenig unterscheiden, können sie
automatisch generiert werden, um den Modellentwickler zu entlasten.
3. System ausführen: Sind alle Vorbereitungen getroffen, so kann das
System ausgeführt werden. Wird mit Microbenchmarks gearbeitet, so
empfiehlt es sich, nach dem Aufwärmen der JVM und der Konstruktion
der Testdaten, die Ausführung zu unterbrechen, um die Profiler und
Instrumentation zu starten.
4. Instrumentierung laden: Genau genommen kommt es auf die Form
der gewählten Instrumentierung an, obs ie an dieser Stelle geladen werden muss. Wurde sie beispielsweise direkt im Quellcode vorgenommen,
ist dieser Schritt nicht nötig. Allerdings wird für Jrupi die Verwendung
eines Verfahrens empfohlen, das eine dynamische Bytecodeinstrumentierung erlaubt, also zur Laufzeit der Anwendung die Instrumentierungen in den Bytecode einbringt.
5. Profiler starten: Die Profiler benötigen als Eingabe die PID des JVMProzesses, den sie beobachten sollen. Daher können sie erst gestartet
werden, wenn die Java-Anwendung innerhalb ihres Ausführungskontext
zum Laufen gebracht wurde.
88
KAPITEL 5. JRUPI ALS KONZEPTUELLES FRAMEWORK
6. Daten analysieren: An dieser Stelle erfolgt die Vorauswertung und
Visualisierung der Daten mit Hilfe des Analyse-Framework. Es unterstützt den Modellentwickler dabei, die Qualität der Daten zu bestimmen und die Menge an Daten zu reduzieren.
7. Modell ableiten: Die finale Aufgabe besteht für den Modellentwickler darin, ein mathematisches Modell aus den Daten abzuleiten. Bei
simplen Zusammenhängen kann dies direkt und ohne Werkzeugunterstützung erfolgen. Für Jrupi wird die Verwendung von Verfahren
zur intelligenten Datenanalyse empfohlen, die in Datenmengen (semi)automatisch nach mathematischen Zusammenhängen suchen.
5.5
Zusammenfassung umzusetzender Komponenten
Dieses Kapitel hat die grundlegenden Konzepte von Jrupi erläutert. Jrupi ist kein starres Gebilde, sondern gibt einen konzeptuellen Rahmen vor,
wie ein Zusammenhang zwischen Java-Codeartefakten und physischer Ressourcennutzung hergestellt werden kann. Es besteht im Wesentlichen aus den
folgenden, weitgehend unabhängigen, Komponenten, die bei einer Umsetzung
von Jrupi mit konkreten Implementierungen oder Werkzeugen instanziiert
werden müssen.
• Profiler: Ihre Aufgabe ist die Sammlung von Daten über die physische
Ressourcennutzung. Es sollte für jeden Hardwarekomponententyp ein
separater Profiler auf Betriebssystemebene (z.B. Subsystem, Treiber)
implementiert werden.
• Datenaustauschformat: Die Profiler sollten die gesammelten Daten
in Form eines flexiblen Austauschformat zur Verfügung stellen, um eine
spätere Offline-Analyse zu ermöglichen.
• Instrumentierung: Die Instrumentierung der Java-Codeartefakte dient
der Verbindung zwischen Codeartefakt und Profiler.
• Analyse-Framework: Für jeden Profiler (und damit jede Hardwarekomponente) existiert im Analyse-Framework eine Komponente, die
die gesammelten Daten einliest, darstellt und aufbereitet.
• Intelligente Datenanalyse: Die veredelten Daten müssen schließlich
auf einen mathematischen Zusammenhang zwischen Ein- und Ausgabe
5.5. ZUSAMMENFASSUNG UMZUSETZENDER KOMPONENTEN
89
untersucht werden. Dazu können eigene Implementierungen oder externe Werkzeuge zum Einsatz kommen.
Die Aufteilung in voneinander unabhängige Komponenten ist der erläuterten
Variabilität der einzelnen Abstraktionsebenen der Systemumgebung (vergleiche Abschnitt 4.1) geschuldet. Aufgrund der Heterogenität der Implementierungen der Abstraktionsebenen kann Jrupi lediglich einen allgemeinen Rahmen vorgeben. Für eine konkrete Umsetzung sollten, soweit dies möglich ist,
existierende Werkzeuge und Frameworks zur Anwendung kommen. Da die
einzelnen Komponenten weitgehend unabhängig voneinander sind, besteht
die Herausforderung in der Bildung einer gut funktionierenden Werkzeugkette.
Das folgende Kapitel 6 zeigt, wie Jrupi effizient implementiert werden
kann. Die Profiler sind in diesem Fall für das Betriebssystem Linux implementiert.
90
KAPITEL 5. JRUPI ALS KONZEPTUELLES FRAMEWORK
Kapitel 6
Die Proof-of-Concept
Implementierung der JRUPI
Die Jrupi (Java Resource Usage Profiling Infrastructure) ist, wie im vorangegangenen Kapitel erläutert wurde, ein konzeptuelles Rahmenwerk zur Ableitung eines mathematischen Zusammenhangs zwischen Java-Codeartefakten
und physischer Ressourcennutzung. Einerseits gibt Jrupi dem Modellentwickler eine Folge von Handlungsanweisungen vor, die zur Gewinnung eines
mathematischen Modells nötig sind. Andererseits beschreibt Jrupi die einzelnen Bestandteile, die für eine konkrete Umsetzung durch geeignete Implementierungen oder Werkzeuge instanziiert werden müssen. Die zunächst
abstrakte Beschreibung der Bestandteile liegt in der Heterogenität der Abstraktionsebenen der Systemumgebung begründet, wobei hauptsächlich die
Ebenen Betriebssystem und JVM betroffen sind. Es wurde gezeigt, dass es
sinnvoll ist, Daten über die physische Ressourcennutzung auf Ebene des Betriebssystems zu erheben. Dieser Umstand macht die verwendeten Profiler
von Natur aus plattformspezifisch (Betriebssystem). Die verwendete JVM
kann auf zwei Arten Einfluss innerhalb von Jrupi nehmen. Zum Einen sollte der Modellentwickler bei der Erstellung von Benchmarks die jeweiligen
Optimierungen der verwendeten JVM beachten. Zum Anderen kann die Implementierung der JVM Einfluss auf die Umsetzung der Instrumentierung haben. Werden die zusätzlichen Anweisungen statisch in Quell- oder Bytecode
eingefügt, so ist die JVM nicht von Bedeutung. Bei einer Instrumentierung
zur Laufzeit muss sie jedoch geeignete Mechanismen bereitstellen, um den
Instrumentationscode vor dem Laden einer Klasse durch den Class Loader
einzufügen.
Dieses Kapitel beschreibt eine Proof-of-Concept Implementierung von
Jrupi, die das Profiling für das Betriebssystem Linux umsetzt. Die Instrumentierung erfolgt dynamisch durch Bytecode-Manipulation zur Laufzeit.
91
92
KAPITEL 6. PROOF-OF-CONCEPT IMPLEMENTIERUNG
Die restlichen Komponenten sind plattformunabhängig. Bei Implementierung
von Jrupi kommen zur Umsetzung der einzelnen Komponenten unterschiedliche Werkzeuge und Konzepte zum Einsatz. Da die Aufgaben, die sie übernehmen, relativ komplex sind, wurde soweit möglich auf existierende Werkzeuge zurückgegriffen. Abschnitt 6.1 instanziiert zunächst die Mehrzahl der
durch Jrupi vorgegebenen Komponenten mit konkreten Werkzeugen. Lediglich das Analyse-Framework ist eine Eigenimplementierung und wird daher
erst in Abschnitt 6.2, der die konkrete Umsetzung in Form einer Werkzeugkette beschreibt, besprochen.
6.1
Instanziierung der Komponenten
Jeder der folgenden Unterabschnitte ist einer spezifischen Komponente gewidmet und erfüllt zweierlei Aufgaben: Erstens liefert er eine Begründung
für die Auswahl der jeweiligen Umsetzung. Zweitens gibt er eine kurze Einführung in die Funktionalitäten der jeweiligen Werkzeuge. Die Beschreibung,
wie die Werkzeuge für die spezifische Implementierung der Komponenten
eingesetzt und kombiniert werden, gibt später Abschnitt 6.2.
6.1.1
Profiler: SystemTap
„(SystemTap) ...makes it very easy to observe anything about a live
system...the problem is to figure out what you want to observe“
Die Profiler sind das Herzstück einer jeden Implementierung der Jrupi. Es
ist ihre Aufgabe, die Daten über die physische Ressourcennutzung auf Betriebssystemebene zu erheben. Damit sind sie die messenden Komponenten
und ihre Implementierung entscheidet wesentlich über die Qualität der erhobenen Daten. In dieser Arbeit werden ausdrücklich Verfahren empfohlen,
die im Allgemeinen unter den Begriffen Dynamisches Tracing oder Dynamische Ablaufverfolgung bekannt sind. Sie erlauben eine Instrumentierung
nahezu beliebiger Stellen im Betriebssystemkern. Für das Betriebssystem Linux steht mit SystemTap1 eine Umsetzung der dynamischen Ablaufverfolgung zur Verfügung, die mittlerweile als ausgereift betrachtet werden kann.
Lediglich die Anwendung im user space, die mit SystemTap prinzipiell auch
möglich ist, sollte mit Vorsicht eingesetzt werden, da sie noch nicht vollständig unterstützt ist. So hat SystemTap den Sprung in die Repositories aller
größeren Distributionen geschafft und kann auch bei Enterprise Varianten
1
http://sourceware.org/systemtap/
6.1. INSTANZIIERUNG DER KOMPONENTEN
93
meist problemlos über die Paketverwaltung nachgerüstet werden. Im Folgenden werden die Konzepte und die Funktionsweise von SystemTap vorgestellt.
Für weiterführende Informationen sei auf die Dokumentation (unter der angegebenen Webseite) oder [BPBS10] verwiesen.
Grundlegende Konzepte
SystemTap erlaubt die dynamische Instrumentierung nahezu beliebiger Stellen innerhalb des Linux-Kernels, wobei die instrumentierten Stellen als Prüfpunkte (Probe) bezeichnet werden und programmierbar sind. Dabei setzt sich
jeder Prüfpunkt aus den fundamentalen Bestandteilen Event und Handler
zusammen.
• Event: Der Eintritt eines Ereignisses, das von besonderen Interesse
ist. Ein Event kann beispielsweise durch Funktionseintritt, Funktionsaustritt oder den Ablauf eines Timers ausgelöst werden.
• Handler: Sobald ein spezifiziertes Event eingetreten ist, werden die
durch einen zugehörigen Handler festgelegten Aktionen ausgeführt.
Die Prüfpunkte können daher mit programmierbaren Messfühlern innerhalb der Kernels verglichen werden. Mit diesen Konzepten ist es möglich,
Aktivitäten innerhalb des Linux-Kernels während der Ausführung zu beobachten, zu inspizieren und zu analysieren.
Zur Beschreibung der Prüfpunkte wurde für SystemTap eine eigene Skriptsprache entwickelt, deren Syntax an C und awk angelehnt ist. Einerseits erlaubt sie die Benennung der Events, die von Interesse sind. Andererseits wird
in dieser Sprache auch der Handlercode, also die Statements, die bei Eintritt
eines spezifizierten Ereignisses ausgeführt werden sollen, festgelegt. Ein Prüfpunkt hat allgemein den in Listing 6.1 dargestellten Aufbau.
1
probe event { statements }
Listing 6.1: Allgemeiner Definition eines Prüfpunktes
Ein Prüfpunkt in SystemTap wird durch das Schlüsselwort probe eingeleitet, gefolgt von einen oder mehreren Events (event). Bei der Festlegung
der Events können auch fortgeschrittene Konzepte wie Wildcards (*) zum
Einsatz kommen. Der Handlercode wird durch eine Reihe von Statements
(statements) beschrieben, die alle Sprachkonzepte der Skriptsprache verwenden können. Dabei ist die Skriptsprache relativ mächtig und definiert
beispielsweise Konstrukte wie Schleifen, assoziative Arrays, statistische Aggregate, eingebaute Hilfsfunktionen und viele mehr. Listing 6.2 zeigt ein Beispiel für die Definition eines Prüfpunktes.
94
1
2
3
KAPITEL 6. PROOF-OF-CONCEPT IMPLEMENTIERUNG
probe kernel . function ( " * @net / socket . c " ) {
printf ( " % s -> % s \ n " , t h r e a d _ i n d e n t (1) , p r o b e f u n c () )
}
Listing 6.2: Beispiel der Definition eines Prüfpunktes
Dieser Prüfpunkt definiert als Events den Eintritt in eine beliebige Funktion, die im Linux-Kernel innerhalb der Datei /net/socket.c definiert ist.
An dieser Stelle wird deutlich, dass zum Einsatz eine gewisse Kenntnis über
den Linux-Kernel nötig ist. Gute Informationsquellen sind [Lov10, BC00]
oder der Quellcode von Linux, der frei verfügbar ist. Im Handler-Body ist
ein für das Tracing typisches Ausgabestatement (printf()) definiert. Das
Beispiel führt zu zwei weiteren wichtigen Konzepten:
• Tapsets sind Bibliotheken, die nützliche Hilfsfunktionen (im Beispiel
probefunc() und thread_indent()) und vordefinierte Prüfpunkte umfassen. Einerseits dienen sie der Abstraktion, wenn die Sammlung von
Daten für eine spezifische Kernelfunktion kompliziert ist. Andererseits
fördern sie die Wiederverwendung, indem sie Aliase für Prüfpunkte
definieren. Ein Alias kann dabei für mehrere Prüfpunkte verwendet
werden (1:n Mapping) und ist daher mit einem Pointcut aus der AOP
[KLM+ 97] vergleichbar.
• Kontext: Die meisten Informationen, die in Handlern gesammelt oder
ausgegeben werden, stammen aus dem Kontext. Dieser umfasst beispielsweise Parameter der Kernelfunktion, lokale Variablen oder über
Hilfsfunktionen aufgelöste Informationen wie die aktuelle Prozess-ID
(PID).
Beispiel
In Abbildung 6.1 ist der Zusammenhang zwischen SystemTap-Skript, Tapsets, sowie der eigentlichen Kernelfunktion dargestellt. Dieses Beispiel verdeutlicht alle bisher vorgestellten Konzepte. Im oberen Bereich ist ein Auszug aus einem Skript namens disktop.stp abgebildet, das Prozesse mit der
höchsten Festplattenaktivität ermittelt und ausgibt. Zu diesem Zweck definiert es einen Prüfpunkt für das Event vfs.write.return, welches sich auf
eine spezielle Kernelfunktion im virtuellen Dateisystem von Linux bezieht.
Das verwendete Event ist ein Alias für einen anderen Prüfpunkt, der als
Event die Kernelfunktion (vfs_write()) bestimmt, und in einem Tapset
mit Namen vfs.stp definiert ist (mittlerer Block). Eine Suche im LinuxKernel zeigt, dass sich diese Funktion in der Datei /fs/read_write.c (unterer Block) befindet. An diesem Beispiel lässt sich sehr schön die verfügbare
95
6.1. INSTANZIIERUNG DER KOMPONENTEN
probe
probe vfs.write.return
vfs.write.return {{
if
if ($return>0)
($return>0) {{
if
if (devname!="N/A")
(devname!="N/A") {{ /*skip
/*skip update
update cache*/
cache*/
io_stat[pid(),execname(),uid(),ppid(),"W"]
io_stat[pid(),execname(),uid(),ppid(),"W"] +=
+= $return
$return
device[pid(),execname(),uid(),ppid(),"W"]
device[pid(),execname(),uid(),ppid(),"W"] == devname
devname
write_bytes
write_bytes +=
+= $return
$return
}}
}}
}}
Skript
disktop.stp
probe
probe vfs.write.return
vfs.write.return == kernel.function("vfs_write").return
kernel.function("vfs_write").return
{{
name
=
"vfs.write"
name = "vfs.write"
retstr
retstr == sprintf("%d",
sprintf("%d", $return)
$return)
file
file == $file
$file
pos
pos == $pos
$pos
buf
buf == $buf
$buf
bytes_to_write
bytes_to_write == $count
$count
[...]
[...]
Tapset
vfs.stp
ssize_t
ssize_t vfs_write(struct
vfs_write(struct file
file *file,
*file, const
const char
char __user
__user *buf,
*buf,
size_t
size_t count,
count, loff_t
loff_t *pos)
*pos)
{{
ssize_t
ssize_t ret;
ret;
[...]
[...]
Kernel
/fs/read_write.c
Abbildung 6.1: Verhältnis zwischen Skript, Tapset und Kernelfunktion
Kontextinformation erkennen. So werden alle Parameter und einige lokale Variablen im Tapset aufgelöst und können im Skript einfach verwendet werden.
Das Skript selbst verwendet mehrere Hilfsfunktionen, die zusätzliche Kontextinformationen zum Prozess liefern und in assoziative Arrays (io_stat
und device) gespeichert werden. Da der Prüfpunkt am Funktionsausgang
definiert ist, kann zusätzlich der Rückgabewert der Funktion ($return) verwendet werden, der der Menge geschriebener Bytes entspricht.
Funktionsweise
Bisher wurde gezeigt, wie Skripte erstellt werden, die zur Laufzeit Daten über
den Linux-Kernel sammeln oder ausgeben. Doch wie ist das überhaupt möglich? SystemTap liefert als Frontend das Konsolenwerkzeug stap. Es nimmt
zunächst eine Skriptdatei entgegen und erstellt daraus in mehreren Schritten
ein geladenes Kernelmodul. In Abbildung 6.2 ist der gesamte Prozess dargestellt. Im Folgenden werden die einzelnen Stufen stichpunktartig erläutert.
Für detaillierte Informationen sei auf [BPBS10] verwiesen.
• Übersetzen: Zunächst wird das Skript (profiler.stp) vom Parser
eingelesen und anschließend auf Fehler untersucht. Daraufhin wird es
96
KAPITEL 6. PROOF-OF-CONCEPT IMPLEMENTIERUNG
Skript
Skript
(profiler.stp)
(profiler.stp)
Tapsets
Tapsets
Übersetzen
Übersetzen
Debug-Info
Debug-Info
Profiler.c
Profiler.c
Build
Build
Profiler.ko
Profiler.ko
Laden
Laden und
und
Ausführen
Ausführen
Ausgabe
Ausgabe
Aktivitäten
Aktivitäten
(stap)
(stap)
Artefakte
Artefakte
Entladen
Entladen
Legende
Abbildung 6.2: Funktionsweise von SystemTap nach
mit Informationen aus Tapsets und Debug-Info2 vervollständigt und
schließlich nach C übersetzt (Profiler.c).
• Build: Aus dem C-Code wird ein Kernelmodul (Profiler.ko) erstellt,
das dynamisch in den Kernel geladen werden kann.
• Laden und Ausführen: SystemTap übernimmt das Laden des erzeugten Kernelmoduls. Sobald das Modul geladen ist beginnt die Ausführung, bei der der Handler-Code an die korrekten Positionen der Beschriebenen Prüfpunkte eingesetzt wird. Das Verfahren ist vergleichbar
mit dem Aufruf einer schnellen Subroutine.
Begründung
SystemTap ist ein mächtiges Framework, das es beispielsweise ermöglicht,
Prüfpunkte an strategischen Stellen in den Subsystemen des Betriebssystems zu platzieren. Meist stellen die Subsysteme, wie das gegebene Beispiel
für das virtuelle Dateisystem zeigte, zentrale Abstraktionen bereit, deren
2
wird für die Adressierung verwendeter Funktionen und Variablen benötigt und bei der
Kompilierung des Kernels erzeugt
6.1. INSTANZIIERUNG DER KOMPONENTEN
97
Funktionen zentrale Anlaufstelle für alle Operationen, die spezifische Hardwarekomponenten betreffen, sind. Prüfpunkte an diesen Funktionen können
aus den bereitgestellten Kontextinformationen detailliert die Nutzung spezifischer Hardwarekomponenten einzelnen Prozessen oder Threads zuordnen.
Daher eignet sich SystemTap hervorragend zur Implementierung der Profiler.
Neben der allgemeinen Eignung bietet SystemTap folgende Vorteile:
• Es ist dynamisch, da die Instrumentationen in den laufenden Kernel,
ohne Neustart oder Rekompilieren, injiziert werden.
• Es ist sicher, da die Einhaltung von Sicherheitsbeschränkungen durch
stap überwacht wird.
• Es ist effizient, da der Handler-Code im privilegierten Kernmodus, vergleichbar mit dem Aufruf einer schnellen Subroutine, an den vorgegebenen Stellen ausgeführt wird. Darüber hinaus sind immer nur die
Prüfpunkte aktiv, die tatsächlich auch angegeben wurden.
• Durch die bisher genannten Vorteile eignet es sich zum Einsatz am
Produktivsystem.
• Es ist flexibel, da es durch eine mächtige Skriptsprache erlaubt, Daten
zu analysieren, zu filtern, zu aggregieren, zu transformieren und zusammenzufassen. Damit wird auch eine kontrollierte Ausgabe ermöglicht.
• SystemTap liefert bei der Installation bereits eine große Menge Tapsets
der Entwickler mit. Dadurch wird eine korrekte Verwendung erleichtert.
• Es ist für fast alle Distributionen verfügbar und kann relativ einfach
nachgerüstet werden. Zudem steht es unter aktiver Entwicklung und
Fragen an die Mailingliste wurden schnell und kompetent beantwortet.
Demgegenüber steht eine steile Lernkurve, die unter Anderem von vorhandenen Kenntnissen des Kernels abhängig ist. Auch die Entwicklung effektiver
Skripte ist bisweilen schwierig, da Ausführungszeit ein wichtiger Faktor ist
und Sicherheitsbeschränkungen eingehalten werden müssen.
6.1.2
Instrumentierung: BTrace
Die Instrumentierung der Java-Anwendung wird benötigt, um eine Verbindung zwischen aktuellen Codeartefakt und den Profilern herzustellen. Jrupi
ist zunächst unabhängig davon, welche der in Abschnitt 4.2 vorgestellten
Techniken verwendet wird. Im Folgenden werden die Vor- und Nachteile der
unterschiedlichen Verfahren zusammengefasst.
98
KAPITEL 6. PROOF-OF-CONCEPT IMPLEMENTIERUNG
Instrumentierung
Quellcode
Bytecode
Vorteile
+ einfach
+ unabhängig von JVM
+ kein Quellcode nötig
+ dynamisch möglich
Nachteile
- Quellcode nötig
- unflexibel
- komplexer
Für Jrupi wird eine Instrumentierung des Bytecodes empfohlen, da dafür kein Quellcode benötigt wird. So können auch Komponenten von Drittanbietern oder externe Bibliotheken vermessen werden. Der Preis dafür ist
die höhere Komplexität, weil mit der abstrakteren Bytecode-Repräsentation
umgegangen werden muss. Da spezielle Stellen (beispielsweise Methodeneinund ausgang) instrumentiert werden sollen, bieten sich auch Werkzeuge der
AOP [KLM+ 97] zur Umsetzung an, um Aspekte zur Übersetzungszeit einbringen zu lassen. Da die zusätzlichen Anweisungen allerdings nur während
des Profiling benötigt werden, müssten zwei Versionen der übersetzten Komponente verwaltet werden: eine mit und eine ohne Instrumentierungen. Daher wurde für die Umsetzung der Instrumentierungen dynamische BytecodeManipulation gewählt.
Bei diesem Verfahren erfolgt die Manipulation des Bytecodes typischerweise zur Laufzeit durch einen Java-Agenten, der die Dienste der Schnittstelle java.lang.instrument nutzt. Zur Implementierung des Agenten können
Frameworks wie JavaAssist[Chi04], BCEL [The06] oder ASM[Bru07] verwendet werden. Der Agent wird entweder mit der JVM gestartet oder dynamisch in die JVM geladen. Weil sich die Implementierung einer flexiblen
Lösung zur dynamischen Bytecode-Manipulation relativ komplex darstellt,
wurde auf das Framework BTrace 3 zurückgegriffen.
Grundlagen BTrace
BTrace wurde bei der Entwicklung maßgeblich von Verfahren zur dynamischen Ablaufverfolgung beeinflusst. Daher besitzen die meisten Konzepte,
die für SystemTap vorgestellt wurden, auch für BTrace Gültigkeit. Der Gegenstand der Instrumentierung ist jedoch eine Java-Anwendung anstelle des
Kernels. BTrace leistet auf der Abstraktionsebene JVM ähnliches wie SystemTap auf Betriebssystemebene.
Technisch gesehen verwendet BTrace das ASM Framework zur BytecodeManipulation sowie einen Mechanismus, um den Agenten dynamisch in eine
laufende JVM zu laden und wieder aus der JVM zu entfernen. Der Instumentationscode wird beim ersten Laden einer Klasse durch den Class Loader
eingebracht.
3
http://kenai.com/projects/btrace
6.1. INSTANZIIERUNG DER KOMPONENTEN
99
Zur Beschreibung der Prüfpunkte verwendet BTrace keine eigene Skriptsprache (wie SystemTap), sondern Java selbst. Ein Prüfpunkt setzt sich aus
der Definition eines Events und darauf folgender Aktionen (ähnlich Handler) zusammen. Zur Definition der Events werden Annotationen verwendet
und der Handlercode wird innerhalb eines Methodenrumpfes ausgedrückt.
Daher erfolgt die Beschreibung der Prüfpunkte innerhalb einer gewöhnlichen
Java-Klasse, die aus Methoden der folgenden Form besteht:
1
2
3
4
@Eventbeschreibung
public static void einName () {
// Aktionen
}
Die Methoden sind mit einer Beschreibung des Events annotiert, die beispielsweise eine spezielle Stelle im Code der Anwendung beschreiben. Im Rumpf
werden die Anweisungen definiert, die bei Eintritt des Events ausgeführt
werden sollen.
1
2
@BTrace
public class T h r e a d S t a r t T r a c e r {
3
@OnMethod ( clazz = " java . lang . Thread " , method = " start " )
public static void threadStart () {
BTraceUtil . println ( " Ein Thread wurde g e s t a r t e t " )
}
4
5
6
7
8
9
}
Bei BTrace wird besonderer Wert auf Sicherheit gelegt, da es für eine Verwendung zur Laufzeit ausgelegt ist. Daher dürfen alle Aktionen nur lesend
stattfinden und können eine gewisse Ausführungszeit nicht überschreiten. Um
sicherzustellen, dass die eingebrachten Anweisungen den Programmzustand
nicht verändern, sind lediglich statische Aufrufe an die BTrace-Runtime erlaubt.
Begründung
BTrace bietet eine ausgereifte Infrastruktur, um zur Laufzeit zusätzliche Anweisungen an diverse Stellen in den Bytecode einzufügen. Damit verfügt es
über alle Vorteile, die für die Instrumentierung des Bytecodes genannt wurden. Für Jrupi sind vor allem folgende Eigenschaften von BTrace von Vorteil:
• Es arbeitet dynamisch, so dass kein Neustart der JVM oder der JavaAnwendung nötig ist.
• Da die Instrumentierung zur Laufzeit aktiviert beziehungsweise deaktiviert werden kann und beim Laden der Klasse durchgeführt wird, kann
100
KAPITEL 6. PROOF-OF-CONCEPT IMPLEMENTIERUNG
die unveränderte Anwendung vorübergehend Gegenstand der Instrumentierung sein. Es muss nur eine Version der Anwendung verwaltet
werden.
• Dieser Umstand garantiert auch Portabilität. Die Instrumentierungsbeschreibung muss nur einmal, zum Beispiel am Entwicklersystem, umgesetzt werden. Anschließend kann sie auf allen anderen System zur
Laufzeit in die JVM geladen werden.
• Aufgrund der Sicherheitsbeschränkungen eignet sich BTrace zum Einsatz am Produktivsystem.
• BTrace ist einfach in der Anwendung. Es ist nicht nötig, sich in die,
teilweise komplizierte, Bytecode-Manipulation einzuarbeiten.
6.1.3
Datenaustauschformat: JSON
Die mit SystemTap implementierten Profiler erlauben eine kontrollierte und
formatierte Ausgabe der gesammelten Daten. Um die Daten für eine spätere
Offline-Analyse wieder einlesen zu können, sollte die Ausgabe in Form eines
flexiblen Austauschformats erfolgen. Zu diesem Zweck wurde für die Proof-ofConcept Implementierung die JSON4 (JavaScript Object Notation) gewählt.
Dabei handelt es sich um eine einfache Sprache im Textformat, die sowohl
von Menschen als auch von Maschinen leicht gelesen und geschrieben werden
kann. Häufig kommt sie im Bereich von Web-Anwendungen (beispielsweise zum Datenaustausch im Zusammenspiel mit Ajax) zum Einsatz, weil sie
gewöhnlich eine kompaktere Repräsentation der Daten als andere Austauschformate (z.B. XML) ermöglicht.
In Abbildung 6.3 sind die Kernelemente der Sprache als Syntaxdiagramme
angegeben. Die beiden Hauptkonstrukte bilden die universellen Datenstrukturen Objekt (object) und Array (array).
• Ein Objekt ist eine ungeordnete Menge bestehend aus Schlüssel- und
Wertpaaren. Der Schlüssel ist ein String (string) und der Wert (value)
kann ein beliebiges Element sein, das in der JSON definiert ist. Unter
anderem kann der Wert wieder ein Objekt sein, so dass die Bildung
rekursiver Strukturen möglich ist.
• Ein Array ist eine einfache Sammlung von Werten, die durch ein Komma voneinander getrennt werden. Auch hier sind rekursive Strukturen
möglich, beispielsweise durch Verschachtelung von Arrays.
4
http://www.json.org/
101
6.1. INSTANZIIERUNG DER KOMPONENTEN
object
object
{{
string
string
::
value
value
}}
,,
array
array
[[
value
value
]]
,,
value
value
string
string
number
number
object
object
array
array
true
true
false
false
null
null
Abbildung 6.3: Kernelemente der JSON als Syntaxdiagramm
Durch die Verwendung dieser universellen Konzepte ist die JSON unabhängig von einer konkreten Programmiersprache und kann durch ihre rekursive Natur auch komplexe Strukturen einfach ausdrücken. Da JSON auf einer
Untermenge von JavaScript basiert, ist jede Definition, die sich an die gegebenen Syntaxregeln hält auch valides JavaScript und kann mit der eval()
Funktion direkt eingelesen werden.
In Listing 6.3 ist ein Beispiel für die Verwendung der JSON im Kontext
des CPU-Profiling auf Methodenebene angegeben. Die rekursive Natur wird
im Beispiel zur Beschreibung der Daten beliebig vieler CPUs („cpu“) verwendet, indem pro CPU ein eigenes Objekt mit dem Schlüssel der CPU-Nummer
angelegt wird.
1
{
" type " : " m e t h o d _ c p u " , // Typ : CPU - Nutzung einer Methode
" s i g n a t u r e " : " a l g o r i t h m s : H e a p S o r t : sort : 1 0 0 0 0 0 ; 5 " , // Signatur
" start " :1297082500412911469 , // Startzeit : Nanosekunden seid Epoch
" delta " :154187389 , // Au sf üh r un gs ze i t der Methode
" cpu " :{
" 0 " :{ " method " :152 , " idle " :0 , " target " :152 , " rest " :2} , // CPU : 0
" 1 " :{ " method " :0 , " idle " :133 , " target " :0 , " rest " :21}
// CPU : 1
}
2
3
4
5
6
7
8
9
10
}
Listing 6.3: Beispiel: Ein Datensatz in JSON, erzeugt durch den CPU-Profiler
für eine Methodenausführung
102
KAPITEL 6. PROOF-OF-CONCEPT IMPLEMENTIERUNG
Begründung
Die JSON ist ein flexibles Austauschformat, das mit Hilfe weniger und einfacher Strukturen in der Lage ist, beliebig komplexe Daten auf elegante Weise
darzustellen.
• JSON ist kompakt in der Beschreibung, wodurch der Overhead reduziert wird. Da die Ausgaben der Profiler zur Laufzeit erfolgen, sollte
die Menge der aufgezeichneten Daten möglichst gering sein.
• Es ist leicht, die Ausgabe eines Profilers um zusätzliche Schlüssel- und
Wertpaare zu erweitern. Das Ergebnis ist wieder valides Format, solange die Syntaxregeln beachtet werden.
• Es ist für Menschen einfach zu lesen und kann daher direkt zur Einsicht
und Analyse der Daten verwendet werden. Für Maschinen ist die JSON
einfach zu parsen und zu generieren, so dass die Aufzeichnungen leicht
erstellt und weiterverarbeitet werden können.
6.1.4
Intelligente Datenanalyse: Eureqa
Nachdem die Daten vom Analyse-Framework aufbereitet und eventuell verdichtet wurden, folgt der letzte Schritt. In der verbleibenden Datenmenge muss nach Zusammenhängen gesucht werden, die das Verhältnis zwischen Eingabe (Methodenparameter und zugehörige Metadaten) und Ausgabe (Ressourcennutzung) nachbilden. Oft sind diese Zusammenhänge nicht
direkt ersichtlich und in den Daten „versteckt“. Um in diesem Fall dennoch
zu einer sinnvollen Aussage zu kommen, wird die Verwendung von Verfahren aus dem Bereich der intelligenten Datenanalyse (IDA) empfohlen. Diese
Datenanalysen kommen (fast) ohne Modellannahmen aus und extrahieren
Wissen direkt aus den vorhandenen Datenmengen. Dabei wird das verwendete Verfahren (beziehungsweise dessen Parameter) an die jeweiligen Daten
adaptiert. Da derartige Verfahren eine hohe Komplexität aufweisen, arbeiten
sie meist heuristisch und finden daher suboptimale Lösungen [Pae06]. Im Zusammenhang mit der IDA sind Soft Computing und Data Mining verwandte
Begriffen.
Wie solche Verfahren im Detail arbeiten, ist für diese Arbeit nicht von Bedeutung. Von Interesse ist lediglich, dass entsprechende Werkzeuge existieren
und sie die formulierte Aufgabe fast automatisch lösen können. Zur Ableitung
eines mathematischen Modells wird das Werkzeug Eureqa [SL09] empfohlen,
das bei seinem Erscheinen für einiges Aufsehen in der Wissenschaft sorgte. Aus den aufgezeichneten Bewegungsdaten eines Doppelpendels konnte es
6.1. INSTANZIIERUNG DER KOMPONENTEN
103
innerhalb weniger Stunden das zweite newtonsche Gesetz für mechanische
Bewegungsgleichungen sowie das Gesetz der Impulserhaltung ableiten.
Eureqa nimmt als Eingabe eine beliebige Datenmenge entgegen. Die Daten können zunächst vorbereitet und ergänzt werden. Dabei ist es auch möglich, Beziehungen zwischen einzelnen Datenreihen explizit anzugeben und
verschiedene Verfahren aus der statistischen Datenanalyse darauf anzuwenden. Anschließend muss noch angegeben werden, auf welchen Zusammenhang
die Daten untersucht werden sollen und welche Bausteine (z.B. Konstanten,
Summe, Produkt, Logarithmus, Wurzel, Polynome,...) in der gesuchten Lösung verwendet werden dürfen. Eureqa versucht zunächst, Verbindungen zwischen einzelnen Zahlen in der Datenmenge herzustellen und das Verhältnis
mit simplen Gleichungen anzunähern. Die meisten der verwendeten Gleichungen werden zunächst wieder verworfen, wenn der festgestellte Fehler
zu groß ist. Die Gleichungen, die den Zusammenhang am besten beschreiben, werden zwischengespeichert, optimiert, neu kombiniert und wieder mit
den Daten verglichen. In einem iterativen Prozess kommen so immer bessere
Annäherungen an die realen Daten zu Stande, die beispielsweise in einem
Diagramm Fehler gegen Komplexität (Gleichung) zusammengefasst werden.
In Abbildung 6.4 ist beispielhaft die Ergebnisansicht bei der Auswertung der
CPU-Daten einer HeapSort Komponente dargestellt.
Abbildung 6.4: Eureqa bei der Auswertung der CPU-Daten einer HeapSortKomponente
104
KAPITEL 6. PROOF-OF-CONCEPT IMPLEMENTIERUNG
Begründung
Eureqa ermöglicht es, Gleichungen und (versteckte) mathematische Beziehungen in Datenmengen zu ermitteln. Im Einzelnen ergeben sich folgende
Vorteile bei der Verwendung:
• Eureqa arbeitet bei der Ableitung vollständig automatisch und kommt
ohne Modellannahmen aus. Im Gegensatz zu wissensbasierten Verfahren muss kein zusätzliches Expertenwissen eingebracht werden, so dass
der Modellentwickler weiter entlastet wird.
• Eureqa leitet eine mathematische Beschreibung ab, die ein kompaktes
und formales Modell zur Beschreibung des Verhältnisses zwischen Einund Ausgabe darstellt.
• Die gefundenen Zusammenhänge werden direkt nach Fehler und Komplexität bewertet. Der Modellentwickler muss lediglich den besten Kompromiss auswählen.
• Eureqa bietet viele Einstellmöglichkeiten bezüglich der Art gewünschten Lösung.
• Daten können vor der Ableitung bequem editiert und mit Hilfe statistischer Verfahren bearbeitet werden.
Der Nachteil bei der Verwendung von Eureqa besteht darin, dass es ein
externes Werkzeug ist und nicht ohne weiteres integriert werden kann. Daten
müssen daher in ein für Eureqa lesbares Format (wie CSV) exportiert werden. Zudem ist Eureqa nativ nur für Windows vorhanden, kann aber unter
Verwendung von Wine5 auch unter Linux eingesetzt werden.
6.2
Technische Umsetzung und Bildung
einer Werkzeugkette
Im vorangegangenen Abschnitt wurden die durch Jrupi vorgegebenen Komponenten mit konkreten Werkzeugen instanziiert. Bisher sind die einzelnen
Werkzeuge und ihre Funktionen als Individuen bekannt. Um dem Modellentwickler eine stringente Arbeitsumgebung zu bieten, müssen die einzelnen Werkzeuge in geeigneter Form zusammenarbeiten. Aufgrund der Unabhängigkeit der Werkzeuge untereinander bietet sich ein lockerer Verbund
5
http://www.winehq.org/
6.2. TECHNISCHE UMSETZUNG
105
in Gestalt einer Werkzeugkette an. Der kritischste Teil besteht in der Verbindung zwischen Codeartefakten und korrespondierender Ressourcennutzung durch Instrumentierung, worauf Unterabschnitt 6.2.1 näher eingeht. Die
Profiler sind durch das JSON-Austauschformat zur Datenbeschreibung lose
mit dem Analyse-Framework gekoppelt. Dabei ist das Analyse-Framework
Teil der Jrupi-Workbench, die zwei Aufgaben erfüllt. Einerseits unterstützt
sie den Modellentwickler bei der Erstellung der Instrumentationen und dem
Aufsetzen einer Testumgebung, indem sie Möglichkeiten zur automatischen
Codegenerierung bietet. Andererseits setzt sie die Funktionen des AnalyseFrameworks (Datendarstellung, Datenauswertung, Datenexport) um. Im Folgenden werden relevante Details der Umsetzung näher erläutert.
6.2.1
Brücke zwischen Codeartefakt und Profiler
Die Verbindung zwischen aktuell ausgeführten Codeartefakt und Ressourcennutzung ist von essentieller Bedeutung für jede Implementierung von
Jrupi. Sie unterscheidet diesen Ansatz von anderen Methoden, wie sie von
Monitoring- oder Diagnosewerkzeugen verwendet werden. Der Kern des Problems besteht in der Tatsache, dass die Ausführung auf der Abstraktionsebene der JVM stattfindet, die Daten aber an anderer Stelle, nämlich auf Betriebssystemebene erhoben werden. Diese Herangehensweise minimiert den
Overhead der Datenerhebung (beziehungsweise verlagert ihn), weil die Profiler ihre Daten an einer komplett anderen Stelle erheben. Sie arbeiten zwar
auch mit Instrumentierung (des Kernels), bieten aber die Vorteile des Samplings, da sie die Ausführung der Java-Anwendung nicht (oder kaum) beeinflussen. Um die Nutzung einzelner Hardwareressourcen spezifischen Methoden zuordnen zu können, wird eine Brücke zwischen aktueller Stelle im Bytecode und Profiler auf Systemebene benötigt. Zu diesem Zweck wird BTrace
eingesetzt, um zusätzliche Anweisungen dynamisch in den Bytecode zu injizieren, die ein durch die Profiler feststellbares Ereignis auf Betriebssystemebene auslösen.
In Abbildung 6.5 ist die konkrete Implementierung der Brücke im Detail
dargestellt. Die zentrale Idee besteht darin, aus der Java-Anwendung heraus,
an strategischen Stellen, einen Systemaufruf (syscall) auszulösen, der auf
Betriebssystemebene durch SystemTap abgefangen und analysiert werden
kann. Für die Umsetzung bietet sich die Verwendung des write() Systemaufrufs an, da er über seinen String-Parameter (Payload) zur Übermittlung
von Informationen benutzt werden kann, und sich relativ einfach aus Java
heraus aufrufen lässt. Er wird von vielen nativen Methoden, die bei Java
für die Ausgabe (System.out) zuständig sind, verwendet. So führt auch der
Aufruf von System.out.println() in der Konsequenz zu einem write()
106
KAPITEL 6. PROOF-OF-CONCEPT IMPLEMENTIERUNG
Java Virtual Machine
Betriebssystem
'Package.Class.Method.Params.method_entr'
public
public void
void someMethod(){
someMethod(){
System.out.println();
System.out.println();
//do
//do work
work
}}
probe
probe syscall.write
syscall.write {{
[System]
[System]
write()
write()
JNI
[C-library]
[C-library]
write()
write()
[syscall]
write()
sysarg
sysarg == user_string($buf)
user_string($buf)
//parse
//parse string...
string...
System.out.println();
System.out.println();
'Package.Class.Method.Params.method_exit'
[BTrace]
[BTrace]
Bytecode
Bytecode Instrumentierung
Instrumentierung
[Systemtap]
[Systemtap]
Kernel
Kernel Instrumentierung
Instrumentierung
Abbildung 6.5: Verbindung zwischen JVM und Profiler durch Bytecode Instrumentierung
Systemaufruf, der als Parameter genau den übergebenen String besitzt.
Daher wird für relevante Methoden, jeweils an Methodeneingang und Methodenausgang, durch BTrace ein System.out.println() Statement eingefügt. Der auszugebende String gehorcht dabei einen festen Format:
• Package:Class:Method:Params:method_entr für Methodeneingang.
• Package:Class:Method:Params:method_exit für Methodenausgang.
Jeder String ist daher ein geordnetes 5-Tupel (Package, Class, Method, Params, Stelle), dessen einzelne Komponenten durch konkrete Werte zu substituieren sind:
1. Package: Name des Packages, das die Klasse enthält
2. Class: Name der Klasse, die die Methode definiert
3. Method: Name der Methode
4. Params: Werte numerischer Parameter der Methode, jeweils durch ein
Semikolon (;) getrennt
5. Stelle: Markierung, ob Methodeneingang (method_entr) oder Methodenausgang (method_exit)
Die ersten vier Komponenten des 5-Tupels dienen der eindeutigen Identifikation einer Methode, wobei die Parameter darüber hinaus für eine spätere
Analyse verwendet werden können.
SystemTap definiert einen Prüfpunkt, der als Event auf den Systemaufruf write() reagiert (probe syscall.write). Stellt SystemTap fest, dass
6.2. TECHNISCHE UMSETZUNG
107
ein write() ausgelöst wurde, so wird zunächst geprüft, ob der Auslöser der
zu überwachende JVM Prozess ist. Ist dies der Fall, so wird die Zeichenkette (aus user_string($buf)) auf den Inhalt method_entr beziehungsweise
method_exit untersucht. Ergibt auch dieser Test ein positives Resultat, so
ist davon auszugehen, dass es sich um einen Methodeneingang oder Methodenausgang handelt. Die Zeichenkette wird daraufhin in einzelne Token
zerlegt und je nach Art weiter verwendet. Die ersten vier Token der übermittelten Zeichenkette dienen, in Kombination mit der Thread-Id (aus den
verfügbaren Kontextinformationen) des aufrufenden Threads, der eindeutigen Identifikation der aktuellen Methode. Zur Verwaltung der aktiven Methoden verwenden die Profiler ein assoziatives Array als Datenstruktur, das
folgenden Aufbau besitzt:
active_methods[Package.Class.Method.Params,TID] = count
Durch die doppelte Zuordnung mit qualifizierter Methodenbezeichnung und
Thread-Id wird sichergestellt, dass die Methodeneingang und Methodenausgang auch tatsächlich richtig verwaltet werden (Thread-Safety), da jeder
Java-Thread durch die JVM 1:1 auf einen Betriebssystem-Thread abgebildet
wird. Die Variable count ermöglicht zusätzlich die Erkennung von rekursiven
Methodenaufrufen:
• Im Falle eines Methodeneingangs wird sie um Eins inkrementiert.
• Im Falle eines Methodenausgangs wird sie um Eins dekrementiert. Ist
sie bei Null angelangt, so wurden alle rekursiven Aufrufe erfasst und
die Nutzungsdaten der korrekten Methode zugeordnet.
Die beschriebenen Statements (System.out.println()) mit passender
Zeichenkette als Parameter werden von BTrace zur Laufzeit an die strategischen Stellen injiziert. Dafür muss im Vorfeld eine geeignete Beschreibung in
Form einer speziell annotierten BTrace-Klasse erfolgen. Diese Beschreibung
besteht aus Paaren von Prüfpunkten, von denen der Eine den Methodeneingang, und der Andere den Methodenausgang einer relevanten Methode
beschreibt. In Listing 6.4 ist eine Beispielbeschreibung für die expandierte
sort() Methode angegeben.
1
2
3
// um Metadaten expandierte Parame terliste
// public T [] sort ( T [] in , int arraySize , int meanSor tLength ) ;
4
5
6
7
8
9
@ O n M e t h o d ( clazz = " a l g o r i t h m s . Q u i c k S o r t " , method = " sort " )
public static void s o r t Q u i c k S o r t E n t r y ( AnyType [] args ) {
System . out . println ( " a l g o r i t h m s : Q u i c k S o r t : sort : " + args [1] + " ; "
+ args [2] + " : m e t h o d _ e n t r : " ) ;
}
108
KAPITEL 6. PROOF-OF-CONCEPT IMPLEMENTIERUNG
10
11
12
13
14
15
@ O n M e t h o d ( clazz = " a l g o r i t h m s . Q u i c k S o r t " , method = " sort " , location =
@ L o c a t i o n ( value = Kind . RETURN ) )
public static void s o r t Q u i c k S o r t E x i t ( AnyType [] args ) {
System . out . println ( " a l g o r i t h m s : Q u i c k S o r t : sort : " + args [1] + " ; "
+ args [2] + " : m e t h o d _ e x i t : " ) ;
}
Listing 6.4: BTrace Instrumentierung für expandierte sort() Methode
Die Definition der Stelle, die instrumentiert werden soll, unterscheidet
sich lediglich im zusätzlichen Annotationsparameter location, wobei der
Wert @Location(value = Kind.RETURN) für einen Methodenausgang steht.
Im Methodenrumpf werden die Ausgaben beschrieben, so dass sie der zuvor erläuterten Form gerecht werden. Dabei ist zu erkennen, dass BTrace
auch in der Lage ist, beliebige Parameter abzufangen. Dazu wird das Array
AnyType[] args verwendet. Die Indizes des Arrays korrespondieren mit der
Position der Parameter in der Methodensignatur. Für das Beispiel der expandierten sort() Methode sind die erweiterten Metadaten an den Stellen
args[1] und args[2] zu finden. Bei der Erstellung der Skripte ist unbedingt
darauf zu achten, dass die Parameter sowohl bei Methodeneingang als auch
bei Methodenausgang in der gleichen Art und Weise ausgegeben werden, da
sie von den Profilern zur eindeutigen Identifikation der Methode verwendet
werden.
Weil die Beschreibungen für BTrace immer ähnlich aussehen werden, kann
die Erstellung durch einen Code-Generator automatisiert werden. Es ändern
sich lediglich die Namen und die Parameter, konzeptuell gesehen bleiben die
Paare aber gleich. Daher wurde für die Workbench ein solcher Generator
implementiert.
6.2.2
Umsetzung der Profiler
Die Profiler sind das Herzstück der Implementierung von Jrupi. Ihre Hauptaufgabe besteht darin, geeignete Stellen im Betriebssystem zu instrumentieren, um Daten über die Ressourcennutzung zu sammeln. Als geeignete Stellen bieten sich für gewöhnlich zentrale Kernelfunktionen in den Subsystemen
an, die von einer heterogenen Menge Hardwareressourcen des gleichen Gerätetyps abstrahieren. Im Wesentlichen besteht jeder Profilers aus folgenden
Bestandteilen:
• Einen oder mehrere Prüfpunkte, die die relevanten Subsysteme instrumentieren.
• Ein Prüfpunkt zur Instrumentierung der write() Systemaufrufe. Dabei werden die im vorangegangenen Unterabschnitt beschriebenen Auf-
6.2. TECHNISCHE UMSETZUNG
109
gaben wahrgenommen. Die Implementierung sieht immer ähnlich aus,
kann aber aus zwei Gründen nicht in ein Tapset ausgelagert werden:
Erstens werden diverse Arrays, die Daten für den konkreten Profiler
aufzeichnen, an dieser Stelle gelöscht. Zweitens ist eine Kommunikation von Tapset zu Profiler nicht möglich. Die Profiler müssten daher
ständig über eine Hilfsfunktion erfragen, ob sich die Liste aktiver Methoden verändert hat (Polling).
• Einen asynchronen Prüfpunkt, der, von einen Timer gesteuert, periodisch grob-granulare Informationen zur Ressourcennutzung des Systemumgebung ausgibt.
• Eine Funktion, die die Ausgabe auf Methodenebene realisiert, sobald
deren Ausführung (letzte Rekursion) beendet ist.
Die beiden letztgenannten Bestandteile sind für die Ausgabe im JSON Austauschformat zuständig. Auch diese Ausgaben sind spezifisch für den jeweiligen Profiler. Jedoch wurden Hilfsfunktionen zur einfacheren Verwendung
von JSON in ein Tapset ausgelagert.
Im Rahmen der Proof-of-Concept Implementierung von Jrupi wurden
drei Profiler für zentrale Arten von Hardwarekomponenten eines Rechnersystems umgesetzt:
• CPUProfiler: CPUs sind zentraler Bestandteil eines jeden Rechnersystems und für gewöhnlich einer der größten Energieverbraucher. Zum
Profiling der CPU-Nutzung bieten sich zwei verschiedene Strategien an.
Erstens können die Kernelfunktionen des Scheduler instrumentiert und
für jeden Thread die Zeit gemessen werden, die er tatsächlich auf einer CPU verbringt. Dazu bietet sich die Verwendung der Prüfpunkte scheduler.cpu_on und probe scheduler.cpu_off an, die bereits
durch ein mitgeliefertes Tapset verfügbar sind.
Zweitens kann der Prüfpunkt timer.profile verwendet werden, der
bei jedem Timer-Interrupt (system tick) ausgelöst wird. Aus den Kontextinformationen kann daraufhin ermittelt werden, welcher Thread
oder Prozess gerade auf welcher CPU abgearbeitet wird. Dieses Vorgehen hat den Charakter von Sampling, da in einer hohen Frequenz an
einer anderen Stelle als dem Scheduler die Daten erhoben werden, wodurch weniger Overhead entsteht. Daher wurde sich bei der Implementierung für die zweite Variante entschieden. Die Ausführliche Definition
des Prüfpunktes ist in B.2 dargestellt.
• Festplatten-Profiler: Festplatten stellen weitere zentrale Komponenten eines Rechnersystems dar und sind, vor allem wenn mehrere zum
110
KAPITEL 6. PROOF-OF-CONCEPT IMPLEMENTIERUNG
Einsatz kommen, häufig für einen großen Teil des Energieverbrauchs
verantwortlich. Die Prüfpunkte für Festplatten werden im Anschluss
an diese Zusammenstellung detaillierter erläutert.
• Netzwerk-Profiler: Auch Netzwerkgeräte verbrauchen, abhängig von
Art und Anzahl, eine gewisse Menge Energie. Mit dem implementierten
Profiler lässt sich die genaue Menge an Traffic ermitteln die, durch Ausführung einer Java-Methode, auf einer spezifischen Netzwerkressource
verursacht wurde. Konzeptuell arbeiten die Prüfpunkte analog zu den
Prüfpunkten für Festplatten und sind in B.1 dargestellt.
Im Folgenden wird exemplarisch ein Prüfpunkt des Festplatten-Profilers
genauer erläutert. In Listing 6.5 ist der Prüfpunkt für schreibende Zugriffe
(vfs.write.return), der bereits aus Abschnitt 6.1.1 bekannt ist (vergleiche
Abbildung 6.1), mit dem Handler-Code des Profilers dargestellt.
1
probe vfs . write . return {
2
if ( $return >0) { // wurde tatsächlich etwas geschrieben ?
3
4
if ( devname != " N / A " ) { /* skip update cache */
5
6
tid = tid ()
7
// Thread - Id aus Kontext
8
if ( pid () == target () ) { // Aufruf i . A . der JVM ?
9
10
// iteriere über alle aktiven Methoden
foreach ([ s , t ] in activ e_method s ) {
11
12
13
// eine Methode eines Threads kann nur aktiv sein
if ( t == tid ) {
14
15
16
// speichere Gerätename , TID und Menge Bytes
method_w [ tid , devname ] += $return
break
17
18
19
20
}
}
// kam von JVM , aber von anderer Stelle
target_w [ devname ] += $return
21
22
23
24
25
} else {
// zeichne U m g e b u n g s a k t i v i t ä t auf
all_w [ devname ] += $return
26
27
28
29
}
// aktualisiere Geräteliste
if (!([ devname ] in dev_list ) ) dev_list [ devname ] = 1
30
31
32
}
33
}
34
35
}
Listing 6.5: Ein Prüfpunkt des Festplatten-Profilers
6.2. TECHNISCHE UMSETZUNG
111
Darüber hinaus definiert der Festplatten-Profiler einen weiteren Prüfpunkt für lesende Zugriffe (vfs.read.return), der nach demselben Muster
aufgebaut ist. Zwar geben die eingefügten Kommentare eine kurze Erklärung
zu den jeweiligen Stellen im Quellcode die wichtigsten Eigenschaften sollen
aber dennoch kurz erläutert werden.
• Zeile 12: Zu diesem Zeitpunkt steht fest, dass etwas geschrieben wurde und der anfordernde Prozess der JVM entsprach. Daher wird nun
über das Array der aktiven Methoden iteriert, um festzustellen, ob die
auslösende Methode eine der überwachten Methoden ist. Das Array der
in Ausführung befindlichen Methoden ist gewöhnlich relativ klein, so
dass sich der Overhead der Iteration in Grenzen hält.
• Zeile 15: An dieser Stelle wird überprüft, ob die Thread-ID des auslösenden Threads des JVM-Prozesses in der Liste aktiver Methoden
geführt wird, da der richtige Methodenname am Prüfpunkt nicht verfügbar ist. Allerdings reicht, aufgrund der erläuterten Arbeitsweise der
JVM bei der Methodenausführung (vergleiche Unterabschnitt 4.1.4) in
Kombination mit dem 1:1 Mapping zwischen Java-Thread und Betriebssystem-Thread, der Abgleich der Thread-ID aus, um eine korrekte Zuordnung zu einer speziellen Methode zu ermöglichen.
• Zeile 18: Die Anzahl geschriebener Bytes wird nun in ein assoziatives Array mit Thread-ID und Gerätebezeichnung abgelegt. Wird später
festgestellt, dass eine Methode mit ihrer kompletten Ausführung fertig
ist, so kann wiederum über die Thread-ID der korrekte Wert geschriebener Bytes und die genaue Gerätebezeichnung der richtigen Methode
zugeordnet werden.
• Zeile 24, 28: Diese beiden Statements dienen dem Profiling der Systemumgebung. Das Erste zeichnet Festplattenaktivitäten der JVM, das
Zweite der restlichen Prozesse auf. Um die Profiler möglichst effizient
zu programmieren, wurde auf eine Zuordnung zu einzelnen Threads
oder Prozessen verzichtet.
Assoziative Arrays bieten eine einfache Möglichkeit, eine Menge Datensätze miteinander in Verbindung zu setzen und aufzuzeichnen. Ihr Einsatz
sollte jedoch mit Bedacht erfolgen. So darf ihre maximale Größe gewisse Werte nicht überschreiten. Dazu ist es nötig, die Arrays effektiv zu verwalten und
ihre Daten zu geeigneten Zeitpunkten zu löschen. Darüber hinaus kann eine
exzessive Verwendung den Profiler ineffizient machen, da assoziative Arrays
112
KAPITEL 6. PROOF-OF-CONCEPT IMPLEMENTIERUNG
als globale Variabel über einen Locking-Mechanismus geschützt werden müssen. Das kann sich unter Anderem im Überspringen einzelner Prüfpunkte äußern, da ihre maximale Ausführungszeit gewissen Grenzen unterliegt. Daher
sollten bei der Programmierung von Profilern einige Grundregeln beachtet
werden:
• Nur die Daten speichern, die tatsächlich benötigt werden.
• Arrays regelmäßig und so früh wie möglich, ganz oder teilweise (einzelne
Elemente), löschen (Clean-Up). Hierfür bietet sich meist die Ausgabe
an.
• Möglichst wenige Berechnungen innerhalb der Profiler anstellen, wenn
sie auch außerhalb erfolgen können.
Mit SystemTap kann ein Profiling fast beliebig genau umgesetzt werden. Jedoch sollten dabei immer auch der eingebrachte Overhead sowie die
Sicherheitsbeschränkungen, denen SystemTap unterliegt, Beachtung finden.
Die größte Herausforderung besteht zumeist in einer Optimierung zwischen
der Effizienz des Profilers und dem Detailgrad der erhobenen Daten.
6.2.3
Umsetzung der JRUPI-Workbench
Die Jrupi-Workbench erfüllt im Wesentlichen zwei Aufgaben: Erstens automatisiert sie Teile des Jrupi Workflows, indem sie Codegeneratoren zur
Verfügung stellt. Zweitens beherbergt sie die Implementierung das AnalyseFramework. Die Workbench setzt dabei auf das Eclipse-Framework auf und
erweitert die Eclipse-IDE um zusätzliche Plugins [GB04].
Damit steht für eventuelle Erweiterungen des Analyse-Frameworks, beispielsweise um zusätzliche Komponenten für die Datenanalyse neuer Profiler
zu implementieren, der flexible Erweiterungsmechanismus von Eclipse zur
Verfügung. Darüber hinaus bietet sich Eclipse zur Entwicklung von JavaAnwendungen an und beheimatet ebenfalls die CoolSoftware-Workbench. So
steht dem Anwender eine konsistente Entwicklungsumgebung für den gesamten Softwareentwicklungsprozess zur Verfügung.
Automatisierung durch Codegenerierung
Der Hauptgegenstand der Codegenerierung ist die aufwändige und fehleranfällige Erstellung der BTrace-Instrumentierungsbeschreibungen. Für jede
Methode, die profiliert werden soll, wird ein Paar Prüfpunkte, jeweils bestehend aus Methodeneingang und Methodenausgang, benötigt. Wie Listing 6.4
113
6.2. TECHNISCHE UMSETZUNG
bereits zeigte, unterscheidet sich die Definition dieser beiden Prüfpunkte lediglich im zusätzlichen Annotationsparameter (location) am Methodenausgang sowie in der letzten Komponente des auszugebenden Identifikationsstrings (method_entr oder method_exit). Ist der qualifizierte Klassenname
sowie die Signatur der Methode verfügbar, so kann die Erstellung vollständig
automatisiert werden.
Neben der Codegenerierung für BTrace-Skripte wurde ein Generator implementiert, der das Skelett einer einfachen Testumgebung aufsetzt. Sie kann
zunächst als Treiber für einzelne Microbenchmarks verwendet werden und
orientiert sich im Aufbau an den besonderen Anforderung an das Benchmarking. In Abbildung 6.6 ist die generierte Klassenstruktur dargestellt.
Main
Main
runSUT()
AbstractSUTRunner
AbstractSUTRunner
setUp()
warmUp()
run()
runSUT()
//Testdaten aufsetzen
setUp();
//JVM aufwärmen
warmUp();
/*Unterbrechung zum Starten
der Profiler & BTrace */
//eigentlicher Testlauf
run();
SUTRunner
SUTRunner
setUp()
warmUp()
run()
Abbildung 6.6: Simples SUT-Skellet, wie es vom Codegenerator erstellt wird
Als Einstiegspunkt (main() Methode) wird die Klasse Main erstellt. Sie
hält eine Referenz auf den abstrakten AbstractSUTRunner, der die Ausführung eines Testlaufs koordiniert. Die abstrakten Methoden sind vom Ersteller
des Benchmarks in der Unterklasse SUTRunner zu implementieren und werden
von der Methode runSUT() der abstrakten Klasse in folgender Reihenfolge
aufgerufen:
1. setUp() dient dem isolierten Aufsetzen von Testdaten, die später als
Parameter der zu bemessenden Methoden dienen. Diese Maßnahme soll
verhindern, dass die Testdatenerstellung Einfluss auf die Messungen
ausübt.
2. warmUp() dient dem Aufwärmen der JVM, damit Methoden durch JITbeziehungsweise Hotspot-Kompilierung hinreichend optimiert werden
114
KAPITEL 6. PROOF-OF-CONCEPT IMPLEMENTIERUNG
können. Dafür werden sie typischerweise mehrfach mit den generierten
Testdaten aufgerufen.
3. run() dient der eigentlichen Ausführung der Microbenchmarks. An dieser Stelle muss der Entwickler entscheiden, welche Methoden wie oft
mit was für Parametern aufgerufen werden.
Beim Aufruf der einzelnen Methoden werden zusätzliche Ausgaben auf der
Konsole erzeugt, um die aktuelle Stelle in der Ausführung zu zeigen. Nachdem der zweite Schritt (Aufwärmphase) beendet ist, wird die Ausführung
unterbrochen, damit der Nutzer die Möglichkeit hat, die Instrumentierungen
mit BTrace in die JVM zu laden und die Profiler zu starten. Dieses Vorgehen
ist (bei eigenständigen Tests) nötig, da sowohl BTrace als auch SystemTap
die PID der JVM übergeben werden muss. Sobald Instrumentierungen und
Profiler gestartet sind, wird die Ausführung, nach Drücken einer beliebigen
Taste, durch Aufruf der Methode run() fortgesetzt.
Die Integration in die grafische Oberfläche der Eclipse-IDE wurde in Form
eines Wizards implementiert. Er kann über einen Button gestartet werden,
wenn im Package Explorer ein Package selektiert ist. Abbildung 6.7 zeigt
die Oberfläche des Wizards, wobei beim Start ein Package mit verschiedenen
Sortieralgorithmen in unterschiedlichen Klassen ausgewählt war.
In der obersten Sektion ist der Zielort für die Codegenerierung einstellbar. Angegebene Packages und Pfade werden automatisch angelegt. In der
mittleren Sektion können Konfigurationen für die Codegenerierung vorgenommen werden. Dabei lässt sich festlegen, was generiert werden soll und ob
der Generator BTrace in den Classpath des Projekts hinzugefügt. Letzteres
ist nicht zwingend erforderlich, vermeidet aber die Anzeige von Fehlern in
Eclipse. Die untere Sektion dient schließlich derjenigen Methoden, für die im
BTrace-Skript Prüfpunkte zu definieren sind.
Zur Implementierung der Codegeneratoren wurde weitgehend auf die Java
Development Tools (JDT)6 für Eclipse aufgebaut, die eine interne Repräsentation aller Java-Sprachelemente besitzen. Daraus lassen sich die benötigten
Daten auslesen und als Eingabe für die Codegeneratoren verwenden.
Analyse-Framework in Eclipse
Die Aufgabe des Analyse-Framework ist die Darstellung und Auswertung
der Daten, die durch die Profiler gesammelt wurden. Darüber hinaus kümmert es sich (optional) um den Export der ausgewerteten Daten in ein werkzeugspezifisches Format, das von Werkzeugen zur intelligenten Datenanalyse
6
http://www.eclipse.org/jdt/
6.2. TECHNISCHE UMSETZUNG
115
Abbildung 6.7: Wizard zur Code-Generierung
wieder eingelesen werden kann. Bei dieser Betrachtung wirkt das AnalyseFramework daher wie ein Filter innerhalb der Werkzeugkette.
Zunächst muss das Analyse-Framework in der Lage sein, die Daten der
Profiler, die im JSON-Format vorliegen, einzulesen. Daher wurde, für die
beschriebene JSON-Syntax, mit Hilfe von EMFText7 eine Textsprache entwickelt. Zur Definition der textuellen Syntax benötigt EMFText ein EcoreMetamodell, das mit dem Eclipse Modeling Framework (EMF) [SBPM08]
erstellt werden kann, und die einzelnen Sprachelemente sowie ihre Beziehungen beschreibt. Die Verwendung von EMFText zur Sprachdefinition bietet
folgende Vorteile:
• Es wird automatisch ein Editor mit Syntaxhervorhebung und Syntax7
EMFText dient üblicherweise der Definition domänenspezifischer Sprachen (DSL),
siehe http://www.emftext.org/
116
KAPITEL 6. PROOF-OF-CONCEPT IMPLEMENTIERUNG
überprüfung (sowie weiteren aus Eclipse bekannten Funktionen) generiert, der als eine rudimentäre Form der Datendarstellung betrachtet
werden kann.
• Da EMFText im Hintergrund mit einer Instanz des definierten EcoreMetamodells arbeitet, können die EMF-Datenstrukturen (wie Resource,
ResourceSet) zum programmatischen Zugriff auf die Daten verwendet
werden. Die Inhalte dieser Datenstrukturen entsprechen den instanziierten Metamodellelementen und sind einfach zu traversieren (z.B. mit
einem TreeIterator).
Die von den Profilern erhobenen Daten liegen in Form einer Datei vor,
die in Eclipse mit dem generierten Editor geöffnet werden kann. Der Zugriff
auf die Rohdaten sollte allerdings nur lesend erfolgen, um sie für verschiedene Analysen wiederverwenden zu können. Zur Darstellung und Analyse
empfiehlt sich die Verwendung eigener, interner Datenstrukturen, da aus den
gesammelten Daten beispielsweise zusätzliche Felder berechnet werden können. Außerdem können die Daten im Rahmen einer statistischen Auswertung hinsichtlich ihrer Qualität bewertet werden, indem zum Beispiel aus
mehreren Datensätzen (mit gleichen Parametern) der Mittelwert sowie die
Standardabweichung bestimmt wird. In diesem Fall werden viele Datensätze
durch einen einzigen ersetzt, der als zusätzliches Feld beispielsweise die Standardabweichung aufweist. Daher sollten derartige Operationen nicht auf den
Originaldaten erfolgen.
Zur Datendarstellung bietet sich die Erweiterung der Eclipse-Workbench
um zusätzliche Views [GB04] an. Derzeit existieren für die diversen Profilertypen tabellarische Darstellungsformen, die die Daten möglichst übersichtlich
anzeigen. Abbildung 6.8 zeigt beispielhaft die CPU-View.
Abbildung 6.8: Die CPU-View des Analyse-Framework, mit ausklappbaren
Detaildaten
6.2. TECHNISCHE UMSETZUNG
117
Links in der Tabelle ist zu erkennen, dass es sich bei den Aufzeichnungen
um die Methode sort() der Klasse Heapsort handelt, die mit verschiedenen
Parametern in mehrfachen Wiederholungen aufgerufen wurde. Neben Startzeit (start) und Ausführungsdauer (duration) ist die Anzahl der system
ticks, in der sich die Methode während der Datenerhebung auf der CPU
befand, dargestellt. Da zu diesem Zeitpunkt noch keine statistische Datenauswertung stattfand, entspricht der Mittelwert (mean) noch der Gesamtzahl
(total), und die Standardabweichung (deviation) ist Null. Die Tabellen
sind, um eine bessere Übersicht zu gewährleisten, ausklappbar (>>). Im nicht
sichtbaren Teil befinden sich Daten zur CPU Nutzung der Systemumgebung
sowie detaillierte Daten, die auf die einzelnen CPUs im System aufgeteilt
sind.
Erweiterung um zusätzliche Profiler
Werden weitere Profiler mit SystemTap implementiert, die beispielsweise andere Hardwarekomponenten überwachen oder verschiedene Kontextinformationen zur Verfügung stellen, so muss das Analyse-Framework um Komponenten, die die gesammelten Daten einlesen, darstellen und verarbeiten
können, erweiterbar sein. Die Komponenten, die für jeweils einen Profiler
zuständig sind, werden im Kontext des Analyse-Frameworks als Analyser
bezeichnet. Um die einfache Erweiterbarkeit zu garantieren, definiert das
Analyse-Framework einen Erweiterungspunkt (extension-point) mit zugehörigen Schema:
• Erweiterungspunkt:
org.coolsoftware.profiling.workbench.analysis.analyser
• Das Schema gibt die Struktur vor, die von der Erweiterung eingehalten
werden muss. Abbildung 6.9 zeigt den Aufbau des Erweiterungspunktes.
Jeder Nutzer des Erweiterungspunktes ProfilerAnalyser muss mindestens eine AnalyserController Erweiterung implementieren. Von besonderer
Bedeutung bei der Definition der Erweiterung ist das verpflichtende Attribut
type. Über dieses Attribut wird die Erweiterung eindeutig als die Komponente identifiziert, die für die Verarbeitung des, in der Ausgabe des Profilers
angegebenen Typs, geeignet ist. Das Analyse-Framework verwaltet alle Erweiterungen, die den definierten Erweiterungspunkt nutzen und wählt, auf
Grundlage der eingelesenen JSON-Datei, automatisch die passende aus.
Des Weiteren muss die Schnittstelle IAnalyserController von der Erweiterung implementiert werden. Sie definiert die Methode analyseJSON(),
118
KAPITEL 6. PROOF-OF-CONCEPT IMPLEMENTIERUNG
<<extension-point>>
ProfilerAnalyser
1..*
<<element>>
<<element>>
AnalyserController
AnalyserController
id:
id: string
string
type:
type: string
string
class:
class: implements
implements
<<interface>>
<<interface>>
IAnalyserController
IAnalyserController
Ausgabe des neuen Profiler:
{
"profiler_type":"cpu_start_flightbox",
"time":1297082491078821571,
"interval":2000
}
[...]
analyseJSON(JsonResource jres)
Abbildung 6.9: Schema des Erweiterungspunktes für Analyser
der als Parameter eine JsonResource (spezielle EMF-Ressource) übergeben
wird. Sobald die korrekte Erweiterung zur Verarbeitung der eingelesenen
JSON-Daten vom Analyse-Framework gefunden wurde, wird über diese Methode die Ressource übergeben. Ab diesem Zeitpunkt liegt die weitere Vorgehensweise bei der Erweiterung.
Auch die bestehenden Implementierungen CPU-, Netzwerk- und Festplattendaten verwenden den definierten Erweiterungspunkt. Sie sind nach dem
Model-View-Controller Muster implementiert und öffnen automatisch passende Eclipse-Views, sobald ihre analyseJSON() Methode aufgerufen wird.
Des Weiteren wurden die Apache Commons Mathematics Library 8 für
statistische Auswertungen sowie das Nebula Project 9 zur Erstellung der Tabellen als externe Bibliotheken in das Analyse-Framework integriert, so dass
auch sie von potenziellen Erweiterungen verwendet werden können.
8
9
http://commons.apache.org/math/
http://www.eclipse.org/nebula/
Kapitel 7
Evaluation
Tip 15 [HT99]
„Use Tracer Bullets to Find the Target “
Zur Ermöglichung eines aktiven Energiemanagements, wie es beispielsweise
die CoolSoftware-Architektur anstrebt, ist eine abstrakte Beschreibung des
Ressourcennutzungsverhaltens von Software hilfreich. Stehen den verwaltenden Autoritäten derartige Beschreibungen in kompakter und formaler Form
zur Verfügung, so können sie proaktiv handeln, anstatt nur auf die Gegebenheiten zu reagieren, um einen energieoptimalen Zustand des Gesamtsystems
herbeizuführen. Es war das Ziel dieser Arbeit, die Ressourcennutzung von
Java-Codeartefakten ermittelbar zu machen und eine abstrakte Verhaltensbeschreibung daraus abzuleiten, die auch für CCM Komponenten verwendet
werden kann.
Da in der Praxis keine zufriedenstellende Lösung bekannt ist, um die physische Ressourcennutzung einzelnen Java-Codeartefakten zuzuordnen, musste ein eigener Ansatz entwickelt werden. Zu diesem Zweck wurde das Jrupi
Framework eingeführt, dessen Aufgabe es ist, einen konzeptuellen Rahmen
vorzugeben, wie das formulierte Ziel erreicht werden kann.
Das Vorgehen bei der Proof-of-Concept Implementierung wurde nach einem Verfahren entwickelt, das in [HT99] als Tracer Bullets (Leuchtspurmunition) bezeichnet wird. Da sehr viele Variablen und Unbekannten in der Umgebung existieren, wurden zunächst viele alternative Werkzeuge und Implementierungen getestet, um zu sehen, mit welcher Kombination das Gesamtziel
erreichbar ist. Eine Entwicklung nach diesem Prinzip produziert keinen Code, der wieder verworfen und durch den Eigentlichen ersetzt wird (wie beim
Prototyp), sondern zeigt den Weg zur Lösung auf, die anschließend um die
verbliebenen Funktionen erweitert werden muss. Aufgrund der begrenzten
Bearbeitungszeit einerseits, und der Spannbreite des Themas andererseits,
119
120
KAPITEL 7. EVALUATION
erschien dieses Verfahren als angemessen. Die prinzipielle Umsetzbarkeit von
Jrupi ist durch die Proof-of-Concept Implementierung bereits gezeigt worden. Nun gilt es noch zu zeigen, dass, um in der Metapher zu bleiben, die
Tracer Bullets auch das Ziel getroffen hat.
7.1
Fallbeispiel: Sortieralgorithmen
Zur Evaluation der Anwendbarkeit wurde mit Sortieralgorithmen ein anschauliches Beispiel ausgewählt, das auch in der Praxis in dieser Form vorkommen könnte. Das Sortieren einer Liste von Elementen nach einen bestimmten Sortierkriterium ist ein grundlegendes algorithmisches Problem
und unterschiedliche Sortierverfahren sind daher gut erforscht. So ist typischerweise die Laufzeitkomplexität der einzelnen Algorithmen für den besten (Best-Case), den durchschnittlichen (Average-Case)und den schlechtesten Fall (Worst-Case) bekannt. Die Komplexität von Algorithmen wird häufig in der Landau-Notation O() angegeben, wobei O eine obere Komplexitätsgrenze (z.B. für Rechenzeit) beschreibt. Ist beispielsweise die Laufzeitkomplexität eines Sortieralgorithmus mit O(n2 ) angegeben, so bedeutet das
ausgeschrieben
O(g) mit g : n 7→ n2 bzw g(n) = n2
(7.1)
Wird nun die Anzahl der zu sortierenden Elemente verdoppelt, steigt die
Laufzeit annäherungsweise um ein Vierfaches [HT99]. Als Funktion ausgedrückt ergibt sich
g(2n) = (2n)2 = 22 n2 = 4n2
(7.2)
Die O-Notation ist jedoch mit Vorsicht zu genießen, da verschiedene Algorithmen üblicherweise in Klassen (z.B. O(1), O(n), O(n·log(n)), O(n2 ),...)
eingeteilt werden. Alle Terme niedrigerer Ordnung beziehungsweise Konstanten werden ignoriert, wie das folgende Beispiel zeigt [HT99].
O(
n2
n2
+ 3n) ⇔ O( ) ⇔ O(n2 )
2
2
(7.3)
Das bedeutet in der Konsequenz, dass bei zwei Algorithmen, die derselben
Klasse angehören, einer von beiden um ein Vielfaches schneller sein kann als
der andere. Daher erscheint die Verwendung von Sortieralgorithmen für die
Evaluation besonders geeignet, da sich die Ergebnisse, soweit es die CPU
betrifft, mit ihrer jeweiligen Komplexitätsklasse gegenprüfen lassen. Die gegebenen Komplexitäten stellen zwar lediglich grobe Abschätzungen dar, sollten sich aber dennoch im Ergebnis widerspiegeln. Zudem können an diesen
Beispiel folgende Eigenschaften gezeigt werden:
121
7.1. FALLBEISPIEL: SORTIERALGORITHMEN
• Sortieralgorithmen zeigen die Problematik auf, dass die Ressourcennutzung von den Eingabedaten (Methodenparametern) abhängig ist.
Damit ist indirekt auch der resultierende Energieverbrauch von den
Parametern abhängig.
• Da die Eingabedaten für Sortierverfahren aus einer Liste von Elementen besteht, wird die Notwendigkeit von Metadaten (wie Größe der
Liste oder durchschnittliche Länge der Zeichenkette) gezeigt, um eine
mathematische Beschreibung der Ressourcennutzung zu ermöglichen.
• Die Ausführung der Algorithmen belastet die CPU relativ stark und
die unterschiedlichen Laufzeitkomplexitäten lassen vermuten, dass sich
die automatische Ableitung einer mathematischen Formel mit Eureqa
als vorteilhaft erweist.
7.1.1
Kurzvorstellung der Algorithmen
Zur Demonstration des Profiling der CPU-Nutzung und der Datenauswertung wurden die drei bekannten Soriteralgorithmen Bubblesort, Quicksort
und Heapsort ausgewählt. Die folgende Tabelle zeigt eine Gegenüberstellung
der Komplexitäten der einzelnen Algorithmen [Lan03].
Algorithmus
Bubblesort
Quicksort
Heapsort
Worst-Case
O(n2 )
O(n2 )
O(n · log(n))
Average-Case
O(n2 )
O(n · log(n))
O(n · log(n))
Best-Case
O(n)
O(n · log(n))
O(n · log(n))
Bubblesort ist der einfachste der angegebenen Algorithmen und wird häufig als naives Negativbeispiel angeführt. Er durchläuft immer wieder das Array und vertauscht direkt benachbarte Elemente (auf Grundlage eines Vergleichs), wenn nötig. Der Algorithmus ist abgearbeitet, wenn in einem Durchlauf kein Vertauschen mehr notwendig ist. Daher kommt für den besten Fall,
der genau dann eintritt, wenn die Liste bereits sortiert ist, eine Komplexität
von O(n) zustande. Für alle anderen Fälle gilt O(n2 ).
Quicksort gilt als eines der schnellsten Sortierverfahren und arbeitet nach
dem Divide-and-Conquer-Prinzip. Die zu sortierende Folge wird dabei in zwei
Teilstücke zerlegt (divide), die durch ein Vergleichselement X getrennt werden. Alle Werte kleiner X kommen in das erste, alle Elemente größer X in
das zweite Teilstück. Daraufhin wird dasselbe Verfahren rekursiv auf die beiden Teilstücke angewandt. Werden die Teilstücke wieder zusammengesetzt,
ergibt sich die sortierte Folge. Im schlechtesten Fall besitzt Quicksort die
122
KAPITEL 7. EVALUATION
Komplexität O(n2 ). Dieser Fall ist aber sehr unwahrscheinlich und tritt nur
dann ein, wenn ein Teilstück stets nur ein Element besitzt [Lan03].
Der letzte Testkandidat ist Heapsort, der als Datenstruktur einen binären
Baum (den Heap) verwendet, dessen Knoten die zu sortierenden Elemente
repräsentieren. Die Sortierung erfolgt durch eine Umordnung der Knotenmarken. Das genaue Verfahren kann beispielsweise in [Lan03] nachgelesen
werden. Da die Verwaltung des Heaps relativ aufwändig ist, gilt Heapsort im
durchschnittlichen Fall als langsamer im Vergleich zu Quicksort. Der Algorithmus garantiert in jedem Fall O(n · log(n)).
Bubblesort tritt aufgrund seiner quadratischen Laufzeit außer Konkurrenz
an. Interessanter ist ein direkter Vergleich zwischen Quick- und Heapsort
bezüglich ihrer CPU-Nutzung. In Bezug auf die durchschnittliche Laufzeit
gilt Quicksort als einer der schnellsten Algorithmen (auch im Vergleich zu
Heapsort).
7.1.2
Setup der Testumgebung
Ein vorstellbarer Anwendungsfall im Zusammenhang mit CoolSoftware wäre die Definition einer Sortierkomponente. Als Varianten könnten dann beispielsweise Implementierungen von Bubblesort, Quicksort und Heapsort zur
Verfügung stehen. Die Komponente bietet den Port sort() an, der ein Array
von Elementen, die untereinander vergleichbar1 sind, entgegen nimmt, und
das sortierte Array zurück gibt. Aufgrund der vielen Gemeinsamkeiten zwischen Komponenten und Objektorientierung, kann der Port für das Profiling
durch eine normale Methode implementiert werden. Jede Implementierungsvariante wird durch eine eigenen Klasse repräsentiert, die das Interface Sort
implementiert. In Abbildung 7.1 ist der Aufbau der Testumgebung als Klassendiagramm dargestellt.
Das Skelett der Testumgebung wurde zunächst mit den Codegeneratoren
der Jrupi-Workbench (vergleiche Unterabschnitt 6.2.3) erstellt. Dabei wurden die Klassen AbstractSUTRunner, SUTRunner und Main generiert. Das
Interface Sort sowie die implementierenden Klasse BubbleSort, QuickSort
und HeapSort stehen stellvertretend für die Komponente und ihre Varianten.
Sie können daher als gegeben angesehen werden und sind der Gegenstand der
Untersuchung. Der Einfachheit halber wurde in diesem Fall die Parameterliste der sort() Methode direkt um die, mutmaßlich nützlichen, Metadaten
(arraySize und meanLength) erweitert. Die Klasse Person stellt den Sortiergegenstand dar, mit dem Attribut name als Sortierkriterium.
Die in Abschnitt 6.2 beschriebenen BTrace-Instrumentierungen wurden
1
in Java durch Implementierung des Interface Comparable
123
7.1. FALLBEISPIEL: SORTIERALGORITHMEN
AbstractSUTRunner
AbstractSUTRunner
runSUT()
Main
Main
setUp()
warmUp()
run()
runSUT()
*
Person
Person
name:String
SUTRunner
SUTRunner
setUp()
warmUp()
run()
<<interface>>
<<interface>>
Sort
Sort
sort(in:T[], arraySize:int , meanLength:int):T[]
BubbleSort
BubbleSort
QuickSort
QuickSort
HeapSort
HeapSort
Abbildung 7.1: Profiling-Setup für Sortieralgorithmen
ebenfalls durch die Codegenerierung der Jrupi-Workbench vollständig automatisch erstellt (vergleiche Anhang B.2). Sie wurden für die sort() Methode
jeder einzelnen Klasse der Sortieralgorithmen (allerdings im selben BTraceSkript) generiert. Zur Instrumentierung sollten immer die konkreten Klassen und nicht die Schnittstelle verwendet werden, da nur so eine eindeutige
Identifikation und Zuordnung (im Nachhinein, die Datensammlung ist nicht
betroffen) möglich ist. Es existiert allerdings noch ein weiterer Grund, der
in Unterabschnitt 4.2.4 beim Thema Benchmarking bereits erläutert wurde.
Zur Datenerhebung, vor allem in Bezug auf CPU-Daten, sollten die Methoden nicht über eine Schnittstelle aufgerufen werden, um Probleme mit dem
Inlining der JVM zu vermeiden. Daher ruft (vergleiche Abbildung 7.1) der
SUTRunner die Methoden direkt in den jeweiligen Klassen auf, anstatt die
Schnittstelle zu verwenden. Allgemein sollten die erläuterten Hinweise zur
Erstellung von Benchmarks und die Arbeitsweise der JVM beachtet werden
(vergleiche Unterabschnitte 4.1.4 und 4.2.4). Das ist vor allem beim CPUProfiling wichtig, da die Optimierungen bei der JIT-Kompilierung größtenteils darauf abzielen.
124
KAPITEL 7. EVALUATION
Verwendete Testdaten
Für Sortieralgorithmen ist bekannt, dass die Art und der Umfang der verwendeten Testdaten Einfluss auf die Laufzeit hat. Der Einfluss der Größe
der zu sortierenden Datenliste ist direkt in der Komplexitätsbeschreibung
ablesbar. Interessant ist beim CPU-Profiling, ob eine direkte Korrelation zur
CPU-Nutzung feststellbar ist. Weiterhin ist bekannt, dass, abhängig von der
Arbeitsweise des Sortierverfahrens, die Vorsortierung der Daten Einfluss auf
die Komplexität des Soriteralgorithmus (z.B. Bubblesort Best-Case) haben
kann. Dieser Umstand wird nicht betrachtet, da er keine allgemeine Aussage über die Qualität eines Verfahrens erlaubt. Die Testdaten werden nach
folgenden Verfahren erzeugt:
• Arrays unterschiedlicher Größe, die Objekte der Klasse Person halten.
• Für jede Person wird ein zufälliger String generiert, der das Attribut
name als Sortierkriterium vorgibt, und aus zufälligen Zeichen des Alphabets besteht.
• Der zufällig generierte String hat innerhalb eines Arrays immer dieselbe
Länge. So kann der Parameter der durchschnittlichen Länge direkt auf
seinen Einfluss untersucht werden.
Mit den generierten Testdaten sollten folgende Fragen bei der Evaluation
beantwortet werden können:
• Welche Parameter haben wie viel Einfluss auf die CPU-Nutzung?
• Wie unterscheiden sich die Verfahren bei der Verwendung der CPU?
• Existiert eine Korrelation zwischen CPU-Nutzung und Ausführungszeit
der Methode?
• Spiegeln sich die Komplexitäten in der CPU-Nutzung wieder?
Das Hauptziel besteht darin, die Anwendbarkeit der Proof-of-Concept Implementierung von Jrupi zu demonstrieren. Das umfasst einerseits die Erhebung der notwendigen Daten und andererseits die Ableitung eine mathematischen Formel, um die CPU-Nutzung der Methoden (respektive Ports) zu
charakterisieren.
125
7.1. FALLBEISPIEL: SORTIERALGORITHMEN
7.1.3
Auswertung der Ergebnisse
Da die Laufzeitkomplexitäten der Algorithmen durchaus Unterschiede aufweisen, wurde zunächst ein Testlauf mit relativ kleinen Arrays gefahren, um
die einzelnen Verfahren einzuschätzen. Vor allem die quadratische Komplexität von Bubblesort lässt auf ein sehr schnelles Ansteigen der Laufzeit schließen. Als Testdaten kamen Arrays der Größe 10.000 bis 40.000 mit der Schrittweite 10.000 zum Einsatz. Die Länge der Strings betrug fünf zufällige Zeichen
und die Ausführung wurde fünf mal pro Arraygröße wiederholt. Im folgenden
sind die Ergebnisse in tabellarischer Form dargestellt:
Anzahl (n) Bubblesort
10.000
2.138
20.000
12.433
30.000
44.828
40.000
70.757
Quicksort
5
14
19
25
Heapsort
7
24
27
61
CPU-Nutzung der Algorithmen (system ticks auf CPU)
Die in der Tabelle dargestellten Ergebnisse mögen auf den ersten Blick überraschen, sind aber korrekt und durch mehrfache Messungen überprüft. Damit
werden gleich mehrere Probleme deutlich:
• Das quadratische Wachstum der Ausführungszeit von Bubblesort ist ein
echtes Problem, das dazu führt, dass er nicht ohne Weiteres mit denselben Testdaten wie die anderen beiden Algorithmen arbeiten kann.
So benötigte er für 40.000 Elemente im schlimmsten Fall über 80 Sekunden. Zum Vergleich: Heap- beziehungsweise Quicksort schafften eine Million Elemente durchschnittlich in knapp über 3 beziehungsweise
knapp unter 2 Sekunden.
• Die Werte für Heap- und Quicksort sind sehr klein, so dass mit ihnen
noch keine vernünftige Aussage möglich ist. Dieses Problem ist noch
offensichtlicher, wenn die Ausführungszeiten noch kleinerer Arrays betrachtet werden:
Anzahl (n) Quicksort Heapsort
500
230.000
250.000
1.000
450.000
520.000
Ausführungsdauer in Nanosekunden
126
KAPITEL 7. EVALUATION
Die Zeit, die Quicksort zum sortieren von 500 Einträgen benötigt, kann
wie folgt in Millisekunden und Sekunden umgerechnet werden.
1ns = 10−6 ms ⇒ 230000ns = 0, 23ms = 0, 00023s
(7.4)
Die Problematik besteht bei solch kurzen Laufzeiten darin, dass der TimerInterrupt, der für das Profiling verwendet wird, nicht in einem so hohen Takt
aufgerufen wird (typischerweise wird er zwischen 500 und 2.000 mal pro Sekunde aufgerufen, abhängig von der CPU-Architektur). Daher sind derartig
geringe Laufzeiten nicht für ein Sampling geeignet. Es kann in diesem Fall davon ausgegangen werden, dass die Methode ihre komplette Ausführungszeit
auch tatsächlich auf der CPU gewesen ist. Daher sollte besser die Ausführungszeit der Methode als Metrik verwendet werden.
Einfluss der durchschnittlichen Länge
Der Einfluss der durchschnittlichen Länge der Strings stellte sich als vernachlässigbar heraus, wie es Abbildung 7.2 zeigt.
Heapsort
Quicksort
Abbildung 7.2: Einfluss der Länge der zu sortierenden Strings auf die CPU
Nutzung bei Heap- und Quicksort
7.1. FALLBEISPIEL: SORTIERALGORITHMEN
127
Zum Test wurden Arrays der Größe 500.000 mit einer durchschnittlichen
Länge von 5 bis 30 Zeichen pro String (Schrittweite 5) bei zehnfacher Wiederholung getestet. Zunächst ist ersichtlich, dass Quicksort sehr viel geringere
CPU-Belastung verursacht als Heapsort. Das deckt sich mit den Beobachtungen, dass Quicksort im Durchschnitt auch schneller sortiert als Heapsort.
Generell lässt sich mit steigender Länge der Zeichenkette leichter Anstieg
feststellen, der aber bei Betrachtung der gesamten CPU-Nutzung vernachlässigbar ist. Offensichtlich sind die lexikographischen Vergleiche keine teuren
Operationen und dieser Parameter (eigentlich Metadatum) kann eliminiert
werden.
CPU-Nutzung: Quicksort vs Heapsort
Da der Einfluss der Länge der Zeichenkette als vernachlässigbar eingestuft
werden konnte, ist nur noch der Parameter Größe des Arrays übrig. Zur
Ermittlung der CPU-Nutzung musste jeder der beiden Algorithmen folgende
Aufgabe lösen:
• Sortierung von 10 Arraygrößen in den Größenordnung von 100.000 bis
1.000.000 (Schrittweite = 100.000).
• Pro Schritt 10 Wiederholungen, mit 10 unterschiedlichen und zufällig
erzeugten Arrays der gleichen Größe.
• Die Länge der Strings (Sortierkriterium) betrug einheitlich 5, da ihr
Einfluss vernachlässigbar ist.
Die Ergebnisse zur CPU-Nutzung sind in Abbildung 7.3 dargestellt. Das
Diagramm zeigt die CPU-Nutzung der Methode in der Einheit system ticks
auf der y-Achse und die Anzahl der zu sortierenden Elemente auf der x-Achse.
Zunächst lässt sich erkennen, dass der Parameter Arraygröße, wie erwartet,
maßgeblichen Einfluss auf die CPU-Nutzung hat.
Auch in diesem Test wird Heapsort von Quicksort bezüglich der Beanspruchung der CPU geschlagen. Da zufällige Arrays mit mehrfacher Wiederholung
der Sortierung verwendet und daraus ein Durchschnitt ermittelt wurde, kann
davon ausgegangen werden, dass es sich um das durchschnittliche Ergebnis
der Algorithmen handelt. Damit wird auch die Ungenauigkeit der LandauNotation verdeutlicht, die für beide Algorithmen im durchschnittlichen Fall
O(n · log(n)) angibt. Das lässt sich durch das Fehlen von Konstanten und
Termen niederer Ordnung erklären. Der erhöhte Aufwand zur Verwaltung
des Baumes bei Heapsort wird damit beispielsweise nicht erfasst. Als Ergebnis kann festgehalten werden, dass Quicksort im Durchschnitt bezüglich
CPU-Nutzung der „grünere“ der beiden Algorithmen ist.
128
KAPITEL 7. EVALUATION
40 0 0
350 0
Heapsort
Methode CPU (system ticks)
300 0
25 0 0
20 0 0
Quicksort
150 0
100 0
5 00
0
0
100 00 0
2 00 00 0
300 00 0
40 00 00
50 00 00
60 00 00
700 0 00
8 00 0 00
9 00 00 0
100 00 00
Anzahl zu sortierender Elemente
Abbildung 7.3: CPU-Nutzung von Heap- und Quicksort in Abhängigkeit der
Arraygröße
Ein weiteres Ergebnis ist eine direkte Korrelation zwischen CPU-Nutzung
und Ausführungszeit. Auf dem Testsystem unterschieden sich die Werte zwischen Ausführungszeit in Millisekunden und CPU-Nutzung in system ticks
(auf CPU) nur geringfügig. Das lässt sich zum Einen durch das Verfahren der
Datenerhebung des CPU-Profilers erklären, das bei jeden Timer-Interrupt
überprüft, welcher Prozess oder Thread gerade auf der CPU ist. Offensichtlich wird dieser Interrupt auf dem Testsystem etwa jede Millisekunde ausgelöst. Zum Anderen ist ein Grund in der kontrollierten Ausführung zu sehen. Da kaum andere Aktivität im System herrschte, musste der Scheduler
nicht eingreifen und die Methode konnte einen CPU-Kern für ihre komplette Ausführung nutzen. In diesem Fall ist die Ausführungszeit also durchaus
ein brauchbarer Indikator für die CPU-Nutzung. Müssen Daten allerdings
an einem Produktivsystem, auf dem typischerweise viele Prozesse um die
CPU konkurrieren, erhoben werden, so spiegelt die Ausführungszeit nicht die
tatsächliche Ressourcennutzung wieder, da die Methode mehr oder minder
häufig in der Ausführung unterbrochen wird. Ebenfalls ermöglicht der CPUProfiler eine genaue Zuordnung der Belastung einzelner CPUs oder Kerne
sowie die Ermittlung von context switches. All diese Aussagen lassen sich mit
7.1. FALLBEISPIEL: SORTIERALGORITHMEN
129
der Ausführungszeit nicht treffen.
Ergebnisse Eureqa
Die erhobenen Daten wurden zunächst mit dem Analyse-Framework gesichtet und statistisch ausgewertet. Das Ergebnis stellte Abbildung 7.3 dar. Anschließend muss eine geeignete Funktion gefunden werden, die das Verhältnis zwischen Ressourcennutzung und Parameter annähert. Zu diesem Zweck
wurden die Daten exportiert und es kam Eureqa zur automatischen Ableitung des Zusammenhangs zum Einsatz. Gesucht war eine Funkion f , die das
Verhältnis folgendermaßen beschreibt
cputicks = f (n)
(7.5)
Dabei bezeichnet n die Größe des Arrays. Zur Konstruktion der Formel durfte Eureqa die Komponente Addition, Division, Multiplikation, Exponent,
Logarithmus und Quadratwurzel verwenden. Da der Zusammenhang relativ
einfach ist, wurden die Formeln innerhalb kurzer Zeit ermittelt. Von ihnen
wurden diejeneigen ausgewählt, die den besten Kompromiss zwischen Genauigkeit und Komplexität bieten. Das Ergebnis für Quicksort lautete
cputicks = f (n) = 14n + 40n · log(4n)
(7.6)
Für Heapsort ergab sich die folgende Abhängigkeit
cputicks = f (n) = 119n · log(2n)
(7.7)
Dabei fällt direkt auf, dass sich die durchschnittlichen Komplexitäten
(O(n·log(n)) der Algorithmen in den beiden Funktionen wiederfinden. Allerdings sind in diesem Fall Konstanten vorhanden, die bei der Landau-Notation
weggelassen würden. Wie der beachtliche Unterschied in der CPU-Nutzung
der beiden Algorithmen jedoch verdeutlichte, dürften diese Konstanten auch
Einfluss auf den Energieverbrauch haben. Es reicht daher nicht aus lediglich
die Komplexitätsklasse eines Algorithmus als Formel heranzuziehen.
Festplatten-Nutzung bei Speicherung des Ergebnis
Zur Demonstration des Profilings der Festplattenaktivität wird ebenfalls das
Beispiel der Sortieralgorithmen verwendet, indem das Ergebnis (sortiertes
Arrays) im Anschluss an die Sortierung in eine Datei gespeichert wird. Damit
kann gezeigt werden, dass die durchschnittliche Länge der Zeichenketten, im
Gegensatz zur CPU-Nutzung, in diesem Fall einen erheblichen Einfluss auf
130
KAPITEL 7. EVALUATION
die Ressourcennutzung hat. Das verwendete Sortierverfahren hingegen ist
zunächst irrelevant. Zum Profiling kamen wieder Arrays der Größe 100.000
bis 1.000.0000 (Schrittweite 100.000) und Zeichenketten der Länge 5 bis 20
(Schrittweite 5) zum Einsatz. In Abbildung 7.4 ist das Ergebnis des Profilings
dargestellt.
25 00 00 00
Bytes geschrieben
20 00 00 00
15 00 00 00
Län ge
Län ge
Län ge
Län ge
10 00 00 00
5
10
15
20
5 00 00 00
0
10 000 0
2 00 0 00
30 00 00
40 00 00
50 0 00 0
6 00 00 0
7 00 00 0
800 0 00
90 00 00
100 00 00
Anzahl Elemente im Array
Abbildung 7.4: Vergleich der Festplattennutzung bei Speicherung verschiedener Arrays
Das Beispiel der Speicherung von sortierten Arrays in eine Datei ist relativ
simpel. Wie zu erwarten verhält sich die Anzahl geschriebener Bytes linear
zu Arraygröße und durchschnittlicher Länge der Zeichenketten. Allerdings
besitzt die Funktion zur Beschreibung des Zusammenhangs zwei abhängige
Parameter. Gesucht ist damit folgende Funktion:
id
diskbytes
= f (n, l)
(7.8)
Dabei gibt n die Größe des Arrays und l die durchschnittliche Zeichenkettenlänge an. Als Einheit dienen Bytes und aus den aufgezeichneten Profilerdaten ist bekannt, dass auf Laufwerk sda geschrieben wurde. Auch in
diesem Fall können die Daten in Eureqa eingelesen und die gesuchte Funktion angegeben werden. Die Verwendung mehrerer Funktionsparameter stellt
dabei kein Problem dar. Eureqa lieferte das folgende Ergebnis:
sda
diskbytes
= f (n, l) = 1 + n · l + n
(7.9)
Dieser Zusammenhang ist relativ einfach und setzt nicht zwangsweise den
Einsatz von Eureqa voraus, um die angegebene Funktion zu ermitteln. Da
7.1. FALLBEISPIEL: SORTIERALGORITHMEN
131
die einzelnen Zeichenketten in eine Textdatei geschrieben wurden, kann davon ausgegangen werden, dass pro Zeichen ein Byte benötigt wird. Dadurch
kommt der Term n · l in die Gleichung. Hinzu kommt ein Zeilenumbruch oder
Trennzeichen pro Element (Addition von n). Bei komplexeren Interaktionen
sind diese Zusammenhänge allerdings nicht ohne Weiteres erkennbar und die
Komplexität der Funktionen steigt. In diesem Fall muss bei der Formelsuche
mit Eureqa eine Abwägung zwischen Fehler der Annäherung und Komplexität der Lösung erfolgen. Dieses Beispiel sollte lediglich anschaulich verdeutlichen, dass die physische Ressourcennutzung der zu Erwartenden entspricht.
Der zeitliche Aspekt der Ressourcennutzung
Wurde beim Profiling festgestellt, dass eine Methode hohe Festplattennutzung verursacht, so ist unter Umständen der zeitliche Faktor der tatsächlichen Nutzung von Bedeutung. Dieser Umstand wird in den Standardprofilern nicht berücksichtigt, da sie kumulativ arbeiten, um eine möglichst geringe Menge an Daten zu erzeugen. Sie summieren die gesamte Nutzung
der Hardwarekomponenten für eine Methode auf und speichern lediglich das
Ergebnis zur Ausgabe. Dieses Vorgehen ist für gewöhnlich ausreichend, um
eine gute Vorhersage zu ermöglichen. Allerdings ist es, gerade bei langlaufenden Methoden, denkbar, die physische Ressourcennutzung zeitlich innerhalb
der Ausführungsdauer zu ermitteln. Zu diesem Zweck wurden zwei weitere SystemTap-Skripte für Netzwerk- und Festplattennutzung von Methoden
unter Berücksichtigung der zeitlichen Einordnung der physischen Zugriffe
entwickelt.
Die Problematik besteht darin, dass sehr viele Daten aufgezeichnet werden müssen, da die Kernelfunktionen in den Subsystemen, die zur eigentlichen
Ressourcennutzung führen, meist sehr häufig mit sehr geringen Ausführungszeiten aufgerufen werden. Beispielsweise wird beim schreibenden Zugriff auf
die Festplatte jeweils für das Schreiben einer Blockgröße (8192 Bytes) ein
Aufruf im virtuellen Dateisystem (VFS) durchgeführt. Beim Speichern eines
Arrays mit 1.000.000 Elementen (a 20 Zeichen) erfolgten 2566 Aufrufe der
write() Methode des VFS mit einer durchschnittlichen Ausführungsdauer
von etwa 15.000 Nanosekunden. In Abbildung 7.5 ist die Methodenausführung in ihren zeitlichen Ablauf und der Anteil, der auf Festplattenaktivität
entfiel, dargestellt.
In diesem Beispiel lassen sich Festplattenaktivität und restliche Methodenausführung klar voneinander trennen, da zunächst sortiert, und anschließend geschrieben wird. Es fällt auf, dass das Schreiben der Daten auf die
Festplatte einen relativ kleinen Teil der Gesamtzeitausführungszeit der Methode ausmacht. Der überwiegende Teil wird für die Sortierung des Arrays
132
KAPITEL 7. EVALUATION
Ende der
Methodenausführung
9 00 0
Byt es pro VFS-Aufruf
8 00 0
2566 Aufrufe
ca. 200ms
keine Festplattenaktivität
(Sortierung)
7 00 0
6 00 0
5 00 0
4 00 0
30 00
2 00 0
10 00
0
0
100 0
2 00 0
3000
40 00
5 00 0
6 00 0
Zeit (ms)
Abbildung 7.5: Zeitlicher Verlauf der Methodenausführung (sortieren und
speichern)
(zeitlich gesehen spielt daher das verwendete Sortierverfahren eine Rolle) benötigt. Diese Betrachtungen sind sehr detailliert und es sollte geprüft werden,
ob sich daraus brauchbare Aussagen ableiten lassen.
Soll die zeitliche Komponente in die Beschreibung aufgenommen werden,
so ist es um so wichtiger, die Vorgaben des Benchmarking zu beachten (vergleiche Unterabschnitt 4.2.4), da sonst die Messergebnisse verfälscht werden.
Eine derartige Beschreibung kann, im Gegensatz zur kumulativen Angabe,
nicht über eine simple Funktion erfolgen. Als mathematische Beschreibung
bieten sich beispielsweise Differentialgleichungen an, die sich auch mit Eureqa bestimmen lassen. Allerdings sollte sehr genau abgewogen werden, ob sich
dieser Aufwand lohnt.
• Die zu sammelnde Datenmenge wird um ein Vielfaches erhöht, da pro
Methode alle Zeiten aller Hardwarezugriffe (eventuell mit Werten) protokolliert werden müssen. In der Regel sind das sehr viele und sehr
kurze Aufrufe in den Subsystemen.
• Je mehr Daten gesammelt werden, desto schwieriger gestaltet sich ihre
Auswertung.
• Je komplexer die gesammelten Daten sind, umso schwieriger lassen sich
die benötigten Zusammenhänge herstellen.
• Der Zeitfaktor benötigt für eine formale Beschreibung eine Differentialgleichungen, deren Ermittlung, falls überhaupt möglich, aufwändig
ist.
7.2. FLEXIBILITÄT DER DATENERHEBUNG
133
• Die zeitliche Verteilung ist im Allgemeinen sporadischer als im gegebenen Beispiel.
In diese Arbeit konnte die Gewinnung einer Beschreibung, die die zeitliche Komponente auf Methodenebene berücksichtigt, nicht mehr aufgenommen werden. Zukünftige Arbeiten könnten zunächst prüfen, ob dieser Aspekt
sinnvoll und mit vertretbaren Aufwand in eine Beschreibung einfließen kann.
Die Möglichkeiten zur Datenerhebung bestehen bereits.
7.2
Flexibilität der Datenerhebung
Der vorangegangene Abschnitt hat gezeigt, wie die Jrupi Implementierung
anwendbar ist, um Daten für verschiedene Implementierungsvarianten zu
sammeln und daraus eine abstrakte Beschreibung der Ressourcennutzung
in Form einer mathematischen Formel abzuleiten. Der Ausgangspunkt war
das Aufsetzen einiger Microbenchmarks, die Generierung der BTrace-Instrumentierungen durch die Workbench sowie eine kontrollierte Ausführung
mit anschließender Datensammlung und Auswertung.
Doch die Implementierung kann sehr viel mehr leisten. Es wurde bereits
mehrfach erwähnt, dass alle Komponenten für den Einsatz am Produktivsystem ausgelegt sind. Darüber hinaus ist es eine wesentliche Stärke der Proofof-Concept Implementierung von Jrupi, dass sie auf dynamische Manipulation des Bytecodes mit BTrace setzt. Dieser Umstand erlaubt es, Daten am
laufenden Produktivsystem, bei gleichzeitig laufender Anwendung zu erheben. Doch mit BTrace ist noch mehr möglich. Während die Anwendung läuft
kann Tracing-Code eingebracht werden, um beispielsweise für das Profiling
interessante Methoden in der Anwendung aufzuspüren. Wurden die Methoden von Interesse identifiziert, so können die Parametertypen der Methode
erfragt werden. Das alles, ohne dass jemals der Quell- oder Bytecode der
Anwendung bekannt sein muss und ohne jemals die laufende Anwendung am
Produktivsystem unterbrochen zu haben. Sind die Methoden identifiziert und
ihre relevanten Parameter bekannt, so können die bereits bekannten BTraceInstrumentierungen geschrieben werden, die die Verbindung zu den low-level
Profilern herstellen. Der Instrumentationcode wird daraufhin in die (immer
noch laufende) Anwendung injiziert und die Profiler wie gewohnt gestartet.
Dieser Aspekt der Implementierung soll nun anhand eines praktischen
Beispiels erläutert werden. Da bisher die Netzwerk-Profiler noch nicht zum
Einsatz kamen, besteht das Ziel im Auffinden einer speziellen Methode und
der Erhebung ihrer Netzwerknutzungsdaten, ohne die Anwendung dazu stoppen zu müssen. Als laufende Anwendung am Produktivsystem bietet sich die
134
KAPITEL 7. EVALUATION
Eclipse-IDE an, die größtenteils in Java implementiert ist. Um den Suchbereich ein wenig einzugrenzen, soll eine Methode des Versionskontrollsystems
SVN auf von ihr verursachten Netzwerktraffic untersucht werden.
Profiling in Eclipse
Bei dieser Demonstration handelt es sich tatsächlich um ein Produktivsystem, da die Zielanwendung die laufende Eclipse-IDE ist, die auch zur Entwicklung und Verwaltung der Implementierungen in dieser Arbeit verwendet
wird. Das Ziel besteht darin, zunächst die Methode zu finden, die für das
commit Kommando bei SVN zuständig ist, und anschließend die genau Menge an Netzwerktraffic einzelner Aufrufe zu ermitteln.
Der verwendete SVN-Connector lässt sich in der Liste installierter Plugins
nachschauen. Bei der vorliegenden Eclipse-IDE ist es SVNKit von Polarion2 .
Die relevanten Packages können entweder in der jar-Datei ermittelt oder aus
der Plugin-Id „geraten“ werden. Da BTrace zur Definition der Prüfpunkte
reguläre Ausdrücke zulässt, muss die genaue Bezeichnung nicht bekannt sein.
Es ist auch nicht erforderlich, den Klassen oder Methodennamen zu kennen.
Allerdings sollte bei großen Anwendungen darauf geachtet werden, dass der
Overhead durch Instrumentierung stark zunimmt, wenn ohne Bedacht sehr
viele Prüfpunkte aktiviert werden. Daher sollte die Auswahl, vor allem bei
laufzeitkritischen Produktivsystemen, so weit wie möglich eingeschränkt werden. Im konkreten Fall des SVN-Connector Plugins ist die Aktivierung vieler
Prüfpunkte allerdings verschmerzbar. In Listing 7.1 ist die Definition des
Prüfpunktes zu sehen.
1
// Instumentiere alle Klassen und alle Methoden im Package
@ O n M e t h o d ( clazz = " / org \\. p o l a r i o n \\. team \\. svn \\. c o n n e c t o r \\..+/ " ,
method = " /.*/ " )
public static void allSVN ( @ P r o b eM e t h o d N a m e String pmn ,
@ Pr ob eC l as sN am e String pcn ) {
// gib Klassennamen und Methodennamen aus
println ( Strings . strcat ( Strings . strcat ( pcn , " . " ) , pmn ) ) ;
}
2
3
4
5
6
7
8
9
Listing 7.1: BTrace Instumentierung für alle Methoden aller Klassen des SVN
Connectors
Der Prüfpunkt gibt an, dass alle Klassen und alle Methoden im Package org.polarion.team.svn.connector instrumentiert, und Ausgaben zum
Tracing der Klassen- (pcn) und Methodennamen (pmn) erfolgen sollen.
Da sich die Eclipse-IDE in Ausführung befindet, kann der Aufruf der
gesuchten Methode provoziert werden. Dazu wurde die Integration von SVN
2
http://www.polarion.com/products/svn/
7.2. FLEXIBILITÄT DER DATENERHEBUNG
135
in die Eclipse-GUI genutzt, um die geänderte Version eines Projektes auf
den SVN-Server von CoolSoftware zu laden (Commit). Das erzeugt zunächst
eine große Menge Ausgaben durch BTrace, da alle Methoden aller Klassen
im Package instrumentiert werden. Unter diesen Ausgaben befindet sich auch
die gesuchte commit() Methode, wie in Listing 7.2 dargestellt.
1
2
3
4
5
6
7
[...]
org . polarion . team . svn . connector . svnkit . S V N K i t C o n n e c t o r F a c t o r y . getId
org . polarion . team . svn . connector . svnkit . S V N K i t C o n n e c t o r F a c t o r y .
getSupportedFeatures
org . polarion . team . svn . connector . svnkit . S VN Ki tC o nn ec to r . commit
org . polarion . team . svn . connector . svnkit .
S V N K i t C o n n e c t o r $ P r o g r e s s M o n i t o r W r a p p e r . < init >
org . polarion . team . svn . connector . svnkit .
S V N K i t C o n n e c t o r $ P r o g r e s s M o n i t o r W r a p p e r . start
[...]
Listing 7.2: Ausgabe auf Konsolen
Damit ist die gesuchte Methode gefunden und alle Informationen vorhanden, um den Prüfpunkt für das Profiling zu definieren. Die Definition der
Instrumentierung erfolgt nach dem in 6.2.1 vorgestellten Schema und ist in
Listing 7.3 dargestellt.
1
2
3
4
5
@OnMethod (
clazz = " org . p o l a r i o n . team . svn . c o n n e c t o r . svnkit . S V N K i t C o n n e c t o r " ,
method = " commit " )
public static void te s tC om mi t En tr y ( AnyType [] args ) {
6
System . out . println ( " org . p o l a r i o n . team . svn . c o n n e c t o r . svnkit :
S V N K i t C o n n e c t o r : commit :0: m e t h o d _ e n t r : " ) ;
7
8
}
9
10
11
12
13
14
@OnMethod (
clazz = " org . p o l a r i o n . team . svn . c o n n e c t o r . svnkit . S V N K i t C o n n e c t o r " ,
method = " commit " ,
location = @ L o c a t i o n ( value = Kind . RETURN ) )
public static void t estCommi tExit ( AnyType [] args , @ P r o b e Me t h o d N a m e
String pmn , @ Pr ob e Cl as sN am e String pcn ) {
15
System . out . println ( " org . p o l a r i o n . team . svn . c o n n e c t o r . svnkit :
S V N K i t C o n n e c t o r : commit :0: m e t h o d _ e x i t : " ) ;
16
17
}
Listing 7.3: Prüfpunkte für commit() des SVNConnector
Die Instrumentierungen können nun durch BTrace in die (immer noch
laufende) Eclipse-IDE geladen werden. Gleichzeitig werden die relevanten
Profiler (in diesem Fall der Netzwerk) mit der Prozess-ID des Eclipse Prozesses als Parameter gestartet. Wird die Ausführung der Methode erneut
angestoßen, so zeichnet der Netzwerk-Profiler die exakte Menge (Bytes) an
Netzwerktraffic, die durch die Ausführung der Methode verursacht wurden,
136
KAPITEL 7. EVALUATION
auf.
Die Ermittlung der Daten in dieser Form am Produktivsystem hat den
Nachteil, dass keine kontrollierte Ausführung (wie im CPU Beispiel) stattfindet. Daher lässt sich nur bedingt Einfluss auf die Eingabedaten ausüben.
Allerdings kann BTrace dazu verwendet werden, eine zusätzliche Analyse der
Parameter durchzuführen. Über das bereits bekannte Array AnyType[] kann
der Zugriff auf alle Parameter erfolgen. Sind die Parameter nicht numerischer
Natur, so kann beispielsweise durch zusätzliche Ausgaben in einen separaten Skript der Typ der Parameter ermittelt werden. Ist der Typ ein Objekt,
so kann in der Instrumentationsbeschreibung durch einen Typecast bei der
Ausführung eine Referenz auf das Objekt erlangt werden, um weitere Daten
daraus zu erhalten.
Jedoch wurde dieses Beispiel gewählt, um die Grenzen des verwendeten
Verfahrens aufzuzeigen. Zwar lassen sich mit Hilfe des Netzwerk-Profilers
genaue Daten zur Ressourcennutzung erheben und mit BTrace die Parameter der Methode (und auch Metadaten darüber) ermitteln. Letztere stehen
aber in diesem Fall in keinen ersichtlichen Zusammenhang zur tatsächlichen
Ressourcennutzung. Der commit() Methode wird ein Array von Strings übergeben, die jeweils ein zu aktualisierendes Projekt repräsentieren. Das SVNSystem speichert seine Steuerinformationen allerdings in Form von Metadaten im Projekt selbst. Daher werden durch die übergebenen Strings lediglich
die zu prüfenden Projekte charakterisiert, deren SVN-Metadaten auf Änderungen untersucht werden sollen. Es ist deshalb nicht möglich, nur durch
Betrachtung der Parameter eine Aussage darüber zu treffen, wie viele Daten übertragen werden müssen. In Abbildung 7.6 ist der zeitliche Verlauf der
physischen Ressourcennutzung dargestellt.
Traffic
[Byte]
20 0 0
180 0
kein Traffic
160 0
140 0
120 0
100 0
8 00
6 00
Netzwerkgerätezugriffe
4 00
2 00
0
0
10 0
2 00
30 0
40 0
50 0
60 0
70 0
80 0
90 0
100 0
Zeit
[ms]
Abbildung 7.6: Netzwerknutzung im zeitlichen Ablauf zweier commit() Ausführungen
7.3. KRITISCHE BEWERTUNG
137
Bei einer Ermittlung der zeitlichen Abfolge der physischen Netzwerkzugriffe lässt sich erkennen, dass die Verwendung der Netzwerkschnittstelle relativ gleichmäßig verteilt über die Ausführungszeit der Methode stattfindet.
Die Menge der übertragenen Daten ist für die einzelnen Nutzungen meist
gleich, was durch die Bildung von Paketen zur Datenübertragung erklärbar
ist. Insgesamt verhält sich die Methode aber immer noch unvorhersehbar.
Dieses Beispiel stellt ein Extrem dar, da eine willkürliche Methode gewählt wurde, ohne Kenntnis der Implementierung des Gesamtsystems zu besitzen. Es zeigt, wie flexibel die Datenerhebung stattfinden kann. Allerdings
ist nicht immer eine sinnvolle Beschreibung lediglich unter Betrachtung der
Methodenparameter und ihrer Metadaten möglich. Eine weitere Arbeit könnte sich mit der Aufnahme von Kontrollflussinformationen beziehungsweise
interner Zustände in die Beschreibung befassen.
Im optimalen Fall liegen einer Komponente daher bei der Auslieferung
Benchmarks bei, die eine gezielte Vermessung im eigenen System ermöglichen. Ist dies nicht der Fall, so kann die beschriebene Methode angewendet
werden. Der Preis dafür ist jedoch ein erhöhter manueller Aufwand, sowohl
bei der Datenerhebung, als auch bei der Auswertung. Um ein sinnvolles mathematisches Modell daraus ableiten zu können, müssen die Daten eventuell
im Nachhinein nach Parametern sortiert werden.
7.3
Kritische Bewertung
Der große Nachteil der beschriebenen Verfahren ist die Abhängigkeit von den
Parametern beziehungsweise der Metadaten über die Parameter. Zum Einen
sind sie nicht immer verfügbar, zum Anderen, und das wiegt noch schwerer,
ist im allgemeinen Fall nicht bekannt, welche Parameter von Bedeutung sind.
So ist beispielsweise bei den Sortieralgorithmen der Parameter der Methode
sort() ein generisches Array, realisiert durch ein Template (T()). Darin enthalten ist, aufgrund der Natur von Templates, eine im Voraus unbekannte
Menge beliebiger Objekte. Im Fall der Sortieralgorithmen ist klar, dass die
Objekte das Interface Comparable implementieren müssen. Nach welchem
Kriterium aber genau sortiert wird, kann nicht ermittelt werden. Da Methodenparameter in Java aus einer beliebigen Menge Objekte bestehen können,
und diese Objekte wiederum Referenzen auf andere Objekte halten können,
die von ihnen verwendet werden, ist eine vollständige Analyse zur Laufzeit
nahezu unmöglich. Daher sollten die Ressourcennutzungsdaten zunächst in
einer kontrollierten Ausführung durch Benchmarks ermittelt werden. In diesem Fall sind die Testdaten bekannt und es kann untersucht werden, welche
Parameter bei Veränderung tatsächlich Einfluss ausüben.
138
KAPITEL 7. EVALUATION
Ein weiterer wichtiger Punkt sind Methoden, deren Ressourcennutzung
in keinen Verhältnis zu den übergebenen Parametern stehen. Wird beispielsweise ein Webservice aus einer spezifischen Methode heraus aufgerufen, so
kann die empfangene Menge an Bytes der Antwort im Normalfall nicht aus
den Parametern geschlossen werden. Ähnliches gilt für das Versenden einer
Suchanfrage, da der String mit der Anfrage in keinen Verhältnis zu den durch
die Suchmaschine gelieferten Ergebnis steht. Dieselbe Problematik besteht
ebenfalls bei Datenbankanfragen. Ebenso kann es sein, dass die Ressourcennutzung sehr stark vom internen Kontrollfluss oder vom internen Zustand
einer Komponente abhängig ist. Auch in diesem Fall ist es schwierig oder
unmöglich, eine akkurate mathematische Beschreibung zu gewinnen. Allgemein lassen sich Methoden in drei Kategorien einteilen (zu einen ähnlichen
Ergebnis kam auch [SMM08]).
1. Methoden, die, unabhängig von Parametern, immer die gleiche Ressourcennutzung verursachen.
2. Methoden, deren Ressourcennutzung in Abhängigkeit der Parameter
heuristisch vorhersagbar ist.
3. Methoden, bei denen kein Verhältnis zwischen Ressourcennutzung Parametern erkennbar ist.
Die Implementierung von Jrupi deckt die ersten beiden Fälle vollständig ab. Beim dritten Fall ist es möglich, Daten über die Ressourcennutzung
zu sammeln. Eventuell können durch längere Beobachtungen gewisse Muster
bei der Ressourcennutzung erkannt werden. Eine genaue Vorhersage in Form
einer mathematischen Beschreibung ist allerdings nicht möglich. Der Umgang mit derartigen Methoden sollte in einer Folgearbeit genauer untersucht
werden.
Kapitel 8
Zusammenfassung und Ausblick
Eine abstrakte Verhaltensbeschreibung von CCM-Komponenten, und damit von Java-Anwendungen im Allgemeinen, bezüglich ihrer Ressourcennutzung zu gewinnen, stellte sich als schwierige Aufgabe heraus. Dabei liegt
die Schwierigkeit nicht etwa in der Komplexität der Beschreibung an sich
begründet, sondern vielmehr in der Problematik, dass sie nur auf Grundlage von Daten zur Nutzung physischer Hardwarekomponenten durch die
implementierte Komponente während der Ausführung adäquat bei gleichzeitig vertretbaren Aufwand ermittelt werden kann. In Abschnitt 8.1 werden
die geleisteten Beiträge der Arbeit zusammengefasst.
Die Problematik stellte sich als ein weitreichendes Gebiet heraus. So setzt
die Umsetzung einer Profiling-Infrastruktur eine gewisse Kenntnis von Hardware, Betriebssystem und der JVM auf Seiten der Systemumgebung voraus. Andererseits umfasst es aber auch das Wissen um Verfahren wie Sampling, Instrumentierung, Profiling, Benchmarking sowie die Anwendung unterschiedlichster Werkzeuge. Die Implementierungen reichten von der Entwicklung der low-level Profiler bis zur Erweiterung der Eclipse-Workbench.
Aufgrund der begrenzten Arbeitszeit ist es schlicht nicht möglich, alle Aspekte vollständig umzusetzen. In Kapitel 7 wurde gezeigt, dass das Ziel getroffen
wurde (Tracer Bullet). Abschnitt 8.2 zeigt auf, was noch getan werden muss.
8.1
Zusammenfassung
In dieser Arbeit wurde die Gewinnung einer abstrakten Verhaltensbeschreibung bezüglich der Ressourcennutzung von Java-Codeartefakten im Rahmen
eines Reverse Engineering umgesetzt. Die Motivation für eine derartige Beschreibung kam aus dem CoolSoftware Projekt, das zur Ermöglichung eines
aktiven Energiemanagements für IT-Infrastrukturen ein abstraktes Verhal139
140
KAPITEL 8. ZUSAMMENFASSUNG UND AUSBLICK
tensmodell der Ressourcennutzung der CCM Komponenten benötigt. Zur
abstrakten Beschreibung wurde eine mathematische Modellierung der Ressourcennutzung auf Schnittstellenebene der Komponente ausgewählt. Die
Komponente wird als Blackbox angesehen, deren Verhältnis zwischen Einund Ausgabedaten durch ein generatives System in Form einer Menge mathematischer Formeln für die relevanten Hardwarekomponenten durch Systeminferenz nachgebildet wird. Die Eingabedaten entsprechen den Parameter
(beziehungsweise Metadaten über die Parameter) der angebotenen Schnittstelle und die Ausgabedaten der resultierenden Ressourcennutzung bei der
Ausführung. Eine Problematik stellen die Parameter dar, weil sie in einer
Sprache wie Java eine nicht vorhersagbare Struktur aufweisen können. Zudem werden zur Ableitung einer mathematischen Formel numerische Daten
benötigt, die oft nur als Metadaten, zum Beispiel für Arrays oder Objekte, verfügbar gemacht werden können. Ein weiteres Problem tritt auf, wenn
die Parameter in keinem Verhältnis zur Ressourcennutzung stehen, wie es
beispielsweise bei Suchanfragen an einen Webserver der Fall ist.
Jenseits dieser Probleme bei der Modellerstellung leistet die entworfene
und implementierte Lösung eine detaillierte Datenerhebung zur Ressourcennutzung von Java-Codeartefakten. Sie hebt sich dadurch hervor, dass sie eine
direkte Verbindung zwischen dem in der Ausführung befindlichen Codeartefakt und dem Profiling der physischen Ressourcennutzung auf Betriebssystemebene etabliert. Durch die Zuordnung der ausgeführten Methode zu einem
durch das Betriebssystem verwalteten Thread kann die Ressourcennutzung,
die als Konsequenz des Methodenaufrufs erfolgte, exakt einer Methode zugeordnet werden.
Ermöglicht wird dies durch den Einsatz von Werkzeugen zur dynamischen
Ablaufverfolgung (SystemTap, BTrace) und der Instrumentierung der JavaAnwendung auf Bytecodeebene. Die Instumentierung des Bytecodes erfolgt
dynamisch durch das Werkzeug BTrace, das die benötigten Ausgabestatements, nachdem es seinen Agenten zur Bytecode-Manipulation in die laufende JVM geladen hat, beim Class Loading der entsprechenden Klassen in
die laufende Anwendung einbringt. Über eine geschickte Verbindung, die den
Systemaufruf write() nutzt, um die aktuelle Stelle in der Java-Anwendung
den low level Profilern mitzuteilen, können die Ressourcennutzungsdaten exakt einzelnen Codeartefakten zugeordnet werden. Damit findet die eigentliche Datenerhebung außerhalb des JVM-Prozesses auf Betriebssystemebene statt und beeinflusst Java-Anwendung nur minimal durch die zusätzlich
eingebrachten Ausgabestatements. Obwohl auf beiden Seiten (JVM und Betriebssystem) mit Instumentierung gearbeitet wird, können auf diese Weise
dennoch die Vorteile von Sampling-Verfahren erreicht werden, da die Datenerhebung entkoppelt von der Anwendung stattfindet. Damit wird der ein-
8.1. ZUSAMMENFASSUNG
141
gebrachte Overhead minimiert, so dass einem Heisenberg-Effekt vorgebeugt
wird.
Die Flexibilität die durch die Kombination von BTrace und SystemTap
erreicht wird, wurde in der Evaluation in Abschnitt 7.2 gezeigt. Mit BTrace
lassen sich zunächst Methoden von Interesse in einer der laufenden Anwendung aufspüren und ihre Parameter erfragen. Sind diese Informationen bekannt, so können die Instrumentierungen zur Verbindung zu den Profilern
wie gewohnt geschrieben werden. Sie werden anschließend in die, immer noch
laufende Anwendung geladen und die Profiler auf Systemebene gestartet. So
lassen sich unbekannte Codeartefakte, für die weder Quell- noch Bytecode
verfügbar ist, vermessen. Dazu muss die Anwendung, die gegebenenfalls auf
einem Produktivsystem läuft und nicht gestoppt werden kann, niemals angehalten werden.
Im Allgemeinen ist es aber leichter, die Daten im Rahmen einer kontrollierten Ausführung durch einen Benchmark zu erheben. Zum Einen sind
damit die Eingabedaten bekannt, so dass die Parameter nicht über ein umständliches Verfahren ermittelt werden müssen. Zum Anderen ermöglicht dies
eine vorgegebene Reihenfolge der Ausführung, wodurch die erhobenen Daten
im Nachhinein nicht geordnet werden müssen. Die Erstellung dieser Benchmarks sollte bereits während der Entwicklung erfolgen und kann gleichzeitig
für ein Performance Regression Testing [MH10] zur Sicherstellung der Erreichung von Performancezielen dienen. Daher ergeben sich zusätzliche Synergieeffekte, da ihre Erstellung zur Sicherstellung der beiden nicht-funktionalen
Eigenschaften Energie und Performance dient, und somit der zusätzliche Aufwand gerechtfertigt ist.
Nachdem die Daten über die physische Ressourcennutzung erhoben sind,
müssen sie ausgewertet werden. Zunächst erfolgt eine Vorauswertung im Rahmen einer statistischen Datenanalyse. Zu diesem Zweck wurde das AnalyseFramework implementiert. Es liest die Daten, die durch SystemTap im JSONFormat in eine Datei gespeichert wurden (Flightrecorder), ein und zeigt sie
zunächst in tabellarischer Form an. Daraufhin können sie statistisch ausgewertet werden, indem beispielsweise Mittelwert und Standardabweichungen
berechnet werden.
Nach dieser Auswertung muss nach einem mathematischen Zusammenhang in den verbleibenden Daten gesucht werden. Dieser muss das Verhältnis
zwischen den Parametern, beziehungsweise ihren relevanten Metadaten, zu
den aufgezeichneten Ressourcennutzungsdaten herstellen. Ist dieser Zusammenhang einfach erkennbar, so kann er direkt in eine simple mathematische
Formel gefasst werden. Andernfalls wird die Verwendung von Werkzeugen zur
intelligenten Datenanalyse empfohlen. Im Speziellen wurde für die Proof-ofConcept Implementierung Eureqa verwendet, das mathematische Zusammen-
142
KAPITEL 8. ZUSAMMENFASSUNG UND AUSBLICK
hänge aus Datenmengen nahezu automatisch lernt. Lediglich die erlaubten
Operationen und die gesuchte Funktion muss angegeben werden.
Sind Funktionen für alle Hardwarekomponenten gefunden, die ein Port
(respektive Methode) verwendet, so ist dieser vollständig beschrieben. Durch
Beschreibung aller Ports einer Variante ist auch die Variante vollständig beschrieben. Das Ziel einer abstrakten Verhaltensbeschreibung für CCM Softwarekomponenten bezüglich ihrer Ressourcennutzung ist erreicht.
8.2
Ausblick
Die Proof-of-Concept Implementierung hat gezeigt, dass das konzeptuelle
Framework Jrupi in der Praxis umsetzbar ist. Durch die geschickte Auswahl geeigneter Werkzeuge können detaillierte Daten zur Ressourcennutzung
einzelner Java-Codeartefakte gesammelt und eine abstrakte Beschreibung in
Form eines mathematischen Modells daraus abgeleitet werden. So lässt sich
die Ressourcennutzung von Java-Methoden im Allgemeinen und von Ports
einer CCM Komponente im Speziellen ermitteln und beschreiben. Dieser Abschnitt erfüllt zwei Aufgaben. Zum Einen gibt er einen Überblick über weitere Funktionen, die implementiert werden sollten, da sie aufgrund der großen
Spannweite des Themas nicht mehr den Weg in diese Arbeit gefunden haben.
Zum Anderen erläutert er mögliche Verwendungen in CoolSoftware.
Offene Funktionen und weitere Aufgaben
Für die Implementierung von Jrupi wären folgende weitere Funktionen wünschenswert:
• Implementierung weiterer Profiler: Bisher sind Profiler für CPUs,
Netzwerkschnittstellen und Festplatten implementiert worden. Eine Implementierung für weitere Hardwarekomponenten (z.B. Arbeitsspeicher,
Grafikkarte,...) wäre wünschenswert.
• Erweiterung um Metriken: Die Profiler könnten um zusätzliche Metriken erweitert werden. Bisher sammeln sie quantitative Daten und
zeitliche Informationen. Es wäre möglich, weitere Metriken (z.B. Durchsatz (Throughput), Warteschlangenlänge, Sättigung (Saturation),...)
direkt innerhalb der Profiler mit einzubeziehen.
• Erweiterung des Analyse-Framework: Werden neue Profiler hinzugefügt, so muss auch das Analyse-Framework entsprechend erweitert
8.2. AUSBLICK
143
werden. Darüber hinaus wäre es sinnvoll, weitere Analyseverfahren einzubeziehen und eine übersichtlichere Datendarstellung (z.B Diagramme) zu ermöglichen. Der Grundstein wurde dazu wurde durch das Einbinden von JFreeChart1 bereits gelegt.
Aufgaben für zukünftige Arbeiten könnten beispielsweise folgende Themen umfassen:
• Kombination mit Kontrollfluss Der entwickelte Blackbox Ansatz
bietet den Vorteil, dass er zu relativ simplem Modellen führt. Dafür
wird unter Umständen auf Genauigkeit verzichtet, da der interne Kontrollfluss sowie der innere Zustand einer Komponente nicht betrachtet
werden. Das führt in der Konsequenz dazu, dass die Ressourcennutzung
nicht immer exakt vorhersagbar ist. Eine Kombination von Jrupi mit
den Interna einer Komponente könnte Abhilfe schaffen. Dabei könnten die in Unterabschnitt 3.1.2 Softwareausführungsmodellen und der
Prozess der Modellextraktion hilfreich sein.
• Lösung des Parameterproblems Ein weiteres wichtiges Problem ist
die Abhängigkeit von Parametern beziehungsweise von Metadaten über
die Parameter. Bei komplexen Strukturen müssten sie in Form einer
gezielten Parameteranalyse in Bezug auf ihre Auswirkungen auf die
Ressourcennutzung ermittelt werden. Eine weitere Arbeit könnte sich
daher damit beschäftigen, wie diese Metadaten gezielt und im besten
Fall automatisch aufgefunden werden können.
• Weitere Beschreibungsformen Die vorgestellten mathematischen
Modelle bieten den Vorteil, dass sie kompakt und formal sind. Darüber
hinaus beschreiben sie den Zusammenhang in der universalen Sprache
der Mathematik und sind daher vielseitig anwendbar. Dennoch wäre es
denkbar, gerade in Kombination mit den beiden zuvor genannten Punkten, weitere Beschreibungsformen mit einzubeziehen. Denkbar wären
auch komplexere mathematische Modelle, mit einer übergeordneten
Struktur oder unter Einbezug zeitlicher Eigenschaften, beispielsweise
in Form von Differentialgleichungen. Auch Fuzzy-Logik [Pae06] könnte
hilfreich sein, um eine sinnvolle Beschreibung in Anbetracht unscharfer
Parameter zu erreichen.
• Mehr Automatisierung Der bisherige Ansatz benötigt immer noch
einige manuelle Arbeit. Zwar sind externe Werkzeuge wie Eureqa in der
Lage, mathematische Zusammenhänge automatisch abzuleiten, aber
1
http://www.jfree.org/jfreechart/
144
KAPITEL 8. ZUSAMMENFASSUNG UND AUSBLICK
nur lose durch Datenimport gekoppelt. Es wäre wünschenswert, diesen
Prozess direkt in die Entwicklungsumgebung zu integrieren. Eventuell
könnten Verfahren zur intelligenten Datenanalyse oder auch neuronale
Netze [Pae06] zum Einsatz kommen, um die Beschreibung automatisch
aus einer Menge Daten innerhalb der Entwicklungsumgebung abzuleiten.
Verwendung in CoolSoftware
Das CoolSoftware Projekt befindet zum aktuellen Zeitpunkt noch in einer
relativ frühen Phase. Für zentrale Bestandteile wie die Laufzeitumgebung
THEATRE sind noch keine Implementierungen verfügbar. Andere Bestandteile wie CCM und ECL befinden sich unter aktiver Entwicklung und wurden im Verlauf der Arbeit größeren Änderungen unterzogen. Deshalb, und
wegen der begrenzten Bearbeitungszeit, war es bisher nicht möglich, einen
endgültigen Platz für die mathematischen Beschreibungen des Ressourcennutzungsverhalten zu ermitteln. Aufgrund der universalen Natur einer mathematischen Beschreibung ist die Integration jedoch in jedem Fall nur mit
geringem Aufwand verbunden. Folgende Punkte sollten dabei berücksichtigt
werden:
• CCM Intuitiv sollte die Beschreibung des Ports einer Variante die
geeignete Stelle zur Vorhaltung der mathematischen Beschreibung sein.
• Expressions Um diverse Ausdrücke und Operationen in ECL verwenden zu können, wurde bereits eine DSL (Domain Specific Language)
für Expressions erstellt. Sie müsste gegebenenfalls um zusätzliche mathematische Funktionen erweitert werden, sollten die abgeleiteten Beschreibungen dies erfordern. Prinzipiell kann sie aber direkt zum Ausdruck der mathematischen Funktionen verwendet werden. Diese Beschreibung müsste anschließend lediglich von der Variante referenziert
werden.
• Simulation In CoolSoftware wird die Infrastruktur simuliert. Durch
die Kombination der mathematischen Beschreibungen mit typischen
Benutzeranfragen aus den Kontextoren und Erfahrungswerten (oder
Abschätzungen) über die typischen Parameter können nun auch Softwarekomponenten in die Simulation einbezogen werden.
• Energie- und Ressourcenmanager Stehen die Energie- und Ressourcenmanager zur Verfügung, so können die Modelle verwendet werden, um eine Abschätzung über die zukünftigen Ressourcennutzung
8.2. AUSBLICK
145
(in Kombination mit Erfahrungswerten und Daten aus der Vergangenheit) zu treffen und gegebenenfalls eine Rekonfiguration zu veranlassen.
Insbesondere sollte es möglich sein, die Energiezustände der Hardwarekomponenten vorausschauend zu verwalten. So könnte beispielsweise
anhand der Modelle entschieden werden, dass die zur Verfügung stehende Menge einzelner Hardwareressourcen ausreicht, um die Dienste
der Komponente zu erfüllen, so dass andere Hardwarekomponenten in
niedrigen Energiezuständen verweilen oder abgeschaltet bleiben können.
Schlussbemerkung
Die Beschreibung des Ressourcennutzungsverhaltens von Softwareartefakten
ist lediglich ein Baustein auf dem Weg zum großen Ziel der Steigerung der
Energieeffizienz von IT-Infrastrukturen. Erst durch Einsatz im Rahmen eines gesamtheitlichen Ansatzes, wie ihn CoolSoftware anstrebt, kann entsprechend großer Nutzen daraus gezogen werden. Die Einsparung von Energie
ist ein Thema, das auch die Softwareseite erreicht hat. So sind in Zukunft
eine Menge neuer Methoden und Ansätze zu erwarten. Eventuell können die
Ergebnisse dieser Arbeit einen Beitrag zum Gesamtziel leisten.
In dieser Arbeit wurde eine Möglichkeit entwickelt, um Ressourcennutzungsdaten zu erheben und einzelnen Java-Methoden zuzuordnen, um anschließend eine mathematische Beschreibung daraus abzuleiten. Die implementierten Profiler sind jedoch unabhängig von Java, da sie auf Betriebssystemebene arbeiten und lediglich einen spezifizierten Prozess (und dessen
Threads) überwachen. Sie können daher für beliebige Anwendungen verwendet werden und einzelne Codeartefakte auf Ressourcennutzung untersuchen
- unter der Voraussetzung, dass die Codeartefakte durch die benötigten Systemaufrufe markiert werden. Eine Möglichkeit, um die Instrumentationen
einzubringen, bietet sich durch die Verwendung von SystemTap im user
space. Werden Anwendungen mit zusätzlichen Debuginformationen kompiliert, so ist SystemTap in der Lage, innerhalb der Anwendungen Prüfpunkte
zu definieren und Kontextinformationen aufzulösen. Ist dieses Feature ausgereift, so kann ein konsistentes Profiling nahezu beliebiger Anwendungen mit
SystemTap erfolgen.
Die implementierten Profiler sollten nicht als starres Gebilde gesehen werden. Dank der Verwendung von SystemTap sind sie programmierbar und
können daher leicht angepasst und erweitert werden. Ein Abgleich zwischen
Ressourcennutzung und gemessenen Energieverbrauch wird zeigen müssen,
welche Metriken sinnvolle Aussagen liefern. Dann ist die Zeit gekommen, eine
Anpassung der Profiler und der Verhaltensbeschreibung durchzuführen.
146
KAPITEL 8. ZUSAMMENFASSUNG UND AUSBLICK
Literaturverzeichnis
[BC00]
Daniel Pierre Bovet and Marco Cassetti, Understanding the Linux Kernel, O’Reilly & Associates, Inc., Sebastopol, CA, USA,
2000.
[BH07]
Luiz André Barroso and Urs Hölzle, The Case for EnergyProportional Computing, Computer 40 (2007), 33–37.
[BPBS10]
Jacob Bart, Larson Paul, Leitao Breno, Henrique, and Augusto Saulo, SystemTap: Instrumenting the Linux Kernel for Analyzing Performance and Functional Problems, IBM Redbooks,
2010.
[BRH04]
James F. Bowring, James M. Rehg, and Mary Jean Harrold,
Active learning for automatic classification of software behavior,
SIGSOFT Softw. Eng. Notes 29 (2004), no. 4, 195–205.
[Bru07]
Eric Bruneton, ASM 3.0 A Java bytecode engineering library,
2007.
[Chi04]
Shigeru Chiba, Javassist: Java Bytecode Engineering Made Simple, 2004.
[CI90]
Elliot J. Chikofsky and James H. Cross II, Reverse Engineering
and Design Recovery: A Taxonomy, IEEE Software 7 (1990),
13–17.
[Cor10]
Standard Performance Evaluation Corporation, SPEC Power
and Performance, 2010.
[CSG+ 10]
Sebastian Cech, Matthias Schmidt, Sebastian Götz, Claas Wilke, Johannes Waltsgott, and Ronny Fritzsche, Cool Component
Model Specification, Tech. report, Dresden, Germany, 2010.
[Dal10]
Valentin Dallmeier, Mining and Checking Object Behavior,
Ph.D. thesis, Universität des Saarlandes, 2010.
147
148
LITERATURVERZEICHNIS
[DDF+ 06]
Simon Dobson, Spyros Denazis, Antonio Fernández, Dominique
Gaïti, Erol Gelenbe, Fabio Massacci, Paddy Nixon, Fabrice Saffre, Nikita Schmidt, and Franco Zambonelli, A survey of autonomic communications, ACM Trans. Auton. Adapt. Syst. 1
(2006), no. 2, 223–259.
[Dua07]
Lucio Mauro Duarte, Behaviour Model Extraction using Context
Information, Ph.D. thesis, University of London, 2007.
[EPG+ 07]
Michael D. Ernst, Jeff H. Perkins, Philip J. Guo, Stephen McCamant, Carlos Pacheco, Matthew S. Tschantz, and Chen Xiao,
The Daikon system for dynamic detection of likely invariants,
Science of Computer Programming 69 (2007), no. 1–3, 35–45.
[ET10]
Ciliendo Eduardo and Kunimasa Takechika, Linux Performance
and Tuning Guidelines, IBM Redbooks, 2010.
[Fli01]
Jason Flinn, Extending Mobile Computer Battery Life through
Energy-Aware Adaptation, Ph.D. thesis, School of Computer
Science, Computer Science Department, Carnegie Mellon University, Pittsburgh, PA, December 2001.
[FPS02]
Jason Flinn, SoYoung Park, and M. Satyanarayanan, Balancing Performance, Energy, and Quality in Pervasive Computing,
ICDCS ’02: Proceedings of the 22 nd International Conference
on Distributed Computing Systems (ICDCS’02) (Washington,
DC, USA), IEEE Computer Society, 2002, p. 217.
[FS99]
Jason Flinn and M. Satyanarayanan, PowerScope: A Tool for
Profiling the Energy Usage of Mobile Applications, Proceedings
of the Second IEEE Workshop on Mobile Computer Systems
and Applications (Washington, DC, USA), WMCSA ’99, IEEE
Computer Society, 1999, pp. 2–.
[FZJ08]
Yunsi Fei, Lin Zhong, and Niraj K. Jha, An energy-aware framework for dynamic software management in mobile computing
systems, ACM Trans. Embed. Comput. Syst. 7 (2008), no. 3,
1–31.
[Gar07]
Matthew Garrett, Powering Down, ACM Queue 5 (2007), no. 7,
16–21.
[GB04]
Erich Gamma and Kent Beck, Contributing to Eclipse: Principles, Patterns, and Plug-Ins, Addison Wesley, 2004.
LITERATURVERZEICHNIS
149
[GCW+ 10] Sebastian Götz, Sebastian Cech, Claas Wilke, Matthias
Schmidt, Johannes Waltsgott, and Ronny Fritzsche, CoolSoftware Architecture Specification, Tech. report, Dresden. Germany, 2010.
[GHJV95]
Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Design patterns: elements of reusable object-oriented software, Addison-Wesley Professional, 1995.
[GSI+ 02]
S. Gurumurthi, A. Sivasubramaniam, M. J. Irwin, N. Vijaykrishnan, and M. Kandemir, Using complete machine simulation
for software power estimation: the SoftWatt approach, feb. 2002,
pp. 141–150.
[GWS+ 10a] Sebastian Götz, Claas Wilke, Matthias Schmidt, Sebastian
Cech, and Uwe Assmann, Towards Energy Auto Tuning, 2010.
[GWS+ 10b] Sebastian Götz, Claas Wilke, Matthias Schmidt, Sebastian
Cech, Johannes Waltsgott, and Ronny Fritzsche, THEATRE
Resource Manager Interface Specification, Tech. report, To be
published in 2010.
[HJMM09] Buhl Hans, Ulrich, Laartz Jürgen, Löffler Markus, and Röglinger Maximilian, Green IT reicht nicht aus!, WIRTSCHAFTSINFORMATIK & MANAGEMENT, Januar 2009.
[HPIM+ 10] Hewlett-Packard, Intel, Microsoft, Phoenix Technologies, and
Toshiba, Advanced Configuration and Power Interface Specification, Revision 4.0a, http://www.acpi.info/spec.htm, April 2010.
[HT99]
Andrew Hunt and David Thomas, The pragmatic programmer:
from journeyman to master, Addison-Wesley Longman Publishing Co., Inc., Boston, MA, USA, 1999.
[JF04]
Weidendorfer Josef and Zenith Federico, The KCachegrind
Handbook, 2004.
[KA05]
Kreft Klaus and Langer Angelika, Java Performance: MicroBenchmarking, JavaSPEKTRUM, September 2005.
[KLM+ 97]
Gregor Kiczales, John Lamping, Anurag Mendhekar, Chris Maeda, Cristina Lopes, Jean-Marc Loingtier, and John Irwin,
Aspect-oriented programming, ECOOP’97 - Object-Oriented
150
LITERATURVERZEICHNIS
Programming (Mehmet Akşit and Satoshi Matsuoka, eds.), Lecture Notes in Computer Science, vol. 1241, Springer-Verlag, Berlin/Heidelberg, 1997, pp. 220–242.
[Lan03]
Hans-Werner Lang, Algorithmen in java, Oldenbourg, 2003.
[Lev03]
John Levon, Oprofile internals, 2003.
[LJ03]
Tao Li and Lizy Kurian John, Run-time modeling and estimation of operating system power consumption, SIGMETRICS Perform. Eval. Rev. 31 (2003), no. 1, 160–171.
[LMP08]
Davide Lorenzoli, Leonardo Mariani, and Mauro Pezzè, Automatic generation of software behavioral models, Proceedings of
the 30th international conference on Software engineering (New
York, NY, USA), ICSE ’08, ACM, 2008, pp. 501–510.
[Lov10]
Robert Love, Linux Kernel Development, third ed., AddisonWesley Professional, 2010.
[Lud03]
Jochen Ludewig, Models in software engineering: an introduction, Software and Systems Modeling 2 (2003), no. 1, 5–14,
10.1007/s10270-003-0020-3.
[LY99]
Tim Lindholm and Frank Yellin, Java virtual machine specification, 2nd ed., Addison-Wesley Longman Publishing Co., Inc.,
Boston, MA, USA, 1999.
[MD07]
Mesfin Mulugeta Dinku, Qos contract negotiation in distributed
component-based software, Ph.D. thesis, Technische Universität
Dresden, Dresden, Germany, July 2007.
[MH10]
Marcus Lagergren Marcus Hirt, Oracle JRockit The Definitive
Guide, Packt Publishing, 2010.
[MMG06]
Richard McDougall, Jim Mauro, and Brendan Gregg, Solaris(TM) Performance and Tools: DTrace and MDB Techniques
for Solaris 10 and OpenSolaris (Solaris Series), Prentice Hall
PTR, Upper Saddle River, NJ, USA, 2006.
[MN07]
Marc van den Bogaard Mirco Novakovic, Profiling, Diagnose
und Monitoring, Javamagazin, Januar 2007.
LITERATURVERZEICHNIS
151
[Pae06]
Jürgen Paetz, Soft Computing in der Bioinformatik: Eine grundlegende Einführung und Übersicht (eXamen.press), SpringerVerlag New York, Inc., Secaucus, NJ, USA, 2006.
[PN08]
Meikel Poess and Raghuanath Othayoh Nambiar, Energy Cost,
The Key Challenge of Today’s Data Centers: A Power Consumption Analysis of TPC-C Results, Proceedings of the
PVLD08 (New York, NY, USA), ACM Press, 2008, pp. 1229–
1240.
[PS06]
Venkatesh Pallipadi and Alexey Starikovskiy, The ondemand governor: past, present and future, Proceedings of Linux Symposium, vol. 2, pp. 223-238, 2006.
[Roy70]
Walker W. Royce, Managing the development of large software systems: concepts and techniques, Proc. IEEE WESTCON,
Los Angeles (1970), 1–9, Reprinted in Proceedings of the Ninth
International Conference on Software Engineering, March 1987,
pp. 328–338.
[Sax10]
Eric Saxe, Power-Efficient Software, ACM Queue 8 (2010),
no. 1, 10–17.
[SBPM08]
Dave Steinberg, Frank Budinsky, Marcelo Paternostro, and
Ed Merks, EMF: Eclipse Modeling Framework, Addison-Wesley
Professional, 2008.
[SEP+ 09]
Chiyoung Seo, George Edwards, Daniel Popescu, Sam Malek,
and Nenad Medvidovic, A framework for estimating the energy consumption induced by a distributed system’s architectural
style, Proceedings of the 8th international workshop on Specification and verification of component-based systems (New York,
NY, USA), SAVCBS ’09, ACM, 2009, pp. 27–34.
[SL09]
Michael Schmidt and Hod Lipson, Distilling Free-Form Natural
Laws from Experimental Data, Science 324 (2009), no. 5923,
81–85.
[SMM08]
Chiyoung Seo, Sam Malek, and Nenad Medvidovic, Estimating the Energy Consumption in Pervasive Java-Based Systems,
Pervasive Computing and Communications, IEEE International
Conference on 0 (2008), 243–247.
152
LITERATURVERZEICHNIS
[SMSS10]
Rajiv Ranjan Suman, Rajib Mall, Srihari Sukumaran, and Manoranjan Satpathy, Extracting State Models for Black-Box Software Components, Journal of Object Technology 9 (2010), no. 3,
79–103.
[Sta73]
Herbert Stachowiak, Allgemeine Modelltheorie, Springer, Wien
[u.a.], 1973.
[Sun08]
Sun, Handbuch zur dynamischen Ablaufverfolgung in Solaris.
[Szy02]
Clemens Szyperski, Component Software: Beyond ObjectOriented Programming, 2nd ed., Addison-Wesley Longman Publishing Co., Inc., Boston, MA, USA, 2002.
[Tan07]
Andrew S. Tanenbaum, Modern Operating Systems, 3rd ed.,
Prentice Hall Press, Upper Saddle River, NJ, USA, 2007.
[The06]
The Apache Software Foundation, Bcel, 2006.
[Tho83]
R. Thom, Mathematical models of morphogenesis, Ellis Horwood
series in mathematics and its applications, Ellis Horwood, 1983.
[TKTJ02]
A. Raghunathan T. K. Tan and N. K. Jha, Embedded Operating
System Energy Analysis and Macro-Modeling, ICCD ’02: Proceedings of the 2002 IEEE International Conference on Computer Design: VLSI in Computers and Processors (ICCD’02)
(Washington, DC, USA), IEEE Computer Society, 2002, p. 515.
[TW04]
Jie Tao and Josef Weidendorfer, Cache Simulation Based on
Runtime Instrumentation for OpenMP Applications, Proceedings of the 37th annual symposium on Simulation (Washington,
DC, USA), ANSS ’04, IEEE Computer Society, 2004, pp. 97–.
[TWM+ 08] Niraj Tolia, Zhikui Wang, Manish Marwah, Cullen Bash, Parthasarathy Ranganathan, and Xiaoyun Zhu, Delivering Energy
Proportionality with Non Energy-Proportional Systems: Optimizing the Ensemble, HotPower ’08: Workshop on Power Aware
Computing and Systems, ACM, December 2008.
[UGV08]
Leif Uhsadel, Andy Georges, and Ingrid Verbauwhede, Exploiting Hardware Performance Counters, Proceedings of the 2008
5th Workshop on Fault Diagnosis and Tolerance in Cryptography (Washington, DC, USA), IEEE Computer Society, 2008,
pp. 59–67.
LITERATURVERZEICHNIS
153
[WBHS07] Neil Walkinshaw, Kirill Bogdanov, Mike Holcombe, and Sarah
Salahuddin, Reverse Engineering State Machines by Interactive Grammar Inference, WCRE ’07: Proceedings of the 14th
Working Conference on Reverse Engineering (Washington, DC,
USA), IEEE Computer Society, 2007, pp. 209–218.
[WCG+ 10] Claas Wilke, Sebastian Cech, Sebastian Götz, Matthias
Schmidt, Johannes Waltsgott, and Ronny Fritzsche, Energy
Contract Language (ECL) Specification v0.1, Tech. report, Dresden. Germany, 2010.
[ZPK00]
Bernard P. Zeigler, Herbert Praehofer, and Tag G. Kim, Theory
of Modeling and Simulation, Second Edition, 2 ed., Academic
Press, January 2000.
154
LITERATURVERZEICHNIS
Abbildungsverzeichnis
1.1
Schematische Darstellung der Lösung . . . . . . . . . . . . . . 11
2.1
2.2
Zentrale Pakete im CCM . . . . . . . . . . . .
Autonome Kontrollschleife eines Auto Tuning
[DDF+ 06] (neu gezeichnet) . . . . . . . . . . .
Architektur des THEATRE, aus [GWS+ 10b] .
2.3
3.1
3.2
3.3
3.4
4.1
4.2
. . . . . . . . . 16
Systems, aus
. . . . . . . . . 21
. . . . . . . . . 22
Prozess der Modellbildung nach der AMT . . . . . . . . . .
Grundlegende Konzepte eines Systems . . . . . . . . . . . .
Ein Objektverhaltensmodell der Klasse Vector, neu gezeichnet aus [Dal10] . . . . . . . . . . . . . . . . . . . . . . . . .
Problematik des Ressourcennutzungsmodells . . . . . . . . .
. 29
. 30
. 33
. 35
4.5
4.6
Schematische Darstellung des betrachteten Gesamtsystems . .
Energieeffizienz bei schlechter Proportionalität (links) und besserer Proportionalität (rechts), aus [BH07] . . . . . . . . . . .
Processor Power States, vereinfacht aus [HPIM+ 10] . . . . . .
Ausgabe von Powertop für Intel(R) Core(TM)2 Duo CPU
(P7350), Rechner im Batteriebetrieb . . . . . . . . . . . . . .
Das Betriebssystem zwischen Hardware und Software . . . . .
Prozesszustände in Linux , nach [Lov10] . . . . . . . . . . . .
5.1
5.2
5.3
5.4
Verhaltensbeschreibung: abstrakt aus konkret
Kontext der Betrachtungen . . . . . . . . . . .
Übersicht Konzept . . . . . . . . . . . . . . .
Ablauf für Modellersteller als Flussdiagramm .
6.1
6.2
6.3
6.4
Verhältnis zwischen Skript, Tapset und Kernelfunktion . . .
Funktionsweise von SystemTap nach . . . . . . . . . . . . .
Kernelemente der JSON als Syntaxdiagramm . . . . . . . .
Eureqa bei der Auswertung der CPU-Daten einer HeapSortKomponente . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.3
4.4
155
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
47
48
50
51
53
56
75
80
84
86
. 95
. 96
. 101
. 103
156
6.5
6.6
6.7
6.8
6.9
7.1
7.2
7.3
7.4
7.5
7.6
A.1
A.2
A.3
A.4
ABBILDUNGSVERZEICHNIS
Verbindung zwischen JVM und Profiler durch Bytecode Instrumentierung . . . . . . . . . . . . . . . . . . . . . . . . .
Simples SUT-Skellet, wie es vom Codegenerator erstellt wird
Wizard zur Code-Generierung . . . . . . . . . . . . . . . . .
Die CPU-View des Analyse-Framework, mit ausklappbaren
Detaildaten . . . . . . . . . . . . . . . . . . . . . . . . . . .
Schema des Erweiterungspunktes für Analyser . . . . . . . .
. 106
. 113
. 115
. 116
. 118
Profiling-Setup für Sortieralgorithmen . . . . . . . . . . . . . .
Einfluss der Länge der zu sortierenden Strings auf die CPU
Nutzung bei Heap- und Quicksort . . . . . . . . . . . . . . . .
CPU-Nutzung von Heap- und Quicksort in Abhängigkeit der
Arraygröße . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Vergleich der Festplattennutzung bei Speicherung verschiedener Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Zeitlicher Verlauf der Methodenausführung (sortieren und speichern) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Netzwerknutzung im zeitlichen Ablauf zweier commit() Ausführungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Das Strukturpaket des CCM, aus [GCW+ 10] . . . . . .
Das Verhaltenspaket des CCM, aus [GCW+ 10] . . . . .
Das Variantenpaket des CCM, aus [GCW+ 10] . . . . .
Beispiel: Komponenten eines Videoservers, aus [MD07].
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
123
126
128
130
132
136
157
158
158
159
Anhang A
CoolSoftware
A.1
CCM-Metamodell
Direction
2
PortType
IN
OUT
INOUT
d : Direction
PortConnectorType
UserType
*
ComponentType
ResourceType
*
1
Contract
SWComponent
Type
*
Abbildung A.1: Das Strukturpaket des CCM, aus [GCW+ 10]
157
158
ANHANG A. COOLSOFTWARE
Amount
Time
1
1
WorkloadItem
Pin
d : Direction
*
Navigating
Element
*
*
BehaviorTemplate
Workload
StateMachine
CostParameter
*
Abbildung A.2: Das Verhaltenspaket des CCM, aus [GCW+ 10]
PortConnectorType
type
PortType
1
type
1
2
PortConnector
1
1
Link
*
*
Component
1
Port
Pin
*
*
1
Behavior
BehaviorTemplate
type
type
1
ComponentType
*
*
CostParameter
Substitution
1
CostParameter
Abbildung A.3: Das Variantenpaket des CCM, aus [GCW+ 10]
159
A.2. BEISPIEL
A.2
Beispiel
<<container>>
<<container>>
Client
Server
<<component>>
<<component>>
VideoServer
VideoPlayer
<<uses>>
<<uses>>
<<uses>>
<<uses>>
<<uses>>
<<resource>>
<<resource>>
<<resource>>
<<resource>>
CPU
Memory
Connection
FileServer
Abbildung A.4: Beispiel: Komponenten eines Videoservers, aus [MD07].
160
ANHANG A. COOLSOFTWARE
A.3
1
2
3
4
5
1
2
3
4
5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ECL für Beispiel
q u a l i t y _ c h a r a c t e r i s t i c frameRate ( stream :
VideoStream ) {
domain : numeric integer frames / second ;
values : stream . getFrameRate ()
}
r e s o u r c e Network {
q u a l i t y _ c h a r a c t e r i s t i c bandwidth {
domain : numeric real KBit / Second ;
}
}
profile NetworkStates for Network {
state connected {
energy - effect 0.8 Watt ;
}
state disconnected {
energy - effect 0.4 Watt ;
}
t r a n s i t i o n connected -> disconnected {
w h e n e v e r event - occurs disconnect () ;
delay 1.0 seconds ;
energy - effect 5.0 Watt ;
}
t r a n s i t i o n disconnected -> connected {
w h e n e v e r event - occurs send () ;
delay 10.0 seconds ;
energy - effect 30.0 Watt ;
}
initial - state disconnected ;
p r e c e d e n c e connected , disconnected ;
}
profile ServerProfile for VideoServer {
state highQuality {
p r o v i d e s [ c h a r a c t e r i s t i c frameRate = 30]
and [ c h a r a c t e r i s t i c imageWidth = 352]
and [ c h a r a c t e r i s t i c imageHeight = 288];
resources
[ c h a r a c t e r i s t i c bandwidth = 450];
}
state lowQuality {
p r o v i d e s [ c h a r a c t e r i s t i c frameRate = 10]
and [ c h a r a c t e r i s t i c imageWidth = 176]
and [ c h a r a c t e r i s t i c imageHeight = 144];
resources
[ c h a r a c t e r i s t i c bandwidth = 150];
}
t r a n s i t i o n any - state -> any - state {}
p r e c e d e n c e highQuality , lowQuality ;
}
A.3. ECL FÜR BEISPIEL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
profile PlayerProfile for VideoPlayer {
state highQuality {
uses [ ServerProfile . highQuality ];
p r o v i d e s [ c h a r a c t e r i s t i c frameRate = 30]
and [ c h a r a c t e r i s t i c image_width = 352]
and [ c h a r a c t e r i s t i c image_height = 288];
resources
[ c h a r a c t e r i s t i c bandwidth = 450] ,
[ c h a r a c t e r i s t i c cpu_usage = 66.7] ,
[ c h a r a c t e r i s t i c ram = 200.0];
}
state lowQuality {
uses [ ServerProfile . lowQuality ];
p r o v i d e s [ c h a r a c t e r i s t i c frameRate = 10]
and [ c h a r a c t e r i s t i c image_width = 176]
and [ c h a r a c t e r i s t i c image_height = 144];
resources
[ c h a r a c t e r i s t i c bandwidth = 150] ,
[ c h a r a c t e r i s t i c cpu_usage = 25.0] ,
[ c h a r a c t e r i s t i c ram = 50.0];
}
t r a n s i t i o n any - state -> any - state {}
p r e c e d e n c e highQuality , lowQuality ;
}
161
162
ANHANG A. COOLSOFTWARE
Anhang B
Quellcode
B.1
1
2
3
4
Zentrale Bestandteile der Profiler
/* BEGIN NETWORK */
probe netdev . transmit
{
tid = tid ()
5
if ( pid () == target () ) {
6
7
foreach ([ s , t ] in activ e_method s ) {
8
9
if ( t == tid ) {
method_trans [ tid , dev_name ] += truesize
break
}
10
11
12
13
14
}
15
16
target_trans [ dev_name ] += truesize
17
18
} else {
rest_trans [ dev_name ] += truesize
}
if (!([ dev_name ] in dev_list ) ) dev_list [ dev_name ] = 1
19
20
21
22
23
}
24
25
26
27
28
probe netdev . receive
{
tid = tid ()
29
30
if ( pid () == target () ) {
31
32
foreach ([ s , t ] in activ e_method s ) {
33
if ( t == tid ) {
method_rec [ tid , dev_name ] += truesize
break
}
34
35
36
37
38
}
163
164
ANHANG B. QUELLCODE
target_rec [ dev_name ] += truesize
} else {
rest_rec [ dev_name ] += truesize
}
if (!([ dev_name ] in dev_list ) ) dev_list [ dev_name ] = 1
39
40
41
42
43
44
45
}
/* END NETWORK */
Listing B.1: Hauptprüfpunkte des Netzwerk-Profiler
1
2
3
4
5
6
7
8
9
/* [...] from SystemTap RedBook :
* There is also a very useful asynchronous probe called
* timer . profile . Handler routines attached to this probe
* are executed on each CPU at every timer interrupt . This makes
* the timer . profile probe useful for writing performance
* profiling tools in SystemTap , because the handler can look
* at the process ’s state on every CPU at a high frequency .
*/
probe timer . profile {
10
tid = tid ()
exe = execname ()
cpu = cpu ()
11
12
13
14
if ( pid () == target () ) {
15
16
foreach ([ s , t ] in activ e_method s ) {
17
18
if ( t == tid ) {
method_ticks [ tid , cpu ] += 1
break
19
20
21
22
}
23
}
24
25
m e t h o d _ t a r g e t _ t i c k s [ cpu ] += 1
26
27
target_cycles [ tid , cpu ] += 1
28
29
} else {
30
31
if ( exe != " swapper " ) {
32
33
m e t h o d _ r e s t _ t i c k s [ cpu ] += 1
allcpus [ cpu ] = 1
34
35
36
} else {
37
38
m e t h o d _ s w a p p e r _ t i c k s [ cpu ] += 1
39
40
}
41
42
}
all_cycles [ exe , cpu ] += 1
43
44
45
}
Listing B.2: Hauptprüfpunkt des CPU-Profilers
B.2. BTRACE-SKRIPT FÜR SORTIERALGORITHMEN
B.2
165
BTrace-Skript für Sortieralgorithmen
1
2
package algorithms . sut ;
3
4
5
6
7
8
import
import
import
import
import
com . sun . btrace . annotations . BTrace ;
com . sun . btrace . annotations . Kind ;
com . sun . btrace . annotations . Location ;
com . sun . btrace . annotations . OnMethod ;
com . sun . btrace . AnyType ;
9
10
11
12
13
14
15
16
@BTrace
public class BTraceScript {
@ O n M e t h o d ( clazz = " a l g o r i t h m s . Q u i c k S o r t " , method = " sort " )
public static void s o r t Q u i c k S o r t E n t r y ( AnyType [] args ) {
System . out . println ( " a l g o r i t h m s : Q u i c k S o r t : sort : " + args [1] + " ; "
+ args [2] + " : m e t h o d _ e n t r : " ) ;
}
17
@ O n M e t h o d ( clazz = " a l g o r i t h m s . Q u i c k S o r t " , method = " sort " , location
= @ L o c a t i o n ( value = Kind . RETURN ) )
public static void s o r t Q u i c k S o r t E x i t ( AnyType [] args ) {
System . out . println ( " a l g o r i t h m s : Q u i c k S o r t : sort : " + args [1] + " ; "
+ args [2] + " : m e t h o d _ e x i t : " ) ;
}
18
19
20
21
22
23
@ O n M e t h o d ( clazz = " a l g o r i t h m s . B u b b l e S o r t " , method = " sort " )
public static void s o r t B u b b l e S o r t E n t r y ( AnyType [] args ) {
System . out . println ( " a l g o r i t h m s : B u b b l e S o r t : sort : " + args [1] + " ; "
+ args [2] + " : m e t h o d _ e n t r : " ) ;
}
24
25
26
27
28
29
@ O n M e t h o d ( clazz = " a l g o r i t h m s . B u b b l e S o r t " , method = " sort " , location
= @ L o c a t i o n ( value = Kind . RETURN ) )
public static void s o r t B u b b l e S o r t E x i t ( AnyType [] args ) {
System . out . println ( " a l g o r i t h m s : B u b b l e S o r t : sort : " + args [1] + " ; "
+ args [2] + " : m e t h o d _ e x i t : " ) ;
}
30
31
32
33
34
35
36
@ O n M e t h o d ( clazz = " a l g o r i t h m s . H e a p S o r t " , method = " sort " )
public static void s o r t H e a p S o r t E n t r y ( AnyType [] args ) {
System . out . println ( " a l g o r i t h m s : H e a p S o r t : sort : " + args [1] + " ; "
+ args [2] + " : m e t h o d _ e n t r : " ) ;
}
37
38
39
40
41
42
@ O n M e t h o d ( clazz = " a l g o r i t h m s . H e a p S o r t " , method = " sort " , location =
@ L o c a t i o n ( value = Kind . RETURN ) )
public static void s o r t H e a p S o r t E x i t ( AnyType [] args ) {
System . out . println ( " a l g o r i t h m s : H e a p S o r t : sort : " + args [1] + " ; "
+ args [2] + " : m e t h o d _ e x i t : " ) ;
}
43
44
45
46
47
48
49
}
Listing B.3: BTrace-Skript für BubbleSort, QuickSort und HeapSort
Erklärung
Ich erkläre, dass ich die vorliegende Arbeit selbständig, unter Angabe aller
Zitate und nur unter Verwendung der angegebenen Literatur und Hilfsmittel
angefertigt habe.
Dresden, den 31.März 2011