Ingenieurbüro David Fischer AG | A Company of the Apica Group http://www.proxy-sniffer.com "Lasttest Plug-In" Entwicklungshandbuch Version 4.6 Deutsche Ausgabe © 2008, 2009, 2010, 2011, 2012 Alle Rechte vorbehalten. Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch Inhaltsverzeichnis 1 Übersicht ...........................................................................................................................3 1.1 Inhalt dieses Dokuments ............................................................................................3 1.2 Einführung ..................................................................................................................3 1.3 Neue Funktionalitäten ab Proxy Sniffer Version 4.5....................................................3 1.4 Neue Funktionalitäten ab Proxy Sniffer Version 4.6....................................................3 2 Eigenschaften, Laufzeitverhalten und Konfiguration ..........................................................4 2.1 Laufzeitumgebung ......................................................................................................4 2.2 Lebenszyklus von Plug-Ins während der Testausführung ...........................................5 2.3 Konfiguration von Plug-Ins mittels des GUIs ..............................................................6 2.4 Lebenszyklus von Plug-Ins nach der Konfiguration ....................................................8 3 Entwicklung eigener Plug-Ins ............................................................................................9 3.1 Java API Dokumentation ............................................................................................9 3.2 Verwenden von mehreren Klassen und von externen Class-Libraries ........................9 3.3 Erstellen des Programm-Skeletts mit dem GUI-Assistenten ..................................... 11 3.4 Programmierung der Plug-In Funktionalität .............................................................. 14 3.4.1 Klasse LoadtestPluginContext ........................................................................... 16 3.4.2 Klasse HttpLoadTest ......................................................................................... 17 3.4.3 Spezielle Hinweise zur Laufzeitumgebung ........................................................ 18 3.4.3.1 Debug-Output während der Plug-In Ausführung ......................................... 18 3.4.3.2 Initialisierung eines Plug-Ins mittels importierten GUI-Variablen................. 18 3.4.3.3 Extrahieren von Daten aus der HTTP-Response von URL-Calls ................ 19 3.4.3.4 Ausführen von Plug-Ins am Ende eines Loops ........................................... 20 3.4.3.5 Plug-Ins bei Cluster-Jobs ........................................................................... 20 3.4.3.6 Integrieren von zusätzlichen (externen) Messdaten ................................... 21 3.4.3.7 Versenden von E-Mails mittels SMTP ........................................................ 26 3.4.3.8 Umgang mit Zeitzonen und Datums-Berechnungen ................................... 26 3.4.3.9 Erzeugen von Plug-In spezifischen Fehler-Arten ........................................ 27 3.4.4 Performante Plug-In Programmierung ............................................................... 29 3.4.4.1 Vorberechnen von Ergebnissen ................................................................. 29 3.4.4.2 Disk und Netzwerk I/O-Operationen ........................................................... 30 3.5 Plug-In Programm-Beispiele..................................................................................... 31 4 Hersteller ......................................................................................................................... 32 © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 2 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch 1 Übersicht 1.1 Inhalt dieses Dokuments Dieses Handbuch gibt in einem ersten Teil einen Überblick über die Eigenschaften, das Laufzeitverhalten und die Konfiguration von Proxy Sniffer Lasttest Plug-Ins. In einem zweiten Teil wird auf die Entwicklung eigener Plug-Ins eingegangen. 1.2 Einführung Proxy Sniffer Lasttest Plug-Ins sind Erweiterungs-Module zum Proxy Sniffer Produkt, welche über das GUI konfiguriert werden und während der Durchführung eines Lasttests zur Ausführung kommen. Nebst bereits vorgefertigten Plug-Ins, welche mit dem Produkt zusammen ausgeliefert werden, lassen sich auch eigene Plug-Ins erstellen, wodurch zusätzliche Funktionalitäten nach eigenem Bedarf dem Produkt hinzugefügt werden können. Plug-Ins habe unter anderem die Eigenschaft, dass diese wieder verwendbar sind. Ist ein neues Plug-In einmal erstellt, so kann dieses in jedem Lasttest-Programm verwendet werden. Zur Erstellung eines neuen Plug-Ins sind Java Programmier-Kenntnisse notwendig, wobei zur Erleichterung der Programmierung das Programm-Skelett eines Plug-Ins mittels eines GUIAssistenten automatisch erstellt werden kann. Die Einbindung eines bereits erstellten Plug-Ins in ein Lasttest-Programm benötigt hingegen keine Programmier-Kenntnisse, da dies über das GUI mittels weniger Maus-Klicks erfolgt. Die Ausführung eines Plug-Ins geschieht auf demselben Exec-Agent Rechner, auf welchem auch das Lasttest-Programm ausgeführt wird. Beim Starten eines Lasttests wird das entsprechende Plug-In automatisch zusammen mit dem Lasttest-Programm an den Exec Agenten übertragen, so dass auf diesem keine vorhergehende Installation des Plug-Ins notwendig ist. 1.3 Neue Funktionalitäten ab Proxy Sniffer Version 4.5 Ab Proxy Sniffer Version 4.5 können Plug-Ins zusätzlich: Break- oder Continue-Sprünge bei "Inner Loops" auslösen. Dem Lasttest-Resultat beliebige XY-Diagramme hinzufügen, deren Daten innerhalb eines Plug-Ins selbst gemessen wurden (Vor V4.5 war die X-Achse eines hinzugefügten Diagramms immer auf die aktuelle Zeit während der Lasttest-Ausführung beschränkt). 1.4 Neue Funktionalitäten ab Proxy Sniffer Version 4.6 Ab Proxy Sniffer Version 4.6 können Plug-Ins zusätzlich: XY-Diagrammen auch auf Cluster-Ebene erzeugen, wobei die Diagramm-Daten der einzelnen Lastgeneratoren entweder mittels Addition oder durch die Bildung eines "künstlichen Mittelwertes" automatisch von Proxy Sniffer vereinigt werden. Eigene Typen von kundenspezifischen Fehlermeldungen erzeugen welche in den "normalen" Detail-Diagrammen und -Daten der Lasttest-Resultate dargestellt werden. © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 3 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch 2 Eigenschaften, Laufzeitverhalten und Konfiguration Alle „Lasttest Plug-Ins“ werden im GUI mittels des Variablen-Handlers in das LasttestProgramm eingebunden. Solche Plug-Ins können: 1. GUI-Variablen importieren 2. Eigenen Programm-Code ausführen 3. GUI-Variablen exportieren, sowie das Lasttest-Programm vorzeitig abbrechen, oder auch weitere Messdaten dem Lasttest-Resultat hinzufügen. Beispiel 1: ein Plug-In errechnet ein Datum (MM.DD.YYYY), welches vom aktuellen Datum aus gesehen immer drei Tage in der Zukunft liegt. Das Datum wird als GUI-Variable exportiert. Diese Variable wird danach bei einem HTML-Formular als Input-Parameter zum dynamischen Setzen eines Buchungsdatums verwendet. Beispiel 2: bei einem Bestell-Vorgang wird mittels des Variablen-Handlers die BestellNummer zuerst als GUI-Variable aus einem URL-Call extrahiert und danach als Input-Variable einem Plug-In übergeben. Das Plug-In schreibt alle so gesammelten Bestell-Nummern aller simulierten Benutzer in ein File, welche nach der Ausführung des Lasttests zur Verfügung steht. Aufgrund dieses Files können später die Bestellungen storniert werden. Beispiel 3: ein Plug-In überwacht dauernd den Fortschritt des Lasttest und stoppt diesen, wenn innerhalb eins Zeitfensters von 5 Minuten mehr als 90% aller Loops fehlschlagen – gezählt über alle virtuellen Benutzer. Dadurch wird bei einem Dauertest über mehrere Stunden ein vorzeitiger Abbruch des Tests erzwungen, wenn der Web-Server kollabiert und sich nicht mehr von selbst „erholt“. 2.1 Laufzeitumgebung Plug-Ins sind stark mit dem Laufzeit-Daten eines Lasttests integriert und können Zugriff nehmen auf: - alle GUI-Variablen welche bereits im Variablen-Handler definiert wurden (Import wie auch Export von GUI-Variablen). - auf die aktuellen (real-time) Messdaten eines laufenden Lasttest-Programms. - Auf die Daten von bestimmten oder auch von allen ausgeführten URL-Calls eines laufenden Lasttest-Programms, inkl. der Möglichkeit diese vor der Ausführung abzuändern oder zu ergänzen, sowie der Möglichkeit, deren Ergebnisse nach der Ausführung weiter zu verarbeiten. - Auf das Betriebssystem des Exec Agenten sofern dessen Schnittstellen über Java zugänglich sind (z.B. auf Netzwerkverbindungen, Disk-Files etc.). - Auf weitere Daten der Laufzeitumgebung eines Lasttests wie z.B. die aktuelle Nummer eines virtuellen Benutzers, die aktuellen Nummer des Lasttest-ausführenden ClusterMembers (bei Cluster-Jobs) oder auch auf den Cookie-Speicher eines virtuellen Benutzers. Einschränkend gilt jedoch, dass ein Plug-In die Struktur des Programm-Ablaufs eines LasttestProgramms in der Regel nicht beeinflussen kann. So kann ein Plug-In z.B. die Reihenfolge der ausgeführten URL-Calls nicht beeinflussen. Ebenso wenig kann ein Plug-In bei einem aufgetretenen (gemessenen) Fehler eines URL-Calls dessen Ergebnis nachträglich „korrigieren“. Umgekehrt ist es jedoch einem Plug-In möglich, einen erfolgreichen URL-Call in einen Fehler zu verwandeln. © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 4 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch 2.2 Lebenszyklus von Plug-Ins während der Testausführung Ein Plug-In wird zuerst initialisiert, danach ein- oder mehrmals ausgeführt, und schlussendlich de-initialisiert. Oft erfolgen währen der Initialisierung und De-Initialisierung keine speziellen Aktionen, d.h. dass diese Schritte oftmals „leer“ sind. Anderseits könnte ein Plug-In z.B. während eines Lasttests auch eine Verbindung zu einer externen Datenbank aufnehmen. In einem solchen Fall würde die Verbindung zur Datenbank während der Initialisierung des Plug-Ins aufgebaut, die Plug-In Daten während der Ausführung des Plug-Ins von der Datenbank gelesen, bzw. in diese geschrieben, und bei der De-Initialisierung des Plug-Ins die Verbindung zur Datenbank geschlossen. Der Zeitpunkt der der Initialisierung + De-Initialisierung, sowie der weitere Zeitpunkt der PlugIn Ausführung lässt sich getrennt d.h. unterschiedlich festlegen. Diese Zeitpunkte sind eng mit den Daten verbunden, auf welche das Plug-In zugreifen kann und werden darum auch als Scope bezeichnet. Folgende Scopes sind möglich: global: unmittelbar vor oder nach der Ausführung des Lasttest-Programms user: beim Erzeugen oder beim Beenden eines virtuellen Benutzers loop: am Anfang oder am Ende jedes ausgeführten Loops URL: vor oder nach dem Aufruf eines bestimmten oder auch aller URL-Calls Der gebräuchlichste Scope bei der Initialisierung + De-Initialisierung eines Plug-Ins ist global. Bei der Ausführung eines Plug-Ins ist der gebräuchlichste Scope jedoch loop oder URL. Liste der möglichen Initialisierungs-Scopes eines Plug-Ins: Initialisierungs-Scope global user loop URL Plug-In Initialisierung unmittelbar nach dem Start des Lasttest-Programms Vor dem Erzeugen eines jeden virtuellen Benutzers, bevor dessen erster Loop ausgeführt wird. Vor der Ausführung eins jeden Loops. Plug-In De-Initialisierung unmittelbar vor dem Ende des Lasttest-Programms Nach dem Beenden eines jeden virtuellen Benutzers, nachdem dessen letzter Loop ausgeführt wurde. Nach der Ausführung eines jeden Loops. Die De-Initialisierung wird auch ausgeführt wenn der Loop fehlschlägt. Dieser Scope kann nicht zur Initialisierung bzw. De-Initialisierung von Plug-Ins verwendet werden. © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 5 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch Liste der möglichen Ausführungs-Scopes eines Plug-Ins: Ausführungs-Scope global Vorher / Nachher before global after user before user after loop before loop after URL before URL after Plug-In Ausführung unmittelbar beim Start des LasttestProgramms, jedoch nach der Initialisierung des Plug-Ins unmittelbar vor dem Ende des LasttestProgramms, jedoch noch vor der DeInitialisierung des Plug-Ins Vor dem Erzeugen eines jeden virtuellen Benutzers, bevor dessen erster Loop ausgeführt wird Nach dem Beenden eines jeden virtuellen Benutzers, nachdem dessen letzter Loop ausgeführt wurde Vor der Ausführung eins jeden Loops, eines jeden Benutzers Nach der Ausführung eines jeden Loops, eines jeden Benutzers ¹ Vor der Ausführung eines bestimmten oder vor jedem URL-Call Nach der Ausführung eines bestimmten oder nach jedem URL-Call ² ¹ das Plug-In wird nicht ausgeführt, wenn der entsprechende Loop fehlschlägt. Die DeInitialisierung des Plug-Ins wird jedoch immer durchgeführt. ² das Plug-In wird immer ausgeführt, auch dann wenn beim URL-Call ein Fehler auftritt. Hinweis: der Import und der Export von GUI-Variablen erfolgt unmittelbar vor bzw. nach der Ausführung des Plug-Ins. 2.3 Konfiguration von Plug-Ins mittels des GUIs Sämtliche bereits vorhandenen Plug-Ins befinden sich unter MyTests im Sub-Directory Plugins. Bei der Konfiguration eines Plug-Ins im GUI (d.h. im Variablen Handler) schaut Proxy Sniffer in diesem Directory nach, worauf aus einer Liste das entsprechende Plug-In ausgewählt werden kann. Falls Sie ein neues, bereits vorgefertigtes Plug-In z.B. per E-Mail erhalten, so müssen Sie dieses zuerst in das Plugins Sub-Directory unter MyTests kopieren, bevor Sie dieses im GUI verwenden können. Die Konfiguration eines Plug-Ins, d.h. das Hinzufügen zu einem Lasttest-Programm, geschieht im GUI über den Variablen-Handler – analog der Definition eines Input-Files oder eines „User Input Fields“ – mittels des „Add Plug-in …“ Buttons. Nachdem Sie im GUI das Plug-In ausgewählt haben, müssen sie gegebenenfalls noch die GUI-Variablen festlegen, welche vom Plug-In importiert oder exportiert werden, sowie den Initialisierungs- sowie Ausführungs-Scope des Plug-Ins. Bei den meisten Plug-Ins wurden die Scopes jedoch bereits bei Entwicklung festgelegt und „fest verdrahtet“, so dass diese im GUI nicht mehr frei gewählt werden können. Eine weitere Eigenschaft eines Plug-Ins, welches bereits bei der Entwicklung festgelegt wird, ist ob dieses nur ein Mal oder mehrmals demselben Lasttest-Programm hinzugefügt werden kann. Kann ein Plug-In nur ein Mal demselben Lasttest-Programm hinzugefügt werden, so sperrte das GUI weitere Versuche das Plug-In mehrmals zu Konfigurieren. © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 6 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch Bild: hinzufügen eines Plug-Ins zum Lasttest-Programm: Bild: festlegen der importierten GUI-Variablen eines Plug-Ins (Beispiel): © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 7 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch 2.4 Lebenszyklus von Plug-Ins nach der Konfiguration Wurde ein Plug-In über das GUI konfiguriert, so wird dessen kompilierter Java-Code beim Speichern der Web-Session zusammen mit der Web-Session gemeinsam im selben File abgespeichert (*.prxdat File der Web-Session). Wird das entsprechende *.prxdat File auf einen anderen Computer übertragen, so „fliesst“ das Plug-In mit und steht dann auch dort zur Verfügung. Bei der Erzeugung eines Lasttests-Programms wird das Plug-In automatisch wieder aus der Web-Session herauskopiert, wobei eine Kopie des Plug-Ins in demselben Projekt-Navigator Directory abgelegt wird, indem sich auch das Lasttest-Programm erzeugt wird. Vor der TestAusführung wird dann das Plug-In zusammen mit dem kompilierten Lasttest-Programm (*.class) gezippt. Danach kann das ZIP-Archiv selbst als Lasttest-Programm ausgeführt werden, wobei das Plug-In zusammen mit dem Lasttest-Programm an den Exec Agenten übertragen wird. Während der Entwicklung eines Plug-Ins kann es vorkommen, dass eine neue Version des Plug-Ins im Plugins Directory unter MyTests erstellt wird. Das GUI überwacht solche Vorgänge und „merkt“, wenn eine Web-Session eine veraltete Version eines kompilierten Plug-Ins enthält. Als Reaktion erscheint im GUI ein Dialog, welche den Benutzer auffordert, die neue Plug-In Version in die Web-Session zu importieren. Allerdings kann eine neue Version eines Plug-Ins nur dann erfolgreich importiert werden, wenn dessen Anzahl GUIInput- und GUI-Output-Variablen noch gleich geblieben ist, und diese auch noch die gleiche Bedeutung haben. Ist dies nicht der Fall, so muss das Plug-In zuerst manuell in der WebSession über den Var Handler gelöscht werden, und danach der Web-Session erneut hinzugefügt werden. Nebst der Web-Session überwacht das GUI auch die kompilierten Kopien des Plug-Ins in den Directories der Lasttest-Programme. Bei einem „refresh“ des Projekt Navigators werden die Icons von veralteten Plug-Ins mit einem gelben Ausrufezeichen versehen. Durch einen Klick auf das entsprechende Icon im Projekt Navigator kann dann die aktuelle Version des Plug-Ins vom Plugins Directory unter MyTests in das Directory des Lasttest-Programms übertragen werden. Sind die Anzahl GUI-Input- und GUI-Output-Variablen des Plug-Ins sowie deren Bedeutung noch gleich geblieben, so kann das Lasttest-Programm nun direkt mit dem überarbeiteten (neuen) Plug-In gestartet werden, ohne dass der Java-Code des LasttestProgramms neu erstellt oder kompiliert werden muss. Der interne Vergleich, ob ein Plug-In veraltet ist oder nicht, erfolgt nicht über das File-Datum sondern über die Berechnung einer Prüfsumme über den kompilierten Code des Plug-Ins. Bei unterschiedlichen Prüfsummen gilt immer diejenige Version als „neu“, welche sich im Plugins Directory unter MyTests befindet. Beispiel: veraltete Kopie des Plug-Ins im Lasttest-Programm Directory – Icon enthält gelbes Ausrufezeichen © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 8 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch 3 Entwicklung eigener Plug-Ins Bei der Entwicklung eigener Plug-Ins sollte in einem ersten Schritt die Anbindung an die Laufzeit-Umgebung von Proxy Sniffer vorgenommen werden. Dies geschieht mittels eines GUI-Assistenten, welcher das Programm-Skelett des Plug-Ins automatisch erzeugt. Danach ist das Plug-In bereits kompilations-fähig, enthält jedoch noch keine innere Logik welche dem Kern der Plug-In Funktionalität ausmacht. In einem zweiten Schritt wird innere Logik dem Plug-In hinzugefügt, wobei dies von Hand programmiert werden muss. Danach kann das Plug-In im GUI über den Variablen-Handler einer aufgezeichneten Web-Session hinzugefügt werden. 3.1 Java API Dokumentation Bei der Programmierung eines Plug-Ins stehen Ihnen die Class-Libraries des Java SDK 5.0 sowie weitere Klassen welche sich im Package dfischer.utils befinden bereits zur Verfügung. Bei Windows-Systemen können Sie die entsprechende API-Dokumentation von dfischer.utils über Start Programme ProxySniffer Proxy Sniffer Java API Documentation einsehen. Bei Mac OS X Systemen ist die API-Dokumentation von dfischer.utils im folgenden Directory abgelegt: /Applications/ProxySniffer/doc/javadoc (index.hrml) Bei der Installation des Proxy Sniffer auf anderen Unix-ähnlichen Betriebssystemen (Solaris, Linux, …) wird die API-Dokumentation nicht automatisch installiert. Sie können diese jedoch von folgender URL herunterladen: http://www.proxy-sniffer.com/download/ProxySnifferJavaDoc.zip 3.2 Verwenden von mehreren Klassen und von externen ClassLibraries Oft besteht ein Plug-In nur aus einer einzigen Klasse und verwendet selbst keine externen Libraries. In diesem Fall ist nichts spezielles zu beachten und Sie können dieses Unterkapitel überspringen. Benötigen Sie jedoch zur Entwicklung eines Plug-Ins weitere Class-Libraries, wie z.B. ein Datenbank-Treiber, oder soll das Plug-In aus mehreren Klassen bestehen, so müssen diese bereits als *.jar Files vorliegen, bevor Sie mit der Entwicklung des Hauptklasse des Plug-Ins beginnen. Beachten Sie in diesem Zusammenhang auch, dass die Hauptklasse eines Plug-Ins niemals selbst Bestandteil eines Java-Packages sein darf, und sich selbst auch nicht in einem jar-Archiv befinden darf. Sind weitere *.jar Files notwendig, so müssen Sie diese zuerst in das Plugins Subdirectory unter MyTests kopieren. Danach müssen Sie noch den CLASSPATH des Web Admin GUIs anpassen. Dies geschieht an folgenden Stellen: Wählen Sie im Project Navigator Fenster oben rechts das „Setup“ Icon (Menü „Project Navigator - Java™ Setup and Report Branding“) und ergänzen Sie den CLASSPATH des Java Compilers sowie des Java Interpreters mit dem Pfaden zu den weiteren *.jar Files. Bei Windows-Systemen müssen Sie zusätzlich das Flle „Proxy Sniffer Console.lax“ und gegebenenfalls dessen Varianten „Proxy Sniffer Console (1024 MB).lax“ und „Proxy Sniffer Console (without local Exec Agent).lax“ von Hand editieren und die Zuweisung an lax.class.path ergänzen. Danach müssen Sie Proxy Sniffer stoppen und dann wieder neu starten. © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 9 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch Bei Unix-ähnlichen Systemen müssen Sie zusätzlich Proxy Sniffer stoppen, danach die externen *.jar Files dem CLASSPATH des Startup-Scripts hinzufügen, und danach Proxy Sniffer erneut starten. Danach können Sie mit der Entwicklung der Haupt-Klasse des Plug-Ins fortfahren. Bei der (späteren) Ausführung eines Lasttests Plug-Ins auf remote Exec Agenten stehen Ihnen zwei verschiedene Alternativen zur Verfügung: a) Entweder zippen Sie das den *.class Code des Plug-Ins mit der kompilierten *.class Code des Lasttest-Programms und allen weiteren externen *.jar Files zu einem ZIP-Archiv zusammen und führen das ZIP-Archiv selbst als Lasttest aus. In diesem (empfohlene) Fall müssen die externen *.jar Files nicht von Hand auf den Exec Agenten installiert werden. Allerdings müssen Sie die externen *.jar Files zusätzlich auch noch in das Directory des Lasttest-Programms kopieren, damit Sie das ZIP-Archiv im GUI erstellen können, was nicht immer ganz praktisch ist. b) Oder Sie installieren die externen *.jar Files von Hand auf den Exec Agenten, wobei Sie auf den Exec Agenten zusätzlich auch im File javaSetup.dat den Wert von javaClasspath ergänzen. Danach müssen Sie die Exec Agenten stoppen und wieder neu starten. In diesem Fall müssen Sie nur das Plug-In mit der kompilierten Klasse des LasttestProgramms zusammen zippen, jedoch nicht die externen *.jar Files. Auch hier führen Sie danach das ZIP-Archiv als Lasttest aus. Diese einfachere Variante ist nur dann empfohlen, wenn die Entwicklung der externen *.jar Files abgeschlossen ist. © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 10 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch 3.3 Erstellen des Programm-Skeletts mit dem GUI-Assistenten Der GUI-Assistent ist im Project Navigator Plugins Subdirectory unter MyTests zugänglich. Starten Sie diesen indem Sie im Project Navigator auf dessen Icon klicken: Hinweis: falls Sie das Proxy Sniffer GUI auf einem Unix-ähnlichen Betriebssystem verwenden, so müssen Sie gegeben falls das Directory Plugins von Hand erzeugen bevor Sie den GUIAssistenten aufrufen können. In der Box oben links müssen Sie folgendes eingeben: Plug-In Class Name: frei wählbarer Name der Java-Klasse des Plug-Ins (ohne *.class oder *.java Datei-Erweiterung). Allow Multiple Usage: hier können Sie festlegen, ob das Plug-In im selben LasttestProgramm (bzw. in derselben Web-Session) mehrmals oder nur ein Mal konfiguriert werden darf. © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 11 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch Plug-In GUI Label: dies ist die Bezeichnung des Plug-Ins im GUI. Geben Sie hier nur ein oder zwei Stichwörter ein. Plug-In Description: dies ist die ausführliche Beschreibung des Plug-Ins welche im GUI angezeigt wird. Sie können hier auch mehrere Sätze eingeben. Eine HTML-Formatierung wird jedoch nicht unterstützt. Das einzige Formatierungs-Sequenz welches unterstützt wird ist „\n“ was einen Zeilenumbruch im dargestellten Text bewirkt. In der Box oben in der Mitte, welche mit „Plug-In Initialization“ beschriftet ist, können Sie den Initialisierungs-Scope des Plug-Ins festlegen (siehe auch Kapitel 2.2). Dabei bedeutet „arbitrary / assigned by GUI“, dass dem Plug-In kein fester Initialisierungs-Scope vorgegeben wird; d.h. dass dieser später bei der Konfiguration des Plug-Ins im GUI frei gewählt werden kann. Es wird jedoch empfohlen, diese Option nicht zu verwenden, sondern sofern möglich immer einen festen Initialisierungs-Scope vorzugeben. In der Box oben rechts, welche mit „Plug-In Execution“ beschriftet ist, können Sie den Ausführungs-Scope des Plug-Ins festlegen (siehe auch Kapitel 2.2). Wiederum ist zusätzlich auch „arbitrary / assigned by GUI“ möglich. Auch hier wird empfohlen einen festen Scope vorzugeben. Danach, etwas weiter unten in der Mitte des Fensters, erfolgt die Definition der Plug-in Input Parameter. Es können bis zu sechs Parameter festgelegt werden, wobei mittels des DropDown Felds „Optional Parameter“ bestimmt werden kann, wie viele Parameter als „PflichtGUI-Variablen“ importiert werden müssen und wie viele Parameter als „optionale GUIVariablen“ importiert werden können. Beachten Sie, dass die Reihenfolge immer so definiert ist, dass die Definition der „Pflicht-Variablen“ vor der Definition der nachfolgenden „optionale Variablen“ erfolgen muss. Die Eingabefelder der Plug-In Input-Parameter haben folgende Bedeutung: GUI Label: dies ist die Bezeichnung des Parameters im GUI. Geben Sie hier nur ein oder zwei Stichwörter ein. local var: dies ist der Name der lokalen Plug-In Variable im automatisch erzeugten Java Programm-Code des Plug-Ins. Die später konfigurierte GUI-Variable übergibt ihren Wert an diese lokale Plug-In Variable. convert to: bestimmt den Datentyp der lokalen Plug-In Variable. Beachten Sie, dass alle GUI-Variablen des Variablen-Handlers als String dem Plug-In übergeben werden. „Convert To“ = „int“ bedeutet z:B. in diesem Zusammenhang, dass die importierte GUIVariable in den (lokalen) „int“ Datentyp konvertiert wird. User Input Field: wird diese Checkbox aktiviert, so wird bei der Konfiguration des Plug-Ins im GUI gleichzeitig auch noch ein neues „User Input Field“ erzeugt, wobei dessen Eingabewert (dessen neue GUI-Variable) der lokalen Plug-In Variable zugewiesen wird. Ist diese Checkbox nicht aktiviert, so muss die GUI-Variable des Input-Parameters bei der Konfiguration im GUI manuell ausgewählt werden. default value: hier kann der Default-Wert der lokalen Variable im Java Programm-Code des Plug-Ins gesetzt werden. Wird kein Default-Wert gesetzt so wird die lokale Variable wie folgt initialisiert: Datentyp String int long float double boolean Initialisierungs-Wert ““ (Leerstring) -1 (minus eins) -1 (minus eins) -1 (minus eins) -1 (minus eins) false © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 12 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch Hinweis: beim Import einer boolean Variablen wird für den Wert true auf das Vorhandensein des Strings „1“ oder „true“ geprüft. Im unteren Teil des Fensters erfolgt die Definition der Plug-in Output Parameter. Es können bis zu vier Output-Parameter gesetzt werden, wobei auch hier einige oder alle optional sein können. Die Eingabefelder der Output-Parameter haben folgende Bedeutung: GUI Label: dies ist die Bezeichnung des Parameters im GUI. Geben Sie hier nur ein oder zwei Stichwörter ein. local var: dies ist der Name der lokalen Plug-In Variable welche (später) der entsprechende konfigurierten GUI-Variable zugewiesen wird. convert from: bestimmt den Datentyp der lokalen Variable im Programm-Code. Beachten Sie bitte, dass alle lokalen Variablen nach der Ausführung des Plug-Ins als String an die GUI-Variablen exportiert werden. default value: hier kann der Default-Wert der lokalen Plug-In Variable gesetzt werden. Wird kein Default-Wert gesetzt so gelten dieselben Regeln wie bei den Input-Parametern. Nachdem Sie alle Eingaben vorgenommen haben können Sie unten links im Fenster des GUIAssistenten auf den Knopf „Save Template and Continue“ klicken und danach im neuen Inhalt desselben Fensters auf den Knopf „Generate Plug-In Code“ klicken. Dabei wird der Programm-Code des Plug-Ins automatisch – zuerst nur im Memory – erzeugt. Durch einen Klick auf den Knopf „Save Plug-In Code“ unten links im Fenster wird der Plug-In Code auf Disk in das Plugins Directory geschrieben. Danach können Sie den Plug-In Code im Project Navigator ein erstes Mal kompilieren. Um das Plug-In später erneut zu kompilieren können Sie im Project Navigator das graue Zahnrad-Icon mit dem „j“ verwenden welche rechts neben dem *.java File-Namen des Plug-Ins angeordnet ist. Bild: erzeugen des Programm-Skeletts (*.java Code) Bild: erneutes kompilieren des Plug-Ins © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 13 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch 3.4 Programmierung der Plug-In Funktionalität Vom GUI-Assistenten werden u.a. die Methoden für die Plug-In Initialisierung public void construct(Object context), für die Plug-In Ausführung public void execute(Object context) sowie für die Plug-In De-Initialisierung public void deconstruct(Object context) erzeugt, wobei Sie in der Regel anschliessend nur den Inhalt der Methode execute mit eigenem Programm-Code ergänzen müssen. public void construct(Object context) { // LoadtestPluginContext pluginContext = (LoadtestPluginContext) context; } public void execute(Object context) { logVector = new LogVector(); LoadtestPluginContext pluginContext = (LoadtestPluginContext) context; } public void deconstruct(Object context) { // LoadtestPluginContext pluginContext = (LoadtestPluginContext) context; } Je nach Ausführungs-Scope wird die Methode execute vom GUI-Assistenten noch mit unterschiedlichem Beispiel-Code versehen welcher durch die Marker // vvv --- sample code --- vvv und // ^^^ --- sample code --- ^^^ umfasst wird. Sie sollten darum bei der Programmierung diesen Beispiel-Code entfernen. Der Zugriff aus einem Plug-In auf die Laufzeitumgebung eines Lasttest-Programms erfolgt mittels der Klasse LoadtestPluginContext, deren aktuelle Instanz allen vorgenannten Plug-In Methoden übergeben wird. Eine ausführliche Beschreibung der Klasse LoadtestPluginContext ist in der Proxy Sniffer Java API Documentation enthalten. © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 14 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch Wichtiger Hinweis: Beachten Sie, dass die einzelnen Methoden der Klasse LoadtestPluginContext innerhalb eines Plug-Ins unter Umständen keinen gültigen ReturnWert liefern, je nachdem wie der Initialisierungs-Scope und der Ausführungs-Scope des PlugIns gewählt wurde (CONTEXT_SCOPE_GLOBAL, CONTEXT_SCOPE_USER, CONTEXT_SCOPE_LOOP oder CONTEXT_SCOPE_URL). In der Proxy Sniffer Java API Dokumentation ist darum bei der Beschreibung der einzelnen Methoden der Klasse LoadtestPluginContext unter „See Also:“ genau festgehalten, für welchen Scope eine Methode gültige Werte während der ProgrammAusführung des Plug-Ins liefert. Beispiel: die Methode getCookieHandler() liefern nur einen gültigen Return-Wert, wenn der Scope CONTEXT_SCOPE_LOOP oder CONTEXT_SCOPE_URL ist. Weitere Hintergrund-Informationen zum Scope sind in diesem Dokument in Kapitel 2.2 enthalten. Falls der Initialisierungs-Scope oder der Ausführungs-Scope nicht mittels des GUI-Assistenten festgelegt wurde (Wert „arbitrary / assigned by GUI“ gewählt), so müssen Sie diesen während derr Laufzeit des Plug-Ins bestimmen. Dazu steht die Methode getContextScope() zur Verfügung. © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 15 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch 3.4.1 Klasse LoadtestPluginContext Dieses Unterkapitel enthält nur die Beschreibung einiger ausgewählter Methoden der Klasse LoadtestPluginContext. Die vollständige Dokumentation dieser Klasse ist in der „Proxy Sniffer Java API Documentation“ enthalten. Methode getContextScope() getPerformanceData() getHttpLoadTest() getHttpTestURL() getThreadStep() Beschreibung Bestimmt den aktuellen Scope zur Laufzeit. Wird nur dann benötigt wenn dieser nicht mittels des GUIAssistenten festgelegt wurde. Erlaubt den Zugriff auf die aktuellen Messdaten. Beispiel: mittels PerformanceData.getPassedLoops() kann bestimmt werden, wie viele Loops – gemessen über alle virtuellen Benutzer – bereits erfolgreich ausgeführt wurden. Erlaubt den Zugriff auf weitere Daten der Laufzeitumgebung – siehe folgendes Unterkapitel. Erlaubt Zugriff auf die Daten des aktuell ausgeführten URL-Calls. Beispiel: mittels HttpTestURL. getRequestHeaderObject().appendHe aderField() kann z.B. dem URL Call vor dessen Ausführung ein zusätzliches HTTP Request Header Field hinzugefügt werden. Gibt den Index des aktuell ausgeführten URL-Calls aus. zulässige Scopes alle Scopes alle Scopes alle Scopes CONTEXT_SCOPE_URL CONTEXT_SCOPE_URL Beispiel 1: der Index kann als InputParameter bei der Methode PerformanceData. getPerformanceDataRecord() verwendet werden, um auf weitere, detaillierte statistische Messdaten des URLs zuzugreifen. setContinueInnerLoopFlag() setBreakInnerLoopFlag() markUrlAsFailed(<Text>) Beispiel 2: der Index kann als InputParameter von PerformanceData. getPageInfoTextOfUrl() verwendet werden, um den Text des zum URL gehörenden Page Breaks zu ermitteln. Erzwingt innerhalb eines "Inner Loops" den direkten Sprung zurück an den Anfang des "Inner Loops". Erzwingt innerhalb eines "Inner Loops" dass dieser sofort verlassen wird. Verwandelt einen erfolgreich durchgeführten URL-Call in einen schweren (roten) Fehler. © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten CONTEXT_SCOPE_URL CONTEXT_SCOPE_URL CONTEXT_SCOPE_URL Seite 16 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch 3.4.2 Klasse HttpLoadTest Dieses Unterkapitel enthält nur die Beschreibung einiger ausgewählter Methoden der Klasse HttpLoadTest. Die vollständige Dokumentation dieser Klasse ist in der „Proxy Sniffer Java API Documentation“ enthalten. Methode contentToDiskFile() getClusterMemberLocalId() triggerAbort() Beschreibung Schreibt den Inhalt eines ausgeführten URL Calls (HTTP response content) als File auf Disk. Gibt bei einem Cluster-Job die Nummer des entsprechenden Cluster-Members (Exec Agenten) aus. Der erste Cluster Member hat die Nummer=0, der zweite die Nummer=1 usw. Da bei einem Cluster-Job dasselbe Plug-In parallel auf sämtlichen Cluster-Member abläuft kann es unter Umständen sinnvoll sein, gewisse Aktionen des Plug-Ins nur an einen bestimmten Cluster Member zu binden (z.B. an Member Nummer 0). Führt den vorzeitigen Abbruch des Lasttest-Programms herbei, wobei die bereits gesammelten statistischen Daten nicht verloren gehen (das *.prxdat File wird erzeugt). © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 17 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch 3.4.3 Spezielle Hinweise zur Laufzeitumgebung 3.4.3.1 Debug-Output während der Plug-In Ausführung Innerhalb der Plug-In Methode execute sollte der Debug-Output niemals mit System.out.println() geschrieben werden, da sonst im Log-File des Lasttest-Jobs ein verwürfelter Mix des Outputs von verschiedenen Loops der virtuellen Benutzern entsteht. Verwenden Sie darum in dieser Methode anstelle dessen immer logVector.log(). Bei den Methoden construct und deconstruct ist dies nicht möglich. Hier können Sie nur System.out.println() verwenden. Um einen vollständigen Debug-Output des Lasttest-Programms inkl. des Debug-Outputs des Plug-Ins zu erhalten müssen Sie beim Starten des Lasttests im GUI Menu „Project Navigator Execute Load Test“ das Feld „Debug Options“ auf „debug loops (including var handler)“ setzen. Bei der späteren Ausführung eines realen Lasttests sollten sie diese Option dann wieder auf „none – recommended“ setzen. Der Debug-Output wird in das *.out File des entsprechenden Lasttest-Jobs geschrieben. Erfolgt ein Programm-Absturz während eines Lasttests aufgrund einer fehlerhaften Plug-In Programmierung, so wird dessen Output in das *.err File des entsprechenden Lasttest-Jobs geschrieben. 3.4.3.2 Initialisierung eines Plug-Ins mittels importierten GUI-Variablen Die importierten GUI-Variablen eines Plug-Ins werden unmittelbar (jedes Mal) vor dessen Ausführung dem Plug-In übergeben – d.h. unmittelbar bevor die Methode execute im Plug-In aufgerufen wird. Nun kann es aber sein, dass dem Plug-In eigentlich schon eine GUI-Variable übergeben werden sollte wenn dieses Initialisiert wird, d.h. beim Aufruf der Methode construct. Das wird jedoch nicht unterstützt. Als Workaround muss in diesem Fall die Initialisierung des Plug-Ins nach dem „Lazy-Prinzip“ erfolgen. D.h. die Methode construct führt keine Initialisierung durch. Anstelle dessen wird bei jedem Aufruf der Methode execute zuerst geprüft, ob das Plug-In bereits initialisiert wurde. Beim ersten Aufruf ist dies noch nicht der Fall und die Initialisierung des Plug-Ins wird mittels der GUI-Variable(n) ausgeführt. Bei allen nachfolgenden Aufrufen von execute wird dann die Initialisierung übersprungen. Das folgende Code-Fragment illustriert dies: [...] /** * Load test add-on module. */ public class TestPlugin implements LoadtestPluginInterface { private String vInitialParameter = ""; // input parameter #1 private int vNormalInputParameter = -1; // input parameter #2 private boolean pluginInitialized = false; private String vOutputParam = ""; // output parameter #1 private LogVector logVector = null; // internal log vector [...] public void construct(Object context) { } [...] public void execute(Object context) { logVector = new LogVector(); LoadtestPluginContext pluginContext = (LoadtestPluginContext) context; © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 18 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch if (!pluginInitialized) { // initialize plug-in by using parameter #1 doInitialize(vInitialParameter); pluginInitialized = true; } [...] } [...] 3.4.3.3 Extrahieren von Daten aus der HTTP-Response von URL-Calls Falls Sie Daten aus dem HTTP Response Header oder aus dem HTTP Response Content eines URL-Calls extrahieren möchten, so hat das Plug-In den Ausführungs-Scope URL / after. Dabei müssen Sie jedoch bei der Programmierung von execute beachten, dass ein solches Plug-In auch ausgeführt wird, wenn der URL-Call fehlschlägt. Zum Beispiel dann, wenn ein Timeout auftritt und darum gar keine Response-Daten empfangen wurden, oder auch z.B. dann, wenn anstelle eines 200 (OK) HTTP Response Codes ein 500 (Internal Server Error) Response Code vom Web-Server zurückgegeben wird. Damit der Program-Code des Plug-Ins in einem solchen Fall nicht „abstürzt“ und das ganze Lasttest-Programm „mit nach unten reisst“ müssen Sie darum zuerst prüfen, ob überhaupt eine gültige Server-Antwort vorliegt – bevor Daten aus der HTTP-Response extrahiert werden. Um dies zu tun steht in der Klasse LoadtestPluginContext die Methode urlPassed() zur Verfügung. Diese liefert nur den Wert true zurück, wenn während eines Lasttests bei diesem URL-Call kein Fehler aufgetreten ist – entsprechend der Konfiguration des GUI-Menüs „HTTP Response Verification“. „Kein Fehler“ bedeutet: Es wurde beim URL-Call der korrekte HTTP Response Code empfangen Die Antwort enthält den korrekten MIME-Type (z.B. TEXT/HTML) Die Überprüfung des empfangenen Daten-Inhalts aufgrund dessen Grösse oder aufgrund eines gefunden Text-Fragments verlief ebenfalls erfolgreich Das nachfolgende Programm-Fragment illustriert den sicheren Zugriff auf den empfangenen Dateninhalt eines URL-Calls: public void execute(Object context) { LoadtestPluginContext pluginContext = (LoadtestPluginContext) context; if (pluginContext.urlPassed()) { HttpTestURL httpTestURL = pluginContext.getHttpTestURL(); String content = httpTestURL.getContentString(); [...] // Weiterverarbeitung des empfangenen Daten-Inhalts } [...] } © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 19 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch 3.4.3.4 Ausführen von Plug-Ins am Ende eines Loops Beachten Sie, dass ein Plug-In am Ende eines Loops nicht ausgeführt wird, wenn bei einem URL-Call innerhalb eines Loops ein Fehler auftritt, und dadurch der ganze Loop fehlschlägt. D.h. wenn ein Fehler auftritt und das Plug-In den Ausführungs-Scope loop / after hat, so wird die Plug-In Methode execute nicht ausgeführt. Falls das Plug-In jedoch (auch / zusätzlich) den Initialisierungs-Scope loop hat, so wird in jedem Fall die Plug-In Methode deconstruct aufgerufen. Dies geschieht bei diesem Initialisierungs-Scope immer am Ende eines jeden Loops - unabhängig davon, ob ein Fehler im entsprechenden Loop auftrat oder nicht. Dabei wird der Methode deconstruct des Plug-Ins immer eine aktuelle Instanz der Klasse LoadtestPluginContext übergeben. Mittels der Methode loopPassed() können Sie nun innerhalb von deconstruct feststellen, ob ein Loop fehlschlug oder nicht. Weiterhin erhalten Sie – nur bei einem fehlgeschlagenen Loop – mittels der Methode getHttpTestURL() Zugriff auf denjenigen URL-Call, welcher den Fehler ausgelöst hat. 3.4.3.5 Plug-Ins bei Cluster-Jobs Bei Cluster-Jobs wird ein Plug-In parallel in allen entsprechenden Exec Agenten des Clusters ausgeführt. Falls Sie besondere Aktionen durchführen möchten – welche nur ein Mal über den ganzen Cluster-Job gesehen ausgeführt werden sollen, so müssen Sie das Plug-In an genau einen Exec Agenten des Clusters binden. Dazu können Sie im Plug-In die ID (Nummer) des Exec Agenten mittels HttpLoadTest. getClusterMemberLocalId() abfragen. Diese Methode liefert für den ersten Cluster Member den Wert 0 zurück, für den zweiten Cluster Member den Wert 1, … usw. Da ein Cluster theoretisch auch nur aus einem einzigen Exec Agenten bestehen kann, sollte das Plug-In an den ersten Cluster-Member (ID = 0) gebunden werden, damit dieses auch immer ausgeführt wird. Bei normalen Exec Agent Jobs, d.h. wenn der Lasttest nicht von einem Cluster ausgeführt wird, liefert getClusterMemberLocalId() den Wert -1 (minus Eins) zurück. Das nachfolgende Programm-Fragment zeigt, wie eine Aktion pro Job immer nur von einem Exec Agenten ausgeführt wird, egal ob es sich um einen gewöhnlichen Exec Agent Job oder um einen Cluster Job handelt: public void execute(Object context) { LoadtestPluginContext pluginContext = (LoadtestPluginContext) context; HttpLoadTest httpLoadTest = pluginContext.getHttpLoadTest(); int clusterMemberLocalId = httpLoadTest.getClusterMemberLocalId(); if ((clusterMemberLocalId == -1) || (clusterMemberLocalId == 0)) { // Aktion wird nur auf einem Exec Agenten ausgeführt [...] } } © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 20 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch 3.4.3.6 Integrieren von zusätzlichen (externen) Messdaten Mittels Plug-Ins ist es auch möglich, während eines Lasttests zusätzlich Messdaten zu sammeln und diese später als XY-Diagramme im GUI anzuzeigen (im Menü „Load Test Result Detail - Statistics and Diagrams“). Dabei spielt es keine Rolle, ob die Messdaten von internen Datenquellen – z.B. extrahiert aus URL-Antworten des Lasttest-Programms – oder von externen Datenquellen stammen. Die einzige Bedingung ist, dass solche zusätzliche Messdaten während der Ausführungsdauer des Lasttest-Programms zur Verfügung stehen. Es lassen sich so – eine geeignete Programmierung vorausgesetzt – auch Messdaten von externen Systemen im LasttestResultat (*.prxres File) integrieren. Der spätest mögliche Zeitpunkt zur Integration von zusätzlichen Messdaten ist kurz vor dem Programm-Ende des Lasttest-Programms, d.h. dann wenn alle virtuellen Benutzer ihren letzten Loop abgeschlossen haben. Der entsprechende Plug-In Initialisierungs-Scope ist dabei global, und das Hinzufügen der Messdaten erfolgt in der Plug-In Methode deconstruct. Das Sammeln von zusätzlichen Messdaten kann jederzeit während des Lasttests erfolgen, wobei pro Messgrösse bzw. pro Messreihe zuerst eine Instanz der Klasse dfischer.utils.DataCollectionSequence erzeugt werden muss. Im Konstruktor dieser Klasse erfolgen die Angaben zur Beschriftung des entsprechenden Mess-Diagramms im GUI. Die Konstruktor-Parameter sind: - sequenceId: frei wählbare, eindeutige Nummer der Datensequenz - diagramTitle: Titel des Diagramms im GUI - diagramSubTitle: Sub-Titel des Diagramms im GUI - yAxisLabel: Beschriftung der Y-Achse im Diagramm - sequenceContext: hat hier keine Bedeutung, als Wert sollte null übergeben werden Nachdem die Instanz von DataCollectionSequence erzeugt wurde, können die einzelnen Messwerte der Messgrösse mittels wiederholten Aufrufen der Methode addItem(DataCollectionFloatItem floatItem) hinzugefügt werden. Dazu muss pro hinzugefügtem Messwert zuvor eine Instanz der Klasse DataCollectionFloatItem erzeugt werden, bevor addItem aufgerufen werden kann. Der Konstruktor von DataCollectionFloatItem enthält zwei Parameter: - timeStamp: Zeitpunkt des Messwerts (verstrichene Millisekunden seit 1970). Soll der augenblickliche Zeitpunkt verwendet werden so kann System.currentTimeMillis() verwendet werden. - floatValue: Grösse des Messwerts. Hinweis: beachten Sie, dass Messwerte welchen einen Zeitpunkt (timeStamp) vor dem Start des Lasttest-Programms enthalten im GUI-Diagramm nicht dargestellt werden. Falls Daten von einem externen System inklusive dessen externen timeStamps importiert werden, so sollte im Plug-In zuerst die Differenz der System-Zeit zwischen dem lokalen System und dem externen System ermittelt werden. Diese Zeit-Differenz kann danach als Korrekturwert für den timeStamp dienen. Nachdem alle Daten gesammelt wurden, müssen diese noch dem Lasttest-Resultat hinzugefügt werden. Dies geschieht mittels der Methode PerformanceData .addDataCollectionSequence(DataCollectionSequence), wobei diese Methode pro Messgrösse, d.h. pro Instanz von DataCollectionSequence, wiederholt aufgerufen wird. Den Zugriff auf die aktuelle Instanz von PerformanceData erhalten Sie über den Plug-In Context (Instanz der Klasse LoadtestPluginContext welche dem Plug-In in den Methoden construct, execute und deconstruct übergeben wird). © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 21 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch Beispiel 1: Das nachfolgende, einfache Beispiel zeigt, wie die "Zeit bis zum Empfang des ersten Bytes eines HTTP-Response-Headers eines URL-Aufrufs" von einem Plug-In extrahiert und als zusätzliches Diagramm dem Lasttest-Resultat hinzugefügt wird. [...] import dfischer.utils.DataCollectionSequence; import dfischer.utils.DataCollectionFloatItem; /** * Load test add-on module. */ public class AdditionalDataExample implements LoadtestPluginInterface { private LogVector logVector = null; private DataCollectionSequence simpleSequence = new DataCollectionSequence(1, "Wait Time for Receiving the first Byte of Response", "", "Milliseconds", null); [...] /** * Execute plug-in after URL call. * * Intrinsic plug-in implementation. */ public void execute(Object context) { logVector = new LogVector(); LoadtestPluginContext pluginContext = (LoadtestPluginContext) context; HttpTestURL httpTestURL = pluginContext.getHttpTestURL(); if (pluginContext.urlPassed()) { DataCollectionFloatItem waitTimeForFirstByteItem = new DataCollectionFloatItem(System.currentTimeMillis(), httpTestURL.getResponseHeaderWaitTime()); simpleSequence.addItem(waitTimeForFirstByteItem); } } [...] /** * Finalize plug-in at end of load test. */ public void deconstruct(Object context) { LoadtestPluginContext pluginContext = (LoadtestPluginContext) context; simpleSequence.addClusterOption(DataCollectionSequence. CLUSTER_OPTION_MERGE_FLOAT_ITEMS_TO_SUM, "Merged Cluster Data"); pluginContext.getPerformanceData().addDataCollectionSequence(simpleSequence); } [...] } // end of class Das nachfolgende Bild zeigt, wie die zusätzlichen Messdaten im GUI dargestellt werden. Wurden mehr als eine Messgrösse dem Lasttest-Resultat hinzugefügt, so erfolgt die Auswahl © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 22 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch der entsprechenden Messgrösse mittels der Dropdown-Liste oberhalb des Diagramms. Alle Diagramme der zusätzlichen Messdaten werden auch in den PDF-Report übernommen. Hinweis: Falls Sie die X-Achse eines Diagramms auch frei wählen möchten, so müssen Sie die gemessenen Daten mittels eines DataCollectionYZFloatItem (anstelle eines DataCollectionFloatItem) der DataCollectionSequence hinzufügen. Bei einem DataCollectionYZFloatItem hat jeder Messwert drei Dimensionen: 1. Der Zeitstempel (XDimension, vorgegeben), 2. Der erste Wert (Y-Dimension), 3. Der zweite Wert (Z-Dimension). Infolgedessen können im GUI für eine solche DataCollectionSequence drei verschiedene Diagramme angezeigt werden: XY-Diagramm, XZ- Diagramm und YZ- Diagramm, wobei hier hauptsächlich die Ansicht des YZ-Diagramms von Bedeutung ist, da nur dieses beide Werte gleichzeitig anzeigt. Über den dazu geeigneten Konstruktor von DataCollectionSequence können Sie die beiden freien Achsen des Diagramms beschriften, und auch festlegen, ob im GUI alle drei Diagramme angezeigt werden sollen, oder nur das YZ- Diagramm. © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 23 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch Beispiel 2: Das nachfolgende, etwas komplexere Beispiel zeigt, wie mittels eines lokalen Threads im Plug-In externe Messdaten integriert werden. Das Plug-In hat den InitialisierungsScope global. Der lokale Thread wird beim Starten des Lasttest-Programms in der Plug-In Methode construct gestartet und am Ende des Lasttest-Programms in der Plug-In Methode deconstruct wieder gestoppt. Innerhalb der Thread-Methode run() erfolgt das Sammeln der Messdaten. Nachdem der lokale Thread (wahrscheinlich) gestoppt wurde, werden gleich danach die Messdaten dem Lasttest-Resultat hinzugefügt. Innerhalb von deconstruct wird nur maximal 10 Sekunden das Ende des Threads gewartet, damit das Programm-Ende des Lasttest-Programms nicht zu stark verzögert wird. Falls der lokale Thread dennoch weiterlaufen würde, so ist dies kein Problem, da danach das Lasttest-Programm sich selbst beendet – ausserhalb des Plug-Ins mit System.Exit() – was zu einem Exit der Java Virtual Maschine des Lasttest-Jobs führt. [...] import dfischer.utils.DataCollectionSequence; import dfischer.utils.DataCollectionFloatItem; /** * Load test add-on module. */ public class ExternalMeasuringData extends Thread implements LoadtestPluginInterface { private LogVector logVector = null; private PerformanceData performanceData = null; // load test result private Thread dataCollectThread = null; // data collecting thread private DataCollectionSequence dataCollectionSequence = null; // external data [...] /** * Initialize plug-in at start of load test. */ public void construct(Object context) { LoadtestPluginContext pluginContext = (LoadtestPluginContext) context; performanceData = pluginContext.getPerformanceData(); // start data collecting thread dataCollectThread = new Thread(this); dataCollectThread.start(); } /** * Thread - used to collect external measuring data */ public void run() { // create data structure for external data and define GUI diagram settings dataCollectionSequence = new DataCollectionSequence(1, "Database Calls", "", "Calls per Second", null); // collect external measuring data in a loop while (!isInterrupted()) { // get external data snapshot float externalValue = 10.0f; // <<< actual value of external data // [ add your own code here to // accumulate external data ] // add external data snapshot to data collection DataCollectionFloatItem dataItem = new DataCollectionFloatItem(System.currentTimeMillis(), externalValue); dataCollectionSequence.addItem(dataItem); © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 24 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch // sleep for one sampling interval. The sampling interval is // arbitrary configurable on the GUI when starting the load test try { sleep(performanceData.getSamplingInterval() * 1000); } catch (InterruptedException e) { interrupt(); } } } /** * Execute plug-in at start of load test. * * Intrinsic plug-in implementation. */ public void execute(Object context) // no action in this method { } /** * Finalize plug-in at end of load test. */ public void deconstruct(Object context) { // stop data collecting thread dataCollectThread.interrupt(); try { dataCollectThread.join(10000); } catch (InterruptedException e) {} // add external measuring data to load test result performanceData.addDataCollectionSequence(dataCollectionSequence); } [...] } // end of class Hinweis: falls die zusätzlich Messdaten nur aus URL-Antworten gewonnen werden, oder wenn zusätzlich Messdaten nur aufgrund von bereits vorhandenen Proxy Sniffer Messwerte berechnet werden, so ist kein lokaler Thread notwendig. © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 25 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch 3.4.3.7 Versenden von E-Mails mittels SMTP Mittels der Klasse dfischer.utils.SmtpMessage können Sie E-Mails aus Plug-Ins versenden. Dies bedingt jedoch, dass Sie über einen SMTP E-Mail Server verfügen, welcher das Einspeisen von E-Mails erlaubt. Da wegen Sicherheits-Auflagen E-Mail Server oft so konfiguriert sind, dass diese eine Einspeisung von E-Mails von anonymen Benützern nicht zulassen, können Sie zur Authentisierung gegenüber dem E-Mail auch einen Benutzernamen und ein Passwort verwenden. Das nachfolgende Programm-Fragment zeigt ein Beispiel wie ein E-Mail im HTML-Format an einem SMTP-Relay-Server gesendet werden kann: String htmlMessage = "<body><html><ul><li>first</li><li>second</li></ul> <font color=\"blue\">blue sea</font></body></html>"; SmtpMessage smtpMessage = new SmtpMessage(smtpHost); smtpMessage.setSmtpAuthentication(authUsername, authPassword); smtpMessage.markHtmlMessage(); smtpMessage.setDebug(); smtpMessage.send(from, to, subject, htmlMessage); System.out.println("--- waiting for completion ---"); smtpMessage.waitForSendCompletion(); // debug message transfer String[] debugOutput = smtpMessage.getDebugOutput(); for (int x = 0; x < debugOutput.length; x++) System.out.println(debugOutput[x]); 3.4.3.8 Umgang mit Zeitzonen und Datums-Berechnungen Um Datums-Berechnungen in Plug-Ins durchzuführen kann wie bei Java üblich die Klasse java.util.GregorianCalendar verwendet werden. Diese sollte jedoch mit der in Proxy Sniffer konfigurierten Zeitzone initialisiert werden, damit die späteren Berechnungen korrekt verlaufen. Verwenden Sie darum die Klasse dfischer.utils.ZoneTime.getGregorianCalendar() um das aktuelle Datum in der konfigurierten Proxy Sniffer Zeitzone zu erhalten. Beispiel: [...] import java.util.Calendar; import java.util.GregorianCalendar; import dfischer.utils.ZoneTime; [...] // get current date and time in configured Proxy Sniffer time zone GregorianCalendar cal = ZoneTime.getGregorianCalendar(); // add 3 days to current date cal.add(Calendar.DAY_OF_YEAR, 3); // get future date int futureDay = cal.get(Calendar.DAY_OF_MONTH); int futureMonth = cal.get(Calendar.MONTH); int futureYear = cal.get(Calendar.YEAR); © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG // value range 1..31 // value range 0..11, 0 = January Alle Rechte vorbehalten Seite 26 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch 3.4.3.9 Erzeugen von Plug-In spezifischen Fehler-Arten Plug-Ins, welche an ein oder an alle URLs gebunden sind, können nach einem URL-Aufruf einen an sich erfolgreichen Aufruf des URLs auch in einen "roten" Fehler überführen, wobei der aktuelle Loop des simulieren Benutzers abgebrochen wird, und der simulierte Benutzer nun mit dem nächsten Loop fortfährt. Beispiel: /** * Execute plug-in after URL call. * * Intrinsic plug-in implementation. */ public void execute(Object context) { logVector = new LogVector(); LoadtestPluginContext pluginContext = (LoadtestPluginContext) context; if (firstCall) { // Definition von Plug-In spezifischen Fehler-Arten pluginContext.getPerformanceData().setErrorStatusTypeTranslation(HttpTestURL .STATUS_TYPE_PLUGIN_ERROR_CODE_1, "No Connection to External Service"); pluginContext.getPerformanceData().setErrorStatusTypeTranslation(HttpTestURL .STATUS_TYPE_PLUGIN_ERROR_CODE_7, "Internal Plug-In Error"); firstCall = false; } if (...) { // Auslösen eines Fehlers // report plug-in specific "red" error and terminate current loop of // simulated user -> continue with next loop of simulated user pluginContext.markUrlAsFailed(HttpTestURL.STATUS_TYPE_PLUGIN_ERROR_CODE_1, "Plug-In Error Message Text A"); } if (...) { // Auslösen eines Fehlers // report plug-in specific "red" error and terminate current loop of // simulated user -> continue with next loop of simulated user pluginContext.markUrlAsFailed(HttpTestURL.STATUS_TYPE_PLUGIN_ERROR_CODE_7, "Plug-In Error Message Text B"); } } Insgesamt können bis zu 10 verschiedene Plug-In spezifischen Fehler-Arten definiert werden (STATUS_TYPE_PLUGIN_ERROR_CODE_0 - STATUS_TYPE_PLUGIN_ERROR_CODE_9) und beim Auslösen jedes Fehlers kann zusätzlich noch eine eigene Fehlermeldung angegeben werden. © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 27 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch Die vom Plug-Ins ausgelösten Fehler werden in den "normalen" Detail-Diagrammen und Daten der Lasttest-Resultate dargestellt (zusammen mit allen anderen gemessenen Fehlern): © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 28 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch 3.4.4 Performante Plug-In Programmierung Die Ausführungs-Zeitdauer eines Plug-Ins selbst beeinflusst die gemessenen Antwortzeiten nicht. Dennoch benötigt ein Plug-In lokale CPU-Zeit zur Ausführung, was dazu führen kann, dass weniger virtuelle Benutzer vom Exec Agenten simuliert werden können – da dessen CPU früher überlastet wird (siehe auch „Leitfaden zum erfolgreichen Durchführen von Lasttests“, Kapitel 4 – Punkt 4). 3.4.4.1 Vorberechnen von Ergebnissen Um den Programm-Code eines Plug-Ins möglichst „CPU-schonend“ zu gestalten sollten vor allem repetitiv ausgeführte Programm-Abschnitte – welche jeweils bei wiederholten Aufrufen von execute zum gleichen Ergebnis führen – so modifiziert werden, dass deren Ergebnis nur beim ersten Mal berechnet wird und dabei dieses zusätzlich lokal im Plug-In abgespeichert wird. Bei allen weiteren Aufrufen desselben Programm-Abschnitts kann nun direkt auf das Ergebnis zugegriffen werden, ohne dass die Berechnung von neuem erfolgen muss. Um eine solche Optimierung ohne grossen Aufwand zu realisieren sollte bevorzugt auf die Klasse java.util.HashMap zurückgegriffen werden. Dabei stellt die fehlende interne Synchronisierung dieser Klasse kein Hindernis dar, da die Ausführung von Plug-In-Instanzen bereits durch Proxy Sniffer synchronisiert wird. Beispiel: Bei einem jedem URL-Aufruf soll zusätzlich die Web-Page (d.h. der Text des Page-Breaks) bestimmt werden. Der nicht-optimierte Code sieht zuerst so aus: public void execute(Object context) { LoadtestPluginContext pluginContext = (LoadtestPluginContext) context; PerformanceData performanceData = pluginContext.getPerformanceData(); int threadStep = pluginContext.getThreadStep(); String pageName = performanceData.getPageInfoTextOfUrl(threadStep); [...] D.h. dass jedes Mal aufgrund des aktuellen URL-Indexes (threadStep) der Text des PageBreaks neu bestimmt wird. Dies ist insofern von Bedeutung, da bei Proxy Sniffer nur das Schreiben bzw. das Sammeln von Messdaten bereits CPU-optimiert ist, jedoch nicht das Auslesen von Messergebnissen. Um dies nun zu Optimieren wird das Ergebnis nur ein Mal berechnet und in einer HashMap abgelegt. Der optimierte Code sieht nun so aus: import java.util.HashMap; public class TestPlugin implements LoadtestPluginInterface { HashMap pageInfoMap = new HashMap(); [...] public void execute(Object context) { LoadtestPluginContext pluginContext = (LoadtestPluginContext) context; PerformanceData performanceData = pluginContext.getPerformanceData(); int threadStep = pluginContext.getThreadStep(); String pageName; Object o = pageInfoMap.get(new Integer(threadStep)); If (o != null) © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 29 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch pageName = (String) o; // Zugriff auf vorberechnetes Ergebnis else { // Ergebnis ein erstes Mal berechnen und abspeichern pageName = performanceData.getPageInfoTextOfUrl(threadStep); pageInfoMap.put(new Integer(threadStep), pageName); } [...] Bedenken Sie, dass die nur ein Beispiel ist. Grundsätzlich können Sie diese Vorgehensweise bei jeder Programm-Aufgabe anwenden, bei der sich Ergebnisse von repetitiv ausgeführten Programm-Abschnitten vorberechnen lassen. Es lohnt sich oft etwas darüber nachzudenken was vorberechnet werden kann und was nicht. 3.4.4.2 Disk und Netzwerk I/O-Operationen I/O-Operationen benötigen in der Regel nicht so viel CPU, doch erfolgen diese Vorgänge Betriebssystem-intern mittels asynchron verarbeiteten Events, was je nach Volumen der I/OOperationen dazu führen kann, dass das gesamte Betriebssystem langsamer wird und bei (fast) allen Aufgaben mehr Durchlaufszeit zu Ausführung einzelner System-Routinen benötigt. Um dies zu optimieren sollten I/O-Verbindungen innerhalb der Methode execute nicht immer wieder geöffnet und gleich wieder geschlossen werden. Vielmehr sollten diese nur beim ersten Aufruf von execute – oder alternativ in der Methode construct – geöffnet werden. Danach sollte die Verbindung zum weiteren Datenaustausch dauernd offen bleiben. Das Schliessen der Verbindung kann dann später in der Methode deconstruct erfolgen. Bei Disk I/O-Operationen welche ein File während eines Lasttests erzeugen kann es auch sinnvoll sein, die File-Daten zuerst nur im Speicher zu sammeln (z.B. mittels java.util.ArrayList oder mittels java.io.PrintWriter(java.io.ByteArrayOutputStream)), und erst in der Methode deconstruct „in einem Rutsch“ auf Disk zu schreiben. Dies geht jedoch nur, falls die Daten nicht zu gross werden. Als Faustregel gilt, dass ein Plug-In insgesamt nicht mehr als 50 MB lokale Daten im Speicher halten soll. © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 30 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch 3.5 Plug-In Programm-Beispiele Bei Windows sowie bei Ubuntu (Linux) und Mac OS X Installationen von Proxy Sniffer finden Sie mehrere Programm-Beispiele (*.java Quellcode) von vorgefertigten Plug-Ins im Project Navigator Directory „MyTests \ Plugins“. Bei der Installation des Proxy Sniffer GUIs bei anderen Unix-ähnlichen Betriebssystemen (Solaris, …) wird das „MyTests / Plugins“ Directory im Project Navigator nicht automatisch erzeugt. Sie müssen dieses Directory darum von Hand selbst erstellen. Danach können Sie die vorgefertigten Plug-Ins und deren Quellcode von http://www.proxy-sniffer.com/download/ProxySnifferPlugins.zip herunterladen und danach den Inhalt dieses ZIP-Archivs in das Project Navigator Directory „MyTests / Plugins“ extrahieren. Hinweis: Beachten Sie, dass bei einer weiteren Installation von Proxy Sniffer die vorgefertigten Plug-Ins überschrieben werden. Falls Sie den Quellcode eines Plug-Ins abändern, so sollten Sie die entsprechende *.java Datei unter einem anderen Namen in „MyTests / Plugins“ abspeichern – und auch den Namen der Plug-In Klasse im Quellcode entsprechend anpassen. © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 31 / 32 Proxy Sniffer V 4.6 Plug-In Entwicklungshandbuch 4 Hersteller Ingenieurbüro David Fischer AG, Schweiz | A company of the Apica Group Product Web Site: http://www.proxy-sniffer.com Alle Rechte vorbehalten. © 2008, 2009, 2010, 2011, 2012 Ingenieurbüro David Fischer AG Alle Rechte vorbehalten Seite 32 / 32