Stresstest mit Gatling

Um Web-Anwendungen und Web Services unter Last zu testen ist ein Tool notwendig, mit dem viele User simuliert und der Server mit vielen Requests gestresst werden kann. Gatling ist ein Werkzeug, das genau dafür geeignet ist und sehr einfach aufgesetzt werden kann.
Im folgenden möchte ich Gatling kurz vorstellen und an einem Praxisbeispiel zeigen, wie ein Web Service einem Lasttest unterzogen wird.

Theorie

logo-gatlingGatling ist ein Open Source Tool, das auf der Java Virtual Machine läuft. Die Entwicklung und die Ausführung von Lasttests ist sehr effizient. Test-Szenarien werden in einer Scala basierten DSL programmiert oder mit dem Gatling-Recorder aufgezeichnet. Intern nutzt Gatling zur Ausführung der Lasttests einen asynchronen Ansatz mit dem Actor Model und asynchrone und damit non-blocking IOs. Die technologische Basis ist Akka. Dadurch können viele Requests abgesetzt werden, ohne dass Threads auf Antwort warten und blockiert sind.
Nach der Ausführung des Lasttest generiert Gatling einen HTML-Report mit dem Ergebnis.

Praxis

Installation

Gatling muss lediglich runter geladen und in einem beliebigen Ordner entpackt werden. Diesen Ordner nenne ich im folgenden %GATLING_HOME%. Eine Java Runtime muss auf dem Rechner installiert und eingerichtet sein (Verweis auf die java.exe in der PATH-Variablen). Der einfachste Weg zum Testen von Java ist, in der Kommandozeile java -version einzugeben. Wird die Java Version der installierten JVM angezeigt passt alles. Es empfiehlt sich das aktuelle JDK zu installieren. Bei der JRE kann der der Fehler „missing ’server‘ JVM at …“ beim Start von Gatling auftreten.
Um Gatling zu starten wird im Verzeichns %GATLING_HOME%/bin das Script gatling.bat (Windows) oder gatling.sh (Linux) ausgeführt.

Lasttest entwickeln

Lasttest können mit dem Recorder aufgezeichnet oder mit einer Scala DSL entwickelt werden. Am einfachsten nimmt man ein Sample und passt dieses an. Beispiele befinden sich in  %GATLING_HOME%/user-files/simulations.
Zunächst wird eine HTTP-Konfiguration benötigt. Damit wird die grundlegende HTTP Kommunikation definiert. Es wird die Base-URL der Web Anwendung, die zu testen, ist und die HTTP Header festgelegt (optional).

val httpConf = httpConfig
   .baseURL("http://localhost:8080/Hello-World")
   .acceptCharsetHeader("ISO-8859-1,utf-8;q=0.7,*;q=0.7")
   .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
   .acceptEncodingHeader("gzip, deflate")
   .acceptLanguageHeader("fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3")
  .disableFollowRedirect

Als nächstes werden Szenarien definiert, z.B. wie User durch eine Anwendung navigieren.

val scn = scenario("Hello Servlet Test")
   .exec(
      http("get_index.html")
         .get("/index.html")
         .check(status.is(200)))
   .pause(0 milliseconds, 100 milliseconds)
   .exec(
      http("get_HelloServlet")
         .get("/HelloServlet")
         .check(status.is(200)))

In Zeile 1 bekommt das Szenario eine Namen. Die exec Anweisung in Zeile 2 definiert eine User-Aktion. In Zeile 3 wird der Aktion ein Name gegeben. Dieser wird später im Report verwendet. Zeile 4 legt fest, das ein Request mit der GET-Method auf die angegebene URL durchgeführt wird. Die URL ist relativ zur Base-URL, die in der HTTP-Konfig angegeben wurde. In Zeile 5 wird die Antwort geprüft, im konkreten Fall, ob der Response-Code 200 ist. Zeile 6 definiert eine Pause (kann genutzt werden um die Zeit zu simulieren, die ein User auf einer Seite verbringt um sie zu lesen, bevor er die nächste Aktion ausführt). In den folgenden Zeilen wird die nächste Aktion nach dem gleichen Schema definiert wie die erste.
Die letzte Anweisung setzt den Lasttest auf.

setUp(scn.users(1000).ramp(10).protocolConfig(httpConf))

Für das unter der Variablen scn definierte Szenario werden 1000 User festgelegt, die in 10 Sekunden Intervallen ihre Aktionen starten. Das führt dazu, dass nicht alle 1000 User gleichzeitig mit den Requests beginnen und 100 Requests pro Sekunde an den Server gestellt werden.

Das Script muss im Ordner %GATLING_HOME%/user-files/simulations abgelegt werden. Wichtig dabei ist zu beachten, dass ein Unterordner gemäß des package Namens des Scripts angelegt wird.

Lasttest ausführen

Um das Script auszuführen wird Gatling mit dem Script gatling.bat bzw. gatling.sh im Verzeichnis %GATLING_HOME%/bin gestartet. Nach dem Start erscheint ein Menü mit den Scripten die unter %GATLING_HOME%/user-files/simulations abgelegt sind. Es muss lediglich die Nummer des gewünschten Scriptes angegeben werden. Gatling stellt noch ein paar optionale Fragen (Name und Beschreibung für den Report) und dann rennt das Script los.
Nach Beendigung steht im Ordner %GATLING_HOME%/results der HTML-Report. Für jeden Lauf wird ein Unterordner angelegt.

Beispiel für Web Service Test

Szenario

Im Beispiel soll ein Web Service, der über HTTP-SOAP bereit gestellt wird getestet werden. Der Web Service bekommt eine UserID übergeben und liefert Details zu dem User. Der Test soll parametrisiert sein, so dass der Service mit verschiedenen UserIDs aufgerufen wird und nicht immer mit der gleichen. Die UserIDs, mit denen der Test ausgeführt wird, werden in einem csv-File abgelegt. Das XML mit dem SOAP-Envelope wird in einem Template File abgelegt und aus dem Script verwendet.

Umsetzung

Gatling unterstützt die Parametrisierung von Requests mit Hilfe von csv-Files. Wir legen im csv-File die UserIDs ab. In der ersten Zeile müssen die Namen der Spalten angegeben werden, in den folgenden Zeilen werden die Daten angegeben. Die einzelnen Spalten werden mit Kommas getrennt (welch Überraschung in einem csv-File 🙂 ). Wir haben nur eine Spalte mit den UserIDs.

userid
meineUID1
meineUID2
meineUID3

Das csv-File muss im Verzeichnis %GATLING_HOME%/user-files/data abgelegt werden. Wir legen es unter dem Namen userids.csv ab.

Ein HTTP-SOAP Web Service wird mit einem POST-Request aufgerufen. Im POST-Body steht der SOAP-Envelope als XML. Damit wir dieses XML nicht mühsam selbst zusammenbauen müssen, verwenden wir den Template-Mechanismus von Gatling. Um eine Vorlage für das XML zu bekommen kann SOAP UI verwendet werden. Dazu mittels SOAP UI aus dem WSDL File des Services einen Request für den Web Service erzeugen lassen und das XML aus dem Request-Fenster von SOAP UI in ein File mit der Endung .ssp kopieren.

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:user="http://wwag.de/integration/samples/userServ">
   <soapenv:Header/>
   <soapenv:Body>
      <user:getUserInfo>
         <userInfoRequest>
            <userId><%= userid %></userId>
         </userInfoRequest>
      </user:getUserInfo>
   </soapenv:Body>
</soapenv:Envelope>

In Zeile 6 steht eine Variable anstelle der UserID, die über den Web Service angefragt wird. Hier werden die UserIDs aus dem csv-File eingesetzt.
Das Template muss im Verzeichnis %GATLING_HOME%/user-files/request-bodies abgelegt werden. Wir legen es unter dem Namen userservRequest.ssp ab.

Nun setzen wir das Script auf, das den Web Service aufruft.

package userservice

import com.excilys.ebi.gatling.core.Predef._
import com.excilys.ebi.gatling.http.Predef._
import com.excilys.ebi.gatling.jdbc.Predef._
import com.excilys.ebi.gatling.http.Headers.Names._
import akka.util.duration._
import bootstrap._

class WebServiceExample extends Simulation {

   val httpConf = httpConfig
      .baseURL("http://localhost:9080/UserService")
      .acceptCharsetHeader("ISO-8859-1,utf-8;q=0.7,*;q=0.7")
      .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")

   val scn = scenario("User Service Test")
      .feed(csv("userids.csv").random)
      .exec(
         http("post_UserServ")
         .post("/UserServImplService")
         .fileBody("userservRequest", Map("userid" -> "${userid}"))
         .check(status.is(200)))

   setUp(scn.users(1000).ramp(10).protocolConfig(httpConf))
}

Die einzelnen Code Zeilen sind weiter oben schon beschrieben, deshalb gehe ich hier nur noch auf die Zeilen ein, die bisher noch nicht besprochen wurden.
In Zeile 21 wird das csv-File mit unseren UserIDs referenziert. Mit der Anweisung feed können Parameter angegeben werden. Per default werden die Einträge im csv-File als Queue verwendet. Immer wenn ein Eintrag benutzt wurde, wird er aus der Queue entfernt. In diesem Modus müssen im csv-File mindestens so viele Einträge sein, wie Requests gesendet werden. Da dies bei einem Lasttest meist nicht praktikabel ist, verwende ich die Anweisung random. Damit wird, pro Request, per Zufall ein Eintrag aus der Liste verwendet. Die Einträge werden dabei nicht aus der Liste entfernt.
In Zeile 24 wird ein POST-Request an die Web Service URL gesendet. Zeile 25 legt den POST-Body fest. Hier wird das zuvor erstellte Template userservRequest verwendet.Die Anweisung fileBody verwendet als Body den Inhalt aus einem File. Als erster Parameter wird der Name des Files angegeben. Da hier ein ssp-Template verwendet wird, muss die Endung .ssp nicht mit angegeben werden. Der zweite Parameter ist eine Map mit den Daten, die im Template eingesetzt werden sollen. Der Key der Map ist der Name der Variablen im Template, die ersetzt werden soll (bei uns war das die Anweisung <%= userid %> im Template) . Der Value der Map ist der Wert mit dem die Variable im Template ersetzt werden soll. Hier geben wir eine Variable (${userid}) an um die Werte aus dem csv-File zu verwenden. Der Variablen-Name muss dem Spalten-Namen aus dem csv-File entsprechen (Zeile 1 im csv-File).
Das Script wird im Verzeichnis %GATLING_HOME%/user-files/simulations/userservice gespeichert.

Fertig ist das Script, das HTTP-SOAP Requests aus einem Template File, parametrisiert mit Daten aus einem csv-File, sendet.
Zur Ausführung, wie oben beschrieben, Gatling starten und das Script auswählen.

Schreibe einen Kommentar

Anmelden um einen Kommentar abzugeben.

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

*