Im Rahmen eines kürzlich durchgeführten Projekts habe ich eine Verbindungskomponente entwickelt, die einen Backend-Webservice mit einem Kreditkartenterminal koppelt. Das Terminal spricht ausschließlich ein spezielles Binärprotokoll, das auf die entsprechenden Calls im Backend abgebildet werden musste. Wer sich für die Details interessiert, kann in der Wikipedia mehr zum sog. GICC Protokoll erfahren.
Um die Wartezeiten auf Endkundenseite zu minimieren, waren vom Auftraggeber Ziele hinsichtlich der erlaubten Verarbeitungszeit pro Transaktion vorgegeben. Jede einzelne Transaktion muss innerhalb einer Sekunde verarbeitet werden, einschließlich der Zeit, die vom Backend-Service benötigt wird. Ein wichtiger Teil des Entwicklungs- und Test-Prozesses waren daher Lasttests und Profiling, um sicherzustellen, dass auch bei Lastspitzen alle Transaktionen innerhalb der erlaubten Zeit abgeschlossen werden.
Für die Lasttests wollte ich JMeter einsetzen, allerdings schien dies auf den ersten Blick nur “die üblichen” Protokolle zu unterstützen, ohne dass gleich eine eigene Extension programmiert werden muss. Da der Zeitplan einigermaßen knapp kalkuliert war, schien mir ein spezifisches Lasttest-Tool zunächst sinnvoller (da besser kalkulierbar), als die Einarbeitung in die Erweiterungsmöglichkeiten von JMeter. Zuvor stieg ich allerdings noch einmal ein wenig tiefer in die JMeter Dokumentation ein und stieß in der Tat auf eine Passage, die sich mir vorher verborgen hatte.
Weil ein Kollege das dort beschriebene Feature auch nicht direkt gefunden hatte, schien es uns angebracht, ein bisschen Werbung dafür zu machen.
JMeter TCP Sampler
Im Lieferumfang von JMeter befindet sich ein TCP Sampler , über den die Dokumentation folgendes beinhaltet:
opens a TCP/IP connection to the specified server. It then sends the text, and waits for a response.
Wenn man einen TCP Sampler in einen Testplan einfügt, stellt sich das in der GUI so dar (Bild aus der JMeter Dokumentation):
TCP Sampler Konfiguration
Wie man sieht, gibt es ein “Text to send” Eingabefeld. Das ist vollkommen ausreichend, wenn das Ziel des Testlaufs ist, einen Server, der ein Klartextprotokoll spricht, zu messen (wie z. B. SMTP – wobei es dafür allerdings bereits eine spezielle Implementierung in JMeter gibt). Für ein Binärprotokoll jedoch eignet sich ein einfaches Textfeld eher nicht. Im vorliegenden Fall sieht eine Beispielnachricht ungefähr so aus, wenn man sie in reinen Text “zwängt”:
Beispieltransaktion in Textdarstellung
Ganz offensichtlich kann man nicht erwarten, dass sich dies 1:1 in das Textfeld einfügen lässt und dann auch noch funktioniert.
Allerdings gibt es für den TCP Sampler eine mächtige Einstellung, die sich ein wenig unscheinbar hinter dem Textfeld mit der Beschriftung „TCPClient classname“ verbirgt. In der Tabelle, die in der Dokumentation die Parameter des TCP Samplers beschreibt, steht lediglich, dass dieser Wert optional ist, und dass er “Defaults to the property tcp.handler, failing that TCPClientImpl.”
TCP Client Class
Es gibt drei Implementierungen, die JMeter für die oben genannte TCP Client Class mitbringt. Die Standardauswahl ist TCPClientImpl
, die das tut, was oben beschrieben wird: Text an den Server senden und auf die Antwort warten.
Für meinen Anwendungsfall interessanter waren hingegen BinaryTCPClientImpl
und deren naher Verwandter LengthPrefixedBinaryTCPClientImpl
.
Sie werden wie folgt beschrieben:
BinaryTCPClientImpl
This implementation converts the GUI input, which must be a hex-encoded string, into binary, and performs the reverse when reading the response. When reading the response, it reads until the end of message byte, if this is defined by setting the property tcp.BinaryTCPClient.eomByte , otherwise until the end of the input stream.
LengthPrefixedBinaryTCPClientImpl
This implementation extends BinaryTCPClientImpl by prefixing the binary message data with a binary length byte. The length prefix defaults to 2 bytes. This can be changed by setting the property tcp.binarylength.prefix.length .
Das ist für den vorliegenden Fall schon deutlich angemessener. Eine binäre Nachricht in einen String von Hexwerten umzuwandeln ist sehr einfach. Entweder man verwendet dazu einen Editor, der das schon mitbringt – zum Beispiel den sehr nützlichen Hex Fiend (Mac) – oder man wechselt einfach auf “Die gute alte Kommandozeile”™:
hexdump Kommando
Diese Hexdarstellung des Requests kann nun einfach in das schon bekannte Textfeld der TCP Sampler Konfiguration eingefügt werden.
TCP Sampler mit Klassenname und Hex-Request
Das ist soweit schon ganz brauchbar, allerdings skaliert dieser Ansatz für mehr als ein paar vereinzelte Nachrichten nicht besonders gut. Glücklicherweise können die anderen JMeter Features aber leicht mit dem TCP Sampler kombiniert werden.
Externe Datenquellen und Variablen
In meinem konkreten Fall gab es mehrere Transaktionstypen, jede mit einer speziellen Binäranfrage. Innerhalb dieser Anfragen sind jedoch variable Ersetzungen vorzunehmen, speziell für die Felder Barcode und Transaktionsbetrag. JMeter bietet einen Mechanismus zur Variablenersetzung an, die sich sogar über externe Datenquellen steuern lässt, zum Beispiel über CSV Dateien.
Für jede Testiteration wird aus der externen Datei dann eine neue Zeile ausgelesen und verwendet, um die JMeter Variablen neu zu setzen. Die Zuordnung von Spalten in der Datei auf Variablennamen ist einstellbar. Also legte ich zunächst eine Datendatei an, die wie folgt aufgebaut war:
58622199999950564FFF,000000000066
58622199999950606FFF,000000006622
58622199999950648FFF,000000001133
...
Diese speicherte ich dann als a_transactions.csv
im gleichen Verzeichnis wie den JMeter Testplan ab.
Anschließend fügte ich ein CSV Data Set Config Element ein und trug dort den Dateinamen ein:
CSV Data Set Config Einstellungen
In der dargestellten Konfiguration sind die Variablennamen in der gleichen Reihenfolge aufgeführt, wie die Werte in der CSV Datei angeordnet sind. Darüber hinaus sorgen die weiteren Einstellungen dafür, dass die Zeilen aus der genannten Datei nur innerhalb der aktuellen Thread Group verwendet werden (später mehr dazu), und dass die Datei bei Erreichen ihres Endes wieder von vorn an gelesen wird. Dadurch lassen sich kontinuierlich laufende Tests durchführen. Natürlich können diese Einstellungen je nach Geschäftsvorfall unterschiedlich zu wählen sein.
Damit konnte ich nun schließlich den bisher fixen Hex-String anpassen, so dass darin an den geeigneten Stellen die Variablen verwendet werden:
007d...f1f0${barcode}...${amount}...f0f2...
Am Ende sah der vollständige Testplan wie folgt aus:
Abschließende Testplan Konfiguration
Wie man im Screenshot sehen kann, enthält der Testplan noch weitere (globale) Variablen, mit denen sich einige Einstellungen leicht zentral vornehmen lassen. Konkret steuern sie hier die verschiedenen Thread Groups und deren Bestandteile. Im konkreten Fall ist jede Thread Group für einen bestimmten Typ Geschäftsvorfall eingerichtet. Durch Veränderungen an den globalen Variablen und durch das Ein- und Ausschalten einzelner Thread Groups erreicht man ein gutes Maß an Flexibilität mit nur einem Testplan.
Lässt man den Plan ablaufen, wird man mit einem netten Diagramm belohnt (abhängig von den eingerichteten Post Processing Schritten):
Beispieltestlauf
Ein interessantes Detail, das man beim Einsatz von externen Datenfiles wissen sollte, ist, dass diese – anders als der Testplan selbst – bei verteilten Tests nicht automatisch auf die entfernten Knoten übertragen werden. Bei Bedarf sind also die CSV Dateien manuell auf alle verwendeten Knoten zu kopieren. Natürlich müssen sie nicht inhaltlich identisch sein, aber es muss gewährleistet sein, dass JMeter alle im Testplan eingetragenen Dateinamen auf jedem Knoten auflösen kann.
Fazit
Obwohl es zunächst nicht den Anschein macht, ist JMeter durchaus zum Testen und Messen von binären Kommunikationswegen geeignet, auch ohne dass man eigene Plugins für jedes konkrete Protokoll erstellen müsste. Voraussetzung dafür ist nur, dass die Messung sich vornehmlich um einfache Ende-zu-Ende Zeit- und Durchsatzmessungen dreht. Außerdem ist es von Vorteil, wenn das vorliegende Protokoll sich irgendwie mit den vorhandenen Variablenersetzungen verbinden lässt.
Natürlich kann der Aufwand für die Erstellung der entsprechenden Datenfiles und Ersetzungskonfigurationen bei komplexeren Kommunikationsprotokollen höher sein, aber dennoch sind die Möglichkeiten der bereits mitgelieferten Komponenten sicher in vielen Fällen ausreichend.
Weitere Beiträge
von Daniel Schneller
Dein Job bei codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
Weitere Artikel in diesem Themenbereich
Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog-Autor*in
Daniel Schneller
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.