SEP 2006 Thema: Debugger für Verteilte Komponentensysteme auf Basis der CTL Martin Krosche Institut für Wissenschaftliches Rechnen Technische Universität Braunschweig Hans-Sommer-Straße 65 D-38106 Braunschweig Version 1.1 (2006-04-11) Copyright © by Institut für Wissenschaftliches Rechnen, Technische Universität Braunschweig This work is subject to copyright. All rights are reserved, whether the whole or part of the material is concerned, specifically the rights of translation, reprinting, reuse of illustrations, recitation, broadcasting, reproduction on microfilm or in any other way, and storage in data banks. Duplication of this publication or parts thereof is permitted in connection with reviews or scholarly analysis. Permission for use must always be obtained from the copyright holder. Alle Rechte vorbehalten, auch das des auszugsweisen Nachdrucks, der auszugsweisen oder vollständigen Wiedergabe (Photographie, Mikroskopie), der Speicherung in Datenverarbeitungsanlagen und das der Übersetzung. Im Folgenden werden die Anforderungen für das SEP (Softwareentwicklungspraktikum) 2006 am Institut für Wissenschaftliches Rechnen der Technischen Universität Braunschweig bekannt gegeben. Einleitung Die CTL (Component Template Library) wurde am Institut für Wissenschaftliches Rechnen der Technischen Universität Braunschweig entwickelt und ist eine C++ Template Bibliothek zur Realisierung verteilter komponentenbasierter Software Systeme. In diesem Kontext ist eine Komponente ein Stück Software, bestehend aus einer Schnittstelle (CI = Component Interface) und einer Implementierung. CI und Implementierung sind über einen Kommunikationskanal miteinander verbunden. Die Kommunikation erfolgt mittels TCP/IP, der Kommunikationsbibliothek MPI, PVM, via Pipe, Thread oder Shared Library. Vergleichbare Produkte sind CORBA (Common Object Request Broker Architecture) Implementierungen, Microsofts .Net oder CCA (Common Component Architecture). In einem Verteilten System kommunizieren Prozesse miteinander über Prozess- bzw. Rechnergrenzen. Genauer betrachtet, kommunizieren die Objekte miteinander, die innerhalb der Prozesse „leben“. Befinden sich die Prozesse auf verschiedenen Rechnern, erfolgt die Kommunikation über das Netzwerk, indem die Rechner eingebettet sind. Ein Objekt, das von außen (außerhalb des Prozesses) angesprochen werden kann, wird von der aufrufenden Instanz eines anderen Prozesses als sogenanntes entferntes Objekt (remote object) wahrgenommen. Die damit verbundenen Aufrufe werden als entfernte Aufrufe (RMI = remote method invocation) bezeichnet. Ein Prozess, der ein entferntes Objekt aufruft, nimmt die Rolle des sogenannten Clients (Kunden) an. Der entfernte Prozess entspricht damit dem Server bzw. Service. Wird ein entferntes Objekt innerhalb eines CTL Systems aufgerufen, so ist dieser Aufruf technisch für den Programmierer völlig identisch zu einem lokalen Aufruf. Daher wird auch von OrtsTransparenz gesprochen. Tatsächlich resultiert ein Methodenaufruf typischerweise in zwei Nachrichten. Die erste Nachricht wird vom Client zum Server gesendet, der als Konsequenz die gewünschte Methode lokal aufruft. Die zweite Nachricht wird vom Server zum Client gesendet, um das Resultat des Methodenaufrufs zu liefern, siehe folgende Abbildung. Das Versenden einer Nachricht entspricht dem Schreiben in einen Kommunikationskanal, das Empfangen einer Nachricht dem Lesen von dem Kanal. Diese Begriffe werden im Folgenden als Synonym für das Versenden und Empfangen von Nachrichten verwendet. RMI call Client Server RMI answer Ziel Durch die Kommunikation über Prozess- bzw. Rechnergrenzen treten weitere Fehlerquellen bei der Implementierung von Verteilten Anwendungen auf, wie beispielsweise verlorene Verbindungen oder das Rebooten eines entfernten Rechners. Das Ziel ist daher die Realisierung eines Debuggers für Verteilte Komponentensysteme auf Basis der CTL. Der Debugger muss über einen Prompt zur Eingabe von Debuggerkommandos, einem interaktiven Visualisierer von Kommunikationsaktivitäten sowie einer Anbindung zu einem existierenden Java bzw. C++ Debugger für monolithische Programme verfügen, um auch eine Komponente selbst debuggen zu können. Zur Fehlersuche in einem Komponentensystem müssen alle zugehörigen Komponenten im DebugModus angestartet werden. Der Debugger übernimmt als außenstehende Instanz die Rolle des Kontrollorgans. Hierzu steuert er den Ablauf der Komponenten, indem er sie anhält bzw. weiterlaufen lässt (vergleichbar mit einer Verkehrsampel). Schaut man genauer hin, so führt der Debug-Modus dazu, dass die Komponenten sich selber anhalten. Der Debugger kontrolliert somit lediglich das Weiterlaufen der angehaltenen Komponenten. Die Kommunikation zwischen Debugger und Komponenten erfolgt ausschließlich über Nachrichten. Die Nachrichten des Debuggers werden als Control-Nachrichten bezeichnet, da sie den Ablauf der Komponenten steuern. Sämtliche Nachrichten, die eine Komponente verschickt, werden als Kopie zuerst an den Debugger gesendet. Im Detail führt eine Komponente, die im Debug-Modus angestartet wurde, Anweisungen aus bis sie einen Schreibbefehl auszuführen, also eine Nachricht zu schreiben hat. Sie schickt die Kopie der Nachricht an den Debugger und wartet auf eine Continuous-Nachricht (spezielle ControlNachricht) des Debuggers. Hat sie diese erhalten, schickt sie die Nachricht an den eigentlichen Empfänger und führt alle Anweisungen bis zum nächsten Schreibbefehl aus. Da der Debugger den Aufruf vor dem tatsächlichen Aufruf im Visualisierer anzeigt, wird das Identifizieren von Fehlerquellen erleichtert. Dies entspricht der Vorgehensweise monolithischer Debugger. Der Debugger erhält zur Laufzeit alle Nachrichten, die Komponenten nach der nächsten Continuous-Nachricht schreiben werden (siehe oben). Er speichert alle IP-Adressen und PortNummern der Komponenten, die nach der letzten Continuous-Nachricht eine Kopie der als nächstes zu verschickenden Nachricht versendet haben, in einer Liste. Folglich warten alle Komponenten aus dieser Liste auf eine Continuous-Nachricht (Reply des Debuggers). Es müssen nicht alle Komponenten des Systems in dieser Liste zu finden sein. Zum Einen taucht eine Komponente, die beschäftgt ist, aber bislang keine Nachricht zu verschicken hat, in der Liste nicht auf. Zum Anderen sind auch Komponenten, die nicht beschäftigt sind, also auf Aufträge warten, nicht in der Liste gespeichert. Das Versenden von Continuous-Nachrichten wird dem Debugger durch den Benutzer über Debuggerkommandos in einem Prompt mitgeteilt. Generell wird eine Control-Nachricht an alle Komponenten der Liste gleichzeitig verschickt (Broadcasting). Der momentane Zustand des Komponentensystems wird von Komponenten, den entfernten Objekten und den letzten Kommunikationen, also der nach dem nächsten Continuous-Lauf zu verschickenden Nachrichten, beschrieben. Möchte der Benutzer den nächsten Zustand des Systems einsehen, beauftragt er den Debugger mit der Versendung der nächsten Continuous-Nachricht. Allerdings kann das Warten des Benutzers auch die Folge haben, das eine weitere Komponente den Punkt erreicht hat, an dem sie einen Schreibbefehl auszuführen hat und demnach eine Nachrichtenkopie verschickt. Der Zustand ist daher dynamisch und soll durch den Visualisierer als solcher auch angezeigt werden. Diese Vorgehensweise erhält die Dynamik des Systems und zwängt dieses nicht in ein vollständig getaktetes System mit festem Taktschritt. Es sei zudem angemerkt, dass Komponenten durch andere Komponenten angestartet oder auch kontrolliert zerstört werden können, also das Komponentensystem dynamisch anwachsen oder auch schrumpfen kann. Allerdings müssen dem Debugger zu Beginn sämtliche zur Laufzeit angestarteten Komponenten bekannt sein. Hierzu liest der Debugger in einer initialen Phase die Komponentenschnittstellen (CIs) aller Komponenten ein. Der Debugger kann in zwei Softwareteile zerlegt werden, einer Hauptroutine und den Mirror. Die Hauptroutine wird durch den Benutzer zum Debuggen des Komponentensystems aufgerufen. Der Mirror stellt einen Spiegel zum Komponentensystem dar. Er wird als CTL Komponente implementiert, der aber im Vergleich zu den Komponenten des Systems nicht im Debug-Modus angestartet wird. Im Mirror werden die in der Initialisierungsphase eingelesenen CIs implementiert. Demnach bildet dieses implementierte Komponentensystem auf der Seite des Debuggers hinsichtlich der Schnittstellen einen Spiegel zum eigentlichen Komponentensystem. Die spiegelnden Komponentenimplementierungen sollen dann den Visualisierer direkt beeinflussen, das heißt die Kommandos zum Zeichnen der einzelnen Grafikbausteine selber aufrufen, siehe die folgende Abbildung (zum Visualisierer gleich mehr). Zur Generierung der spiegelnden Implementierungen ist ein Code-Generator erforderlich. Die Realisierung des Mirrors als Komponente hat einen entscheidenen Vorteil. Da die Nachrichten zwischen Debugger und Komponenten des Komponentensystems binär codiert sind, gilt es diese zu verstehen. Demnach muss der Debugger in der Lage sein, selber Nachrichten schreiben, aber auch eintreffende Nachrichten lesen zu können. Als CTL-Komponente übernimmt der Mirror automatisch die Konvertierung der binären Daten. Eine Re-Implementierung des CTL Protokolls ist daher nicht nötig. Als Konsequenz müssen die Kopien der Nachrichten durch die Komponenten des Systems direkt an den Mirror geschickt werden. Wird eine Komponente während des Debug-Modus angestartet, erhält sie daher die IP-Adresse und Port-Nummer des Mirrors. Debugger Port Komponentensystem Mirror Comp2:: Comp2:: Obj2 Obj2 Obj3 Obj1 Obj4 Comp1:: Comp3:: Obj3 Obj1 Obj4 Comp1:: Comp3:: Der Visualisierer soll den derzeitigen Zustand des Komponentensystems in Form eines Graphen erfassen. Der Zustand wird durch das vom Benutzer eingegebene Debuggerkommando initiiert. Der Graph umfasst die Komponenten und entfernten Objekte (zum Beispiel durch Kreise dargestellt), als auch die zuletzt stattgefundenen Kommunikationen (zum Beispiel durch Pfeile dargestellt). Des Weiteren soll jede Kommunikation interaktiv anwählbar sein, um so nähere Informationen über die Kommunikation zu erhalten. Diese Informationen beinhalten die Argumente bzw. Ergebnisse des mit der Nachricht verknüpften Methodenaufrufs, sofern sinnvoll. Zum Beispiel sollen Vektoren mit relativ vielen Einträgen nicht angezeigt werden. Zudem soll graphisch ersichtlich sein, zu welcher Komponente ein Objekt gehört, siehe folgende Abbildung. Es bietet sich an, die Objekte einer Komponente als Verbund graphisch kenntlich zu machen. Eintreffende Nachrichten sollen durch den Debugger im Graph des Visualisierers umgehend aktualisiert werden. Somit wird die Dynamik des Systems durch den Visualisierer erfasst. Komponenten, die seit dem letzten Continuous-Lauf keine Nachricht verschickt haben, sollen im Graphen kenntlich gemacht werden (zum Beispiel durch blasse Farben). Natürlich soll auch das dynamische Anwachsen bzw. Schrumpfen des Systems in der Visualiserung abgebildet werden. Dies ergibt sich bei korrekter Implementierung allerdings automatisch. Die Komponenten, Objekte und Kommunikationen sollen im Graphen automatisch und sinnvoll angeordnet werden. Der Benutzer soll des Weiteren auch in der Lage sein, interaktiv die Lage der Komponenten und Objekte zu bestimmen. Möchte der Benutzer ein n-fachen Continuous-Lauf (n Continuous-Nachrichten hintereinander) durchführen, so wäre eine Möglichkeit, die Liste der Komponenten (siehe oben) so oft zu durchlaufen bis jede zu Beginn des Durchlaufs vermerkte Komponente n Nachrichtenkopien verschickt hätte. Es ist möglich, dass dieser Vorgang nicht terminiert oder zumindest für den Benutzer zu lange dauert. Daher soll der Benutzer die Möglichkeit haben die Ausführung eines solchen Befehls zu stoppen. RMI call double 2.00 double 0.5 Comp1:: Obj1 Comp2:: RMI call method2 Obj2 Ebenso muss ein existierender Java bzw. C++ Debugger eingebunden werden, mit dem eine Komponente selbst geöffnet werden kann. Dies soll über einen Systemcall geschehen. Zum Abschluss soll nun die Arbeitsweise des Debuggers zusammengefasst werden. Der Debugger liest zu Beginn alle CIs der Komponenten des Komponentensystems ein. Aus diesen erzeugt er mittels Code-Generator die spiegelnden Komponenten auf der Seite des Debuggers. Die Implementierungen der spiegelnden Komponenten beeinflussen direkt den Visualisierer, das heißt, eine eintreffende Nachrichtenkopie einer Komponente resultiert umgehend in der Aktualisierung des Zustandsgraphen des Komponentensystems. Komponenten, die noch keine Nachricht innerhalb des momentanen Zustands verschickt haben, sollen als solche kenntlich gemacht werden. Der Benutzer soll die Möglichkeit haben, interaktiv Kommunikationsinformationen zu erhalten und den Graphen selber anzuordnen. Des Weiteren soll auch die Möglichkeit des direkten Debuggens durch einen Debugger für monolithische Programme bereitgestellt werden. Zur Entwicklung des Debuggers müssen eine Reihe bestehender Tools bzw. Bibliotheken zum Einsatz kommen. Insbesondere für die Graphendarstellung, das Parsen der CIs mit anschließender Code-Generierung sowie den Java bzw. C++ Debugger für monolithische Programme müssen entsprechende Tools bzw. Bibliotheken gefunden und auf Funktionalität und Zuverlässigkeit geprüft und dementsprechend ausgewählt werden. Wünschenswert wäre noch, sich auch vergangene Zustände (Zustandshistorie) im Debugger ansehen zu können. Demnach müssten die einzelnen Zustände chronologisch richtig abgespeichert werden. Einsatz Der zu entwickelnde Debugger muss in vollem Umfang unter Linux lauffähig sein. Als Programmiersprache soll Java verwendet werden. Daher muss auf die Java CTL Realisierung CTL4j zurückgegriffen werden. Übersicht In der folgenden Zusammenfassung wird der zu entwickelnde Debugger noch einmal aufgezeigt. • • • • • • Debugger als CTL Komponente, Code-Generator und Spiegel des Komponentensystems Parser zur Konvertierung der Komponentenschnittstellen (CIs) Prompt zur Eingabe von Debuggerkommandos durch den Benutzer Interaktiver Visualisierer von Kommunikationsaktivitäten, Graphendarstellung des Komponentensystemzustands, interaktives Anwählen von Kommunikationen im Graphen Schnittstelle zu existierendem Java bzw. C++ Debugger für monolithische Programme Einsicht in Zustandshistorie wünschenswert Die technische Produktumgebung ist wie folgt. • Betriebssystem: Linux • Programmiersprache: Java • Entwicklungsumgebung: eclipse • Testwerkzeuge: JUnit • Tools/Bibliotheken: CTL4j und weitere (Analyse notwendig) Funktionen Die im Folgenden angegebenen Funktionalitäten des zu entwickelnden Debuggers sind lediglich aus Sicht des Benutzers. Der Prompt zur Eingabe von Debuggerkommandos muss mitunter die folgenden Kommandos ausführen können. • run prog: Starten des zu prüfenden Programms • next arg: Zeige den arg-ten Zustand des Komponentensytems, der dem derzeitigen Zustand folgt (für den Entwickler: schicke arg Continuous-Nachrichten an alle Komponenten) • break on arg: Zeige den nächsten Zustand, in dem Komponente, Objekt, Funktion oder Methode arg aufgerufen wird und halte dort an • list: Auflisten aller Komponenten im Prompt • debug arg: Öffne Komponente arg im Java bzw. C++ Debugger für monolithische Programme • more arg: Spiegeln der Kommunikationsinformation zur Funktion/Methode arg im Prompt Der Visualisierer muss, wie oben beschrieben, den derzeitigen Zustand des Komponentensystems anzeigen. Über das interaktive Anwählen einer Kommunikation innerhalb des Graphen müssen nähere Informationen (Argumente bzw. Rückgabewerte) über die Kommunikation geliefert werden. Graphenbausteine sollen automatisch und sinnvoll angeordnet werden, allerdings auch durch den Benutzer verschoben werden können. Mittels existierendem Java bzw. C++ Debugger für monolithische Programme muss eine gewünschte Komponente geöffnet werden können. Zu guter letzt wäre noch eine Einsicht in die Zustandshistorie des Komponentensystems wünschenswert. Dem Benutzer wäre so möglich den Weg zum derzeitigen Zustand historisch zurückzuverfolgen. Qualitätsanforderungen Die Qualitätsanforderungen an das zu entwickelnde Produkt sind im Folgenden angegeben. Benutzbarkeit Eine benutzerfreundliche Bedienung ist Voraussetzung. So sollen die Graphenbausteine des Visualisierers automatisch gesetzt aber auch auf Wunsch des Benutzers durch diesen manuell angeordnet werden können. Zuverlässigkeit Es wird auf ausgiebige Tests zum Erhalten eines stabiles Produktes Wert gelegt. Effizienz Optimierungen bezüglich der Effizienz sind nicht vorgesehen. Änderbarkeit Das Programmdesign muss für eine spätere Erweiterung ausgelegt sein. Neben einer sauberen Programmstruktur sollen die Kommentare nach Javadoc Vorgaben erfolgen. Programmcode und Kommentare sollen in Englisch verfasst werden. Klassen, Methoden, Variablen etc. sollen sinnvoll benannt werden. Zum Testen der Klassen soll unter anderem JUnit zum Einsatz kommen. Zugehörige schriftliche Ausarbeitungen sind wünschenswerterweise ebenso in Englisch auszuführen. Hierzu soll Latex verwendet werden. Bei der Wahl der zu verwendenen Tools/Bibliotheken muss darauf geachtet werden, dass diese mit hoher Wahrscheinlichkeit auch in Zukunft von den Herstellern gepflegt werden. Des Weiteren müssen alle Quellen in das institutsinterne CVS Repository eingespeist und in diesem während des SEPs gepflegt werden. Ergänzungen Die in diesem SEP entstandene Software muss unter LGPL gestellt werden. Auf der Webseite dieses Themas wird eine Studienarbeit zur Verfügung gestellt, in der genauer auf das CTL Protokoll sowie CTL4j eingegangen wird. Ansprechpartner für das SEP 2006 am Institut für Wissenschaftliches Rechnen ist Martin Krosche ([email protected]).