Binäre Kommunikation mit JMeter messen

1 Kommentar

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 Configuration Screen

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

Sample transaction in text view

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 command call

hexdump Kommando

Diese Hexdarstellung des Requests kann nun einfach in das schon bekannte Textfeld der TCP Sampler Konfiguration eingefügt werden.

TCP Sampler with classname and hex request

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 set up

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:

Final Test Plan configuration

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

Sample Test Run

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.

Daniel Schneller beschäftigt sich seit über 15 Jahren mit dem Entwurf und der Umsetzung komplexer Software- und Datenbanksysteme und ist unter anderem Autor des MySQL Admin Cookbook. Derzeit konzentriert er sich bei der CenterDevice GmbH als Princial Cloud Engineer auf OpenStack und Ceph basierte Cloudsoftware. Gemeinsam mit Lukas Pustina hat er die CloudFibel aus der Taufe gehoben.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentare

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.