Sequenz-Diagramm automatisch generieren
03.04.2015

Ein Sequenz-Diagramm stellt den Ablauf eines Software-Programms in einem Bild dar. Wer sich in ein Software-Projekt mit wenig bis keiner Dokumentation einarbeiten soll, wird sich eine Möglichkeit zum automatischen Erzeugen von Sequenz-Diagrammen wünschen. Bisher hatte ich den Aufwand zum Einarbeiten in einen Compiler und den Abstract Syntax Tree (AST) gescheut. Als ich von der Programmiersprache C++ auf Ruby wechselte, habe ich einen neuen Anlauf genommen. Mit der Programmiersprache Ruby und dem Programm Plantuml ist es mittlerweile relativ einfach möglich sich ein Sequenz-Diagramm automatisch aus einem laufenden Programm erzeugen zu lassen.
Beispiel-Diagramm
Das folgende Sequenz-Diagramm wurde automatisch aus einem Beispiel-Programm erzeugt. Die verschiedenen Threads des Programms werden mit verschiedenen Farben der Pfeile dargestellt.

automatisch generiertes Sequenz-Diagramm
Um dieses Diagramm erzeugen zu lassen sind folgende Schritte unter Linux notwendig:
wget https://www.torsten-traenkner.de/linux/development/generate_sequence_diagram_0.1.tgz
# extract and generate the diagram
tar xzvf generate_sequence_diagram_0.1.tgz
cd generate_sequence_diagram_0.1
./generate.sh
# display the diagram
eog plantuml_sequence_diagram.png
Zustand der aktuellen Implementierung
Im Augenblick zeige ich hier nur ein Proof-of-Concept. Eine Weiterentwicklung zu einem ausgereiften Produkt plane ich nicht. Folgende Ruby-Features funktionieren bisher:
- verschiedene Objekte derselben Klasse darstellen (Beispiel: Class3)
- Objekte in Modulen bzw. Namespaces (Beispiel Namespace::Class2)
- Vererbung von Methoden (Beispiel Class2 erbt von Class4)
- mehrere Threads (im Beispiel schwarz, blau und rot dargestellt)
- Ursprung der Threads (durch einen "Self-Pfeil" dargestellt)
Technische Details zur Funktionsweise
Um Ausschriften über Funktionsaufrufe und Threads aus dem Beispiel-Programm zu erhalten, muss die Datei tracer.rb eingebunden werden. Sie ist bereits mit in der Demo-Anwendung enthalten.
require './tracer.rb'
Listing der Datei tracer.rb
Zum Verfolgen (Tracen) von Funktionsaufrufen bietet Ruby die komfortable Funktion set_trace_func zur Instrumentalisierung des Quellcodes. Die Ausschriften können durch $stderr.puts nach Standard Error (stderr) und in eine Datei umgeleitet werden.
# overwrite Thread.new to find the source of the new thread
class Thread
alias :old_initialize :initialize
def initialize(*args, &proc)
callingClass = eval("self.class", proc.binding)
callingClassID = eval("self.object_id", proc.binding)
new_proc = proc do |*args, &block|
$global_trace_mutex.synchronize do
$stderr.puts "new_thread: #{callingClass}_#{callingClassID} thread_#{Thread.current.object_id}"
end
proc.call(*args, &block)
end
old_initialize(*args, &new_proc)
end
end
set_trace_func proc { |event, file, line, id, binding, classname|
if ["call","return"].include?(event) and not ["Thread","Mutex", "Kernel"].include?(classname.to_s)
object_id=eval("self.object_id", binding)
thread_id=eval("Thread.current.object_id", binding)
$global_trace_mutex.synchronize do
$stderr.puts "#{event}: " + classname.to_s + "_#{object_id}." + id.to_s + " thread_#{thread_id}"
end
end
}
Mit dem Tracer schreibt das Beispiel-Programm folgende exemplarische Ausschriften nach Standard Error:
ruby main.rb 2> sequence_output.txt
# example output of tracer.rb
cat sequence_output.txt
call: Class1_11059060.startThread thread_9904260
return: Class1_11059060.startThread thread_9904260
new_thread: Class1_11059060 thread_11151300
call: Class3_11180300.initialize thread_11151300
return: Class3_11180300.initialize thread_11151300
Dabei ist:
- call der Aufruf einer Funktion
- return die Rückkehr aus einer Funktion und
- new_thread das Starten eines neuen Threads (ein paralleler Ablauf)
Desweiteren enthält jede Zeile den Namen der Klasse des Objekts, die Objekt-ID als Nummer, den aktuellen Funktionsnamen bzw. Methodennamen und die Thread-ID als Nummer. Anhand der Thread-ID läßt sich herausfinden von welchem Thread aus die Funktionen aufgerufen wurden. Mit diesen Informationen lassen sich in dem Sequenzdiagramm auch parallele Abläufe darstellen.
Aufbereitung der Ausschriften für Plantuml
Die Ausschriften des Tracers lassen sich noch nicht mit dem Programm Plantuml zu einem Bild verarbeiten. Zur Umwandlung der Textformate des Tracers zu Plantuml habe ich ein kleines Shell-Skript geschrieben. Dieses Shell-Skript "sequenceDiagramConverter.sh" ist in der Demo-Anwendung enthalten.
./sequenceDiagramConverter.sh sequence_output.txt > plantuml_sequence_diagram.txt
Der folgende kleine Auszug gibt einen Eindruck davon, wie die Ausschriften in eine Plantuml-Datei umgewandelt werden:
call: Class3_11360320.initialize thread_11340720
call: Class1_11222820.someMethod thread_11340720
return: Class1_11222820.someMethod thread_11340720
return: Class3_11360320.initialize thread_11340720
# excerpt from the converted plantuml file
participant main
participant "Class1" as 11222820
participant "Class3" as 11360320
main [#ff0000]-> 11360320 ** : initialize
11360320 [#ff0000]-> 11222820 : someMethod
11222820 -[#ff0000]-> 11360320
11360320 -[#ff0000]-> main
Für die Plantuml-Datei müssen zunächst die beteiligten Objekte definiert werden. Das sind im Beispiel die Main-Funktion, ein Objekt von Class1 und ein Objekt von Class3. Die Nummer hinter Participant ist die Objekt-ID, die zur eindeutigen Identifizierung der einzelnen Objekte benutzt wird. Die Pfeile "->" stellen die Funktionsaufrufe und Rücksprünge aus den Funktionen dar. Die Farbe der Pfeile ist dem jeweiligen Thread zugeordnet. Im oberen Beispiel erzeugt die main-Funktion mit initialize ein neues Objekt von Class3. Im Konstruktor von Class3 wird die Funktion someMethod von Class1 aufgerufen. Und schließlich kehren beide Funktionsaufrufe wieder zurück (mit einem Return-Pfeil dargestellt "-->").
Sequenzdiagramm mit Plantuml erzeugen
Schließlich erzeugt Plantuml aus der umgewandelten Text-Datei ein Bild mit einem Sequenzdiagramm wie es weiter oben auf dieser Seite zu sehen ist.
java -jar plantuml.jar plantuml_sequence_diagram.txt
Wenn alles funktioniert hat, dann wurde das Bild: plantuml_sequence_diagram.png erstellt.
Ausblick
Wie oben bereits geschrieben ist die Beispiel-Anwendung nur ein Proof-of-Concept. Es gibt vermutlich einige Sprachfeatures von Ruby, die mit dem Konzept noch nicht abgedeckt sind. Ich würde mich freuen, wenn jemand das Konzept weiterentwickelt und sich hier meldet.
Viel Spaß beim Experimentieren! Falls noch etwas unklar sein sollte, dann kannst du die Kommentar-Funktion benutzen.