Versionen im Vergleich
Schlüssel
- Diese Zeile wurde hinzugefügt.
- Diese Zeile wurde entfernt.
- Formatierung wurde geändert.
In 1 - Szenario: GrundlagenMetriken haben wir unseren ersten erfolgreichen JMeter-Test geschrieben und ausgeführtMetriken exponiert. In diesem Tutorial erzeugen wir Last und checken am Ende, ob dies zu den erzeugten Metriken passt.
Vorbedingung
Als Vorbedingung benötigen wir natürlich unsere JWT-Anwendung, die wir aufrufen und testen können. In den folgenden Tutorials gehen wir die Performance beim Laden eines Dokuments.
Dokument laden
Beim Laden eines Dokuments geht standardmäßig eine Anfrage an den Server, die das Laden initialisiert. Diese Anfrage wollen wir aufzeichnen, um das Laden in unserem Test nachstellen zu können.davon aus, dass wir die Anwendung aus Tutorial 005 lokal unter folgender URL deployed haben: http://localhost:8080/
Übersicht Szenario Ablauf
- Die im folgenden untersuchte Datei findet sich als tutorial-jmeter-testplan.jmx im Ordner jwt-tutorial-performancetest/src/test/jmeter/
- Die Datei sollte in der JMeter UI zum Editieren geöffnet werden. In Windows ist das z.B. über die Datei bin/jmeter.bat im JMeter Verzeichnis möglich.
- Die erste ThreadGroup wird gestartet: 15 Threads werden zeitversetzt gestartet, um 15 User zu simulieren
- Jeder User verbindet sich zum Server und lädt ein Dokument
- Jeder User lädt Seite 1 in 100% Zoomgröße und danach diese Seite nochmal in 111% Zoomgröße.
- Danach wird eine Schleife durchlaufen, die automatisch über die Seiten 2 bis 5 iteriert und diese Seiten in 100% Zoomgröße lädt.
- Die Ergebnisse der einzelnen HTTP-Requests werden ausgeloggt im "View Results Tree".
- Sobald die erste ThreadGroup vollständig abgearbeitet wurde, wird die zweite ThreadGroup gestartet.
- In der zweiten ThreadGroup wird ein einziger HTTP Request auf http://127.0.0.1:8080/actuator/metrics/jwt.render.tile gefeuert, der die Metrik bzgl. des Kachelrenderings zurück gibt.
- per "JSON Extractor" und "JSON Assertion" wird überprüft, ob wie erwartet genau 90 Kacheln gerendert wurden.
- Im letzten "Results Tree" wird das Ergebnis aus 8) ausgegeben.
Anpassung des Testplans
Damit der Testplan an evtl. vorhandene Integrations-spezifischen Bedürfnisse angepasst werden kann, wird erklärt, wie die einzelnen Bausteine des Testplans erstellt wurden.
Anpassung des Testplans: UUID erstellen
Image Added
Das obige Bild zeigt, wie man eine neue UUID anlegt. Zu beachten ist hier, dass ${__UUID()} eine JMeter Funktion ist, die bei jedem Thread zu einem anderen Wert evaluiert.
Wir haben hier 2 UUIDs: die client UUID wird durch das Setzen von "Update Once per Iteration" nur einmal pro Laufzeit eines Threads erzeugt, während die pageSegmentId für jeden Request neu erzeugt wird.
Dies würde man anders machen, falls für eine Seite 2 Kacheln erzeugt würden, z.B. wenn man dieselbe Seite für die Haupt-Pageview und für die ThumbnailPageView rendern möchte - dann würde man dieselbe pageSegmentId verwenden.
Durch die erzeugte clientId können wir nun das initiale Laden der Seite simulieren: http://127.0.0.1:8080/jwt/transport/longpoll?clientId=${clientId}&messageId=-1&suspend=false
Anpassung des Testplans: Laden des Dokuments
Image Added
Aufzeichnen des Requests
Bereits vor dem Aufruf sollten wir die Entwicklerwerkzeuge unseres Browsers öffnen (meist über F12). Ist die Seite vollständig geladen, können wir in den Reiter Netzwerk wechseln und die Anfragen nach XHR filtern. Uns interessiert der Request, der den Service-Call createDocument enthält. In Google Chrome funktioniert das ungefähr so:
Erweitern | ||
---|---|---|
| ||
Den kopierten Request legen wir nun unter /src/test/resources/bodies in der Textdatei Request_Load_File.txt ab.
Testcode
Zur Erstellung dieses Testfalls legen wir die Datei SimulationFileLoading.scala unter src/test/scala/com/levigo/jadice/web/performance an.
Header setzen
Um gültige Anfragen zu senden und Fehler auszuschließen, müssen wir die Header der Anfragen korrekt setzen. Wir ergänzen folgende Header in das neue Szenario:
Codeblock | ||||||
---|---|---|---|---|---|---|
| ||||||
val defaultHeader = Map(
"Accept" -> "*/*",
"Accept-Encoding" -> "gzip, deflate, br",
"Accept-Language" -> "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7",
"Cache-Control" -> "no-cache",
"Connection" -> "keep-alive",
"Content-Type" -> "text/plain;charset=UTF-8",
"DNT" -> "1",
"Origin" -> "http://localhost:8080",
"Pragma" -> "no-cache",
"User-Agent" -> "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
"X-GWT-Module-Base" -> "http://localhost:8080/imageviewer/",
"X-GWT-Permutation" -> "79A1155DD3CB7EBBA33EE56F4B55FD77")
|
Damit der Server jeden unserer Clients eindeutig identifizieren kann, muss jeder Request die zugehörige Client-ID beinhalten. Die Generierung erfolgt als erste Aktion im Szenario.
Codeblock | ||||||
---|---|---|---|---|---|---|
| ||||||
.exec(_.set("uuid", java.util.UUID.randomUUID)) |
Über das Statement ${uuid} können wir jetzt auf die UUID zugreifen, wie im folgenden Abschnitt veranschaulicht.
Aufruf ausführen
Da wir nun die Header und die Payload vorbereitet haben, können wir das Szenario um den Aufruf ergänzen:
Codeblock | ||||||
---|---|---|---|---|---|---|
| ||||||
.exec(http("Load Document")
.post("/jwt/transport/longpoll?clientId=${uuid}&messageId=-1")
.headers(defaultHeader)
.body(RawFileBody("src/test/resources/bodies/Request_Load_File.txt"))
) |
Der Test sollte jetzt lauffähig sein und ein Dokument anfragen:
language | scala |
---|---|
theme | RDark |
title | Simulation - vollständig |
collapse | true |
Der kopierte Request wurde in etwa so in den Sampler "Load Document" kopiert.
Dabei sind folgende Änderungen zu beachten:
- Es muss immer ein Request aus dem aktuellen Integrationsprojekt genommen werden. Allein durch eine Änderung der jadice web toolkit Version kann der Request bereits komplett ungültig sein.
- der URL Parameter "clientId" wird im Path http://127.0.0.1:8080/jwt/transport/longpoll?clientId=${clientId}&messageId=1 zu ${clientId} ersetzt, wie im obigen Schritt beschrieben. Wird dies nicht gemacht und stattdessen die full blown UUID verwendet, wird die Simulation immer mit demselben Client durchgeführt, was den Server dann dazu veranlassen wird, diesen Client herunter zu bremsen. Sprich im Falle einer inkorrekten Handhabung wird der Server nur gedrosselt Last erzeugen.
- Im veröffentlichten Testplan (s. Body Data) wird eine in C:/Temp/Test.pdf hinterlegte Datei verwendet, um mögliche Netzwerkverzögerungen zu umgehen - wie das in Ihrem Szenario aussehen soll ist Ihnen überlassen.
Anpassung des Testplans: Tile Rendering
Um diesen Teil zu verstehen, beschäftigen wir uns kurz mit den JWT-Mechanismen zur Anzeige von Dokumenten. Will der JWT-Client ein Dokument anzeigen, fordert er vom JWT-Server entsprechend gerenderte Bilddaten an. Dabei liefert der Server nicht das komplett gerenderte Dokument, sondern teilt das Dokument in sogenannte Tiles bzw. Kacheln auf. Der Ausschnitt der Tiles hängt von verschiedenen Faktoren ab:
- Ausgewählte Seite
- Zoomlevel
- Scrollposition
- Rendereinstellungen (z.B. Gradation)
Die Tiles sind PNGs und werden vom Server über ein Servlet, dem TileServlet, zur Verfügung gestellt. Der Pfad des Tileservlets mappt standardmäßig auf /jwt/tile.
Scrollen
Wenn wir bzw. unsere Anwender nun innerhalb eines Dokuments scrollen, blättern oder zoomen, schickt der JWT-Client jeweils einen neuen Request an das TileServlet. Der JWT-Server rendert dann den passenden Ausschnitt und stellt diesen dem Client zur Verfügung.
Wir beschränken uns hier auf das Vorgehen beim Scrollen. Bei den anderen Szenarien würden wir aber analog vorgehen.
Beim Scrollen werden nacheinander Tiles zum gerade angezeigten Ausschnitt angefordert. Wie schon im letzten nutzen wir die Entwicklerwerkzeuge unseres Browsers, um die Anfragen an das TileServlet zu identifizieren. Sobald wir durch das Dokument nach unten scrollen, sollten mehrere solche Request gelistet sein (/jwt/tile?c=..&v=...). Wir kopieren die komplette Request-URL und erstellen für jede Anfrage einen neuen Aufruf innerhalb des Szenarios.
Erweitern | ||
---|---|---|
| ||
Image Added |
Die Anfragen an das TileServlet sind wieder einfache GET-Requests:
Codeblock | ||||||
---|---|---|---|---|---|---|
| ||||||
https://webtoolkit.jadice.com/enterprise/jwt/tile?c=26544984-4c81-7a1c-4c81-7a1c4c817a1c&v=2654498e-1546-ee47-1546-ee471546ee47&r=7*0*13**0F127572DA9D94B90BFF4A2A8781B4B5*com.levigo.jadice.web.shared.model.internal.FlattenedRenderSpecificationData/4206259206*com.levigo.jadice.web.shared.model.settings.AnnotationRenderSettings/3358781670*[I/2970817851*[Lcom.levigo.jadice.web.shared.model.PageSegmentHandle;/270004123*com.levigo.jadice.web.demo.common.shared.service.sources.ClassPathWithAnnoHandle/1755717260*Factsheet+jadice+web+toolkit+(PDF)*/Info_jadice_web_toolkit.pdf*document*e62ae968-4f61-4049-a134-e6689679b654*[Ljava.lang.String;/2600011424*ROT_000*1*2*3*4*4*0*0*96*5*4*0*0*255*255*6*1*7*8*9*0*10*4*11*12*0*127*89*0*0*13*0.1132* |
Tile-Request
Abschließend schauen wir uns die aufgezeichneten Requests noch einmal genauer an:
Codeblock | ||||
---|---|---|---|---|
| ||||
/jwt/tile?c=26544984-4c81-7a1c-4c81-7a1c4c817a1c&v=2654498e-1546-ee47-1546-ee471546ee47&r=7*0*13**0F127572DA9D94B90BFF4A2A8781B4B5*com.levigo.jadice.web.shared.model.internal.FlattenedRenderSpecificationData/4206259206*com.levigo.jadice.web.shared.model.settings.AnnotationRenderSettings/3358781670*[I/2970817851*[Lcom.levigo.jadice.web.shared.model.PageSegmentHandle;/270004123*com.levigo.jadice.web.demo.common.shared.service.sources.ClassPathWithAnnoHandle/1755717260*Factsheet+jadice+web+toolkit+(PDF)*/Info_jadice_web_toolkit.pdf*document*e62ae968-4f61-4049-a134-e6689679b654*[Ljava.lang.String;/2600011424*ROT_000*1*2*3*4*4*0*0*96*5*4*0*0*255*255*6*1*7*8*9*0*10*4*11*12*0*127*89*0*0*13*0.1132* |
In den Request-Parametern sind unterschiedliche Informationen enthalten:
- Infos zum Rendering in FlattenedRenderSpecificationData und AnnotationRenderSettings.
- Unser PageSegmentHandle, das UrlHandle (vgl. 2 - Laden eines Dokuments). Es enthält alle Felder des Handles in serialisierter Form, in unserem Fall also die URL des geladenen Dokuments.
- Zum Schluss noch weitere Informationen, wie z.B. die Rotation: ROT_000.
Weil es sich um einen einfachen GET-Request handelt, können wir die vollständige URL auch direkt in den Browser eingeben und das gerenderte Tile wird im Browser dargestellt. Eine Änderung des Rotationswerts auf z.B. ROT_180 zeigt das Dokument auf dem Kopf an.
Info |
---|
Aufgrund der Struktur des TileRequest müssen wir folgendes beachten:
|
Anpassung des Testplans: Loops
Image Added
Hier wird, anstatt für jede neue Seite einen manuell erstellten Request zu hinterlegen, für die Seiten 2-5 eine Schleife verwendet. Zusätzlich zum obigen Abschnitt muss dafür am Ende des Requests noch die Variable pageCounter verwendet werden, die unterhalb des "Loop Controller" definiert ist. Durch "Starting value" 2, "Increment" 1 und "Maximum 5" erzeugen wir Requests genau für die erwähnten Seiten 2-5.
Beachten Sie bitte auch, dass wir sogar im Titel für den Request "Tile with pageIndex ${pageCounter}" die Variable stehen haben - dadurch wird nachher im Results Tree die korrekte Seite geloggt.
Anpassung des Testplans: Automatisches Überprüfen des angefragten JSONs
Für den Task "Überprüfe, ob die exponierten Metriken stimmen" wurden zwei JMeter Elemente neu angelegt: Post Processors → JSON Extractor und Assertions → JSON Assertion.
Es wird am Ende ein JSON wie folgt erwartet:
Codeblock | ||
---|---|---|
| ||
{
"name":"jwt.render.tile",
"description":null,
"baseUnit":"seconds",
"measurements":[
{
"statistic":"COUNT",
"value":90.0
},
{
"statistic":"TOTAL_TIME",
"value":415.4121875},
{
"statistic":"MAX","value":0.0
}
],
"availableTags":[]
} |
Vor allem soll überprüft werden, dass der "Count" genau 90 ist.
Das JSON selbst wird analog wie oben beschrieben über einen HTTP Sampler per simplen GET auf http://127.0.0.1:8080/actuator/metrics/jwt.render.tile geholt.
Evaluiert wird dieser JSON Path:
Codeblock | ||
---|---|---|
| ||
measurements[?(@.statistic=='COUNT')].value |
Zur Überprüfung eigener JSON Strukturen kann man z.B. https://jsonpath.com/ benutzen. Gibt man dort als "Input" das oben gezeigte JSON und als JSON Path das CodeSnippet aus der obigen Zeile ein, sollte als "Evaluation Result" das Fragment "{90}" stehen.
Tutorials
- 0 - Projekt einrichten
- 1 - Szenario: GrundlagenMetriken
- 2 - Szenario: Dokument laden3 - Szenario: Bildabschnitte ladenAufbau des Testplans