Du bist hier:Start»Linux»Entwicklung»UML

Sequenz-Diagramm automatisch generieren

03.04.2015

Small sequence diagram

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

automatisch generiertes Sequenz-Diagramm

Um dieses Diagramm erzeugen zu lassen sind folgende Schritte unter Linux notwendig:

# download the example application
wget http://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.

# add the tracer to your program
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.

$global_trace_mutex = Mutex.new

# 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:

# run the program and redirect stderr

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.

# convert the tracer output to plantuml input

./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:

# excerpt from the tracer output:

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.

# convert plantuml text file to an image with the sequence diagram

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.

Kommentar schreiben