Verteilter Last- und Performancetest mit JMeter/Ant

Inhalt

Problem
Einschränkungen
Vorraussetzungen
Installation
Testplan erstellen
JMeter und Ant
Reports erstellen
Report-Grafiken
Verteilter Test
Bekannte Fehler

Problem

Last- und Perfomancetest mit JMeter

  • Verteilter Test mit mehreren Knoten
  • Automatisierte Ausführung mehrerer Testpläne
  • Report-Erstellung

Einschränkungen

JMeter kommuniziert zwischen Master und den Slaves über RMI. Dies funktioniert nicht über Netzgrenzen hinweg. Wird dies doch gebraucht, muss ein geeigneter Proxy eingesetzt werden (wird hier nicht betrachtet).

Vorraussetzungen

Installation (Debian/Ubuntu)

#Aktualisierung der Paketquellen:
user@localhost:~$ sudo apt-get update

#Installation von Updates bestehender Software (optional):
user@localhost:~$ sudo apt-get upgrade

#Installation Ant (installiert OpenJDK automatisch mit, falls nicht vorhanden):
user@localhost:~$ sudo apt-get install ant

#Installation Oracle-Java
#Java Repo hinzufügen
user@localhost:~$ sudo echo "deb http://ppa.launchpad.net/webupd8team/java/ubuntu precise main" | tee -a /etc/apt/sources.list
user@localhost:~$ sudo echo "deb-src http://ppa.launchpad.net/webupd8team/java/ubuntu precise main" | tee -a /etc/apt/sources.list

#Import Repo Key
user@localhost:~$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys EEA14886

#Aktualisierung der Paketquellen
user@localhost:~$ sudo apt-get update

#Installer
user@localhost:~$ sudo apt-get install oracle-java8-installer

#Auflisten alle installierten Java-Versionen
user@localhost:~$ update-java-alternatives -l
java-1.7.0-openjdk-amd64 1071 /usr/lib/jvm/java-1.7.0-openjdk-amd64
java-8-oracle 1072 /usr/lib/jvm/java-8-oracle

#Setzen der richtigen Version
user@localhost:~$ sudo update-java-alternatives -s java-8-oracle

#Check ob alles geklappt hat
user@localhost:~$ java -version
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)

#Ende Java Installation

#Installation JMeter Ant Library:
user@localhost:~$ wget http://www.programmerplanet.org/media/ant-jmeter/ant-jmeter-1.1.1.jar
user@localhost:~$ sudo cp ant-jmeter-1.1.1.jar /usr/share/java/

#Installation JMeter (hier manuell und nicht über Paketquellen):
user@localhost:~$ mkdir jmeter_stress
user@localhost:~$ cd jmeter_stress
user@localhost:~$ wget http://ftp.halifax.rwth-aachen.de/apache//jmeter/binaries/apache-jmeter-2.13.zip
user@localhost:~$ unzip apache-jmeter-2.13.zip

Testplan erstellen

Wie ein Testplan erstellt wird, ist hier beschrieben. Damit an dieser Stelle schnell weitergemacht werden kann, kann hier ein Testplan Beispieltestplan, der 127.0.0.1 mit 2 Usern testet, heruntergeladen werden.

JMeter und Ant

Ziel ist es, alle Testpläne in einem Ordner auszuführen und die Resultate gesondert zu speichern. Dazu sind zwei Verzeichnisse anzulegen:

#Sicherstellen, dass wir im richtigen Ordner sind
user@localhost:~$ cd jmeter_stress
#Verzeichnis für Testfälle anlegen
user@localhost:~$ mkdir TestPlans
#Verzeichnis für Resultate anlegen
user@localhost:~$ mkdir Results

Die Basis build.xml ist einfach:

<project name="Distributed stress testing with jmeter and ant" basedir=".">
<!-- Löschen der alten Ergebnisse -->
<target name="clean">
<delete>
<fileset dir="Results"/>
</delete>
</target>

<!-- Starten von JMeter -->
<target name="jmeter" depends="clean">
<!-- Hier die heruntergeladene Ant-JMeter-Library -->
<taskdef name="jmeter" classpath="/usr/share/java/ant-jmeter-1.1.1.jar" classname="org.programmerplanet.ant.taskdefs.jmeter.JMeterTask"/>

<!-- JMeter Installation angeben -->
<!-- Resultatdatei angeben -->
<jmeter jmeterhome="${basedir}/apache-jmeter-2.13" resultlogdir="${basedir}/Results/">
<!-- Ausführen aller Testfälle im Ordner TestPlan -->
<testplans dir="${basedir}/TestPlan" includes="*.jmx"/>
</jmeter>
</target>

<target name="main" depends="clean,jmeter"/>
</project>

Das erste Target clean löscht die Resultate der alten Testläufe und ist die Basis für das Target jmeter. In jmeter wird der Ant-JMeter-Task org.programmerplanet.ant.taskdefs.jmeter.JMeterTask referenziert und parametrisiert. Dabei wird das jmeterhome und der Ort des Resultlogs resultlog gesetzt. testplans legt abschließende fest, welche Testpläne ausgeführt werden sollen (alle in dem Verzeichnis TestPlan).
Das Target main gruppiert alle Targets, so dass alle Targets im Standardfall ausgeführt werden.

Gestartet wird das Script folgendermaßen:

#Starten des Main Targets
user@localhost:~$ ant -f build.xml main
#Explizites Starten des jmeter Targets
user@localhost:~$ ant -f build.xml jmeter

Der Output sollte ungefähr so aussehen:

user@localhost:~$ ant -f build.xml main
Buildfile: /home/zarsen9/jmeter_stress/build.xml

clean:

jmeter:
[jmeter] Executing test plan: /home/zarsen9/jmeter_stress/TestPlan/test.jmx ==> /home/zarsen9/jmeter_stress/Results/test.jtl
[jmeter] Creating summariser

<summary>
[jmeter] Created the tree successfully using /home/zarsen9/jmeter_stress/TestPlan/test.jmx
[jmeter] Starting the test @ Tue Sep 29 14:10:30 CEST 2015 (1443528630036)
[jmeter] Waiting for possible shutdown message on port 4445
[jmeter] summary + 14 in 17.1s = 0.8/s Avg: 6934 Min: 5701 Max: 8481 Err: 0 (0.00%) Active: 8 Started: 17 Finished: 9
[jmeter] summary + 8 in 5.4s = 1.5/s Avg: 5995 Min: 5366 Max: 7540 Err: 0 (0.00%) Active: 0 Started: 18 Finished: 18
[jmeter] summary = 22 in 22.5s = 1.0/s Avg: 6593 Min: 5366 Max: 8481 Err: 0 (0.00%)
[jmeter] Tidying up ... @ Tue Sep 29 14:10:50 CEST 2015 (1443528650207)
[jmeter] ... end of run

main:

BUILD SUCCESSFUL
Total time: 22 seconds

Reports erstellen

JMeter bringt verschiedene Tools mit, die Resultate sinnvoll darzustellen. Hierzu befindet sich unter extras im JMeter-Verzeichnis eine xslt-Transformation jmeter-results-detail-report_21.xsl. Um diese nutzen zu können, müssen jedoch die Resultate in XML-Form vorliegen. Der Standard ist CSV. Dazu müssen im jmeter Target zwei Konfigurationsparameter angegeben werden:

<property name="jmeter.save.saveservice.assertion_results" value="all"/>
<property name="jmeter.save.saveservice.output_format" value="xml"/>

Es fehlt nun noch ein Target zum Starten der Transformation:

<target name="report" depends="jmeter">
<xslt in="${basedir}/Results/Results.xml" out="${basedir}/Results/Results.html" style="${basedir}/apache-jmeter-2.13/extras/jmeter-results-detail-report_21.xsl"/>
</target>

Das Target muss entsprechend noch in main integriert werden. Die build.xml sieht anschließend so aus:

<project name="Distributed stress testing with jmeter and ant" basedir=".">
<!-- Löschen der alten Ergebnisse -->
<target name="clean">
<delete>
<fileset dir="Results"/>
</delete>
</target>

<!-- Starten von JMeter -->
<target name="jmeter" depends="clean">
<!-- Hier die heruntergeladene Ant-JMeter-Library -->
<taskdef name="jmeter" classpath="/usr/share/java/ant-jmeter-1.1.1.jar" classname="org.programmerplanet.ant.taskdefs.jmeter.JMeterTask"/>

<!-- JMeter Installation angeben -->
<!-- Resultatdatei angeben -->
<jmeter jmeterhome="${basedir}/apache-jmeter-2.13" resultlogdir="${basedir}/Results/">
<!-- Ausführen aller Testfälle im Ordner TestPlan -->
<testplans dir="${basedir}/TestPlan" includes="*.jmx"/>
<!-- Resultate im XML-Format speichen -->
<property name="jmeter.save.saveservice.assertion_results" value="all"/>
<property name="jmeter.save.saveservice.output_format" value="xml"/>
</jmeter>
</target>

<target name="report" depends="jmeter">
<xslt basedir="${basedir}/Results/" destdir="${basedir}/Results/" includes="*.jtl" style="${basedir}/apache-jmeter-2.13/extras/jmeter-results-detail-report_21.xsl"/>
</target>

<target name="main" depends="clean,jmeter,report"/>
</project>

Das Result ist eine HTML-Datei:

jmeter_ant_results_html

Report-Grafiken

Auf Basis der JTL-Dateien kann nicht nur der Report erstellt werden, sondern auch Übersichtsgrafiken. Dazu kann die CMDRunner.jar (Dokumentation) genutzt werden.

Das Tool bietet verschiedene Diagrammtypen an:

  • ThreadsStateOverTime
  • BytesThroughputOverTime
  • HitsPerSecond
  • LatenciesOverTime
  • PerfMon
  • ResponseCodesPerSecond
  • ResponseTimesDistribution
  • ResponseTimesOverTime
  • ResponseTimesPercentiles
  • ThroughputVsThreads
  • TimesVsThreads
  • TransactionsPerSecond
  • PageDataExtractorOverTime
  • MergeResults

Die CMDRunner.jar ist in den JMeter Plugins enthalten. Diese findet man hier (JMeterPlugins-Standard-1.x.x.zip). Den Inhalt entsprechend in das JMeter-Verzeichnis integrieren.

Damit nicht für jeden Grafiktyp ein einzelnes Target geschrieben werden muss, wird es hier über ein FOR-Konstrukt gelöst. Dazu wird die Lib ant-contrib (Dokumentation)(Download) benötigt. Die jars werden im folgenden unter ${basedir}/lib/ erwartet. Als nächstes muss in die build.xml folgende taskdef hinzugefügt werden:

<taskdef uri="antlib:net.sf.antcontrib" resource="net/sf/antcontrib/antlib.xml">
<classpath>
<pathelement location="${basedir}/lib/ant-contrib-1.0b5.jar"/>
</classpath>
</taskdef>

An den prefix ac binden:

xmlns:ac="antlib:net.sf.antcontrib"

Die CMDRunner.jar wird anschließend pro erstellter Result-jtl und für die Diagrammtypen TimesVsThreads,ResponseTimesOverTime,ThreadsStateOverTime aufgerufen:

<target name="graph" depends="report">
<ac:for param="jtlfile">
<path>
<fileset dir="${basedir}">
<include name="**/*.jtl"/>
</fileset>
</path>
<sequential>
<local name="filename" />
<basename property="filename" file="@{jtlfile}"/>
<ac:for param="reportType" list="TimesVsThreads,ResponseTimesOverTime,ThreadsStateOverTime">
<sequential>
<java jar="${basedir}/apache-jmeter-2.13/lib/ext/CMDRunner.jar" fork="true">
<arg value="--tool"/>
<arg value="Reporter"/>
<arg value="--generate-png"/>
<arg value="${basedir}/Results/${filename}_@{reportType}.png"/>
<arg value="--input-jtl"/>
<arg value="${basedir}/Results/${filename}"/>
<arg value="--plugin-type"/>
<arg value="@{reportType}"/>
<arg value="--width"/>
<arg value="1024"/>
<arg value="--height"/>
<arg value="768"/>
</java>
</sequential>
</ac:for>
</sequential>
</ac:for>
</target>

Die build.xml sieht anschließend so aus:

<project name="Distributed stress testing with jmeter and ant" basedir="." xmlns:ac="antlib:net.sf.antcontrib">
<taskdef uri="antlib:net.sf.antcontrib" resource="net/sf/antcontrib/antlib.xml">
<classpath>
<pathelement location="${basedir}/lib/ant-contrib-1.0b5.jar"/>
</classpath>
</taskdef>

<!-- Löschen der alten Ergebnisse -->
<target name="clean">
<delete>
<fileset dir="Results"/>
</delete>
</target>

<!-- Starten von JMeter -->
<target name="jmeter" depends="clean">
<!-- Hier die heruntergeladene Ant-JMeter-Library -->
<taskdef name="jmeter" classpath="/usr/share/java/ant-jmeter-1.1.1.jar" classname="org.programmerplanet.ant.taskdefs.jmeter.JMeterTask"/>

<!-- JMeter Installation angeben -->
<!-- Resultatdatei angeben -->
<jmeter jmeterhome="${basedir}/apache-jmeter-2.13" resultlogdir="${basedir}/Results/">
<!-- Ausführen aller Testfälle im Ordner TestPlan -->
<testplans dir="${basedir}/TestPlan" includes="*.jmx"/>
<!-- Resultate im XML-Format speichen -->
<property name="jmeter.save.saveservice.assertion_results" value="all"/>
<property name="jmeter.save.saveservice.output_format" value="xml"/>
</jmeter>
</target>

<target name="report" depends="jmeter">
<xslt basedir="${basedir}/Results/" destdir="${basedir}/Results/" includes="*.jtl" style="${basedir}/apache-jmeter-2.13/extras/jmeter-results-detail-report_21.xsl"/>
</target>

<target name="graph" depends="report">
<ac:for param="jtlfile">
<path>
<fileset dir="${basedir}">
<include name="**/*.jtl"/>
</fileset>
</path>
<sequential>
<local name="filename" />
<basename property="filename" file="@{jtlfile}"/>
<ac:for param="reportType" list="TimesVsThreads,ResponseTimesOverTime,ThreadsStateOverTime">
<sequential>
<java jar="${basedir}/apache-jmeter-2.13/lib/ext/CMDRunner.jar" fork="true">
<arg value="--tool"/>
<arg value="Reporter"/>
<arg value="--generate-png"/>
<arg value="${basedir}/Results/${filename}_@{reportType}.png"/>
<arg value="--input-jtl"/>
<arg value="${basedir}/Results/${filename}"/>
<arg value="--plugin-type"/>
<arg value="@{reportType}"/>
<arg value="--width"/>
<arg value="1024"/>
<arg value="--height"/>
<arg value="768"/>
</java>
</sequential>
</ac:for>
</sequential>
</ac:for>
</target>

<target name="main" depends="clean,jmeter,report,graph"/>
</project>

Die Grafiken befinden sich dann entsprechend im Results-Ordner.

Verteilter Test

Ein verteilter Test bei JMeter funktioniert nach dem Master-Slave-Prinzip. Hierbei verteilt ein Master die Aufgaben an die Slaves, die die Testpläne ausführen und die Ergebnisse an den Master zurückliefern. Dieser aggregiert die Ergebnisse zu einem Gesamtergebnis.

Um den verteilten Test zu aktivieren, ist es auf dem geplanten Master notwendig, an zwei Stellen etwas zu änden:
1. jmeter.poperties angepassen (~./apache-jmeter-2.13/bin/jmeter.properties):

#---------------------------------------------------------------------------
# Remote hosts and RMI configuration
#---------------------------------------------------------------------------

# Remote Hosts - comma delimited
remote_hosts=127.0.0.1
#remote_hosts=localhost:1099,localhost:2010

Hier muss die Zeile remote_hosts=127.0.0.1 angepasst werden. Alle Server müssen als Kommaliste aufgeführt werden. Beispiel:

remote_hosts=192.168.0.44,192.168.0.45

2. build.xml angepassen:
Das Target jmeter muss hierbei um die Zeile runremote=”true” ergänzt werden:

<target name="jmeter" depends="clean">
<!-- Hier die heruntergeladene Ant-JMeter-Library -->
<taskdef name="jmeter" classpath="/usr/share/java/ant-jmeter-1.1.1.jar" classname="org.programmerplanet.ant.taskdefs.jmeter.JMeterTask"/>

<!-- JMeter Installation angeben -->
<!-- Resultatdatei angeben -->
<jmeter jmeterhome="${basedir}/apache-jmeter-2.13" resultlogdir="${basedir}/Results/" runremote="true">
<!-- Ausführen aller Testfälle im Ordner TestPlan -->
<testplans dir="${basedir}/TestPlan" includes="*.jmx"/>
<!-- Resultate im XML-Format speichen -->
<property name="jmeter.save.saveservice.assertion_results" value="all"/>
<property name="jmeter.save.saveservice.output_format" value="xml"/>
</jmeter>
</target>

Im letzten Schritt muss JMeter im Server-Mode (jmeter-server.sh) auf den Slave-Knoten gestartet werden:

user@localhost:~$ sh jmeter-server
Could not find ApacheJmeter_core.jar ...
... Trying JMETER_HOME=..
Found ApacheJMeter_core.jar
Created remote object: UnicastServerRef [liveRef: [endpoint:[192.168.0.44:6033](local),objID:[6595986d:15022daa81c:-7fff, 7502539226970863204]]]
Starting the test on host 192.168.0.44 @ Thu Oct 01 12:05:59 CEST 2015 (1443693959255)
Finished the test on host 192.168.0.44 @ Thu Oct 01 12:06:31 CEST 2015 (1443693991191)

Anschließend kann auf dem Master das Build-Script gestartet werden:

user@localhost:~$ ant -f build_d.xml main
Buildfile: /home/zarsen9/jmeter_stress/build_d.xml

clean:

jmeter:
[jmeter] Executing test plan: /home/zarsen9/jmeter_stress/TestPlan/test.jmx ==> /home/zarsen9/jmeter_stress/Results/test.jtl
[jmeter] Creating summariser

<summary>
[jmeter] Created the tree successfully using /home/zarsen9/jmeter_stress/TestPlan/test.jmx
[jmeter] Configuring remote engine: 192.168.0.44
[jmeter] Starting remote engines
[jmeter] Starting the test @ Thu Oct 01 12:06:04 CEST 2015 (1443693964944)
[jmeter] Remote engines have been started

Die Ergebnisse befinden sich dann im gewohnten Verzeichnis.

Fertig.

Bekannte Fehler

  • JMeter mit OpenJDK 64bit
    an error occurred: null
    

    Hier hilft nur eine Installation des Oracle(Sun)-Java.

  • Content is not allowed in prolog:
    report:
    [xslt] Processing /home/zarsen9/jmeter_stress/Results/Results.xml to /home/zarsen9/jmeter_stress/Results/Results.html
    [xslt] Loading stylesheet /home/zarsen9/jmeter_stress/apache-jmeter-2.13/extras/jmeter-results-detail-report_21.xsl
    [xslt] : Error! Content is not allowed in prolog.
    [xslt] : Error! com.sun.org.apache.xml.internal.utils.WrappedRuntimeException: Content is not allowed in prolog.
    [xslt] Failed to process /home/zarsen9/jmeter_stress/Results/Results.xmlBUILD FAILED
    /home/zarsen9/jmeter_stress/build_1.xml:30: javax.xml.transform.TransformerException: javax.xml.transform.TransformerException: com.sun.org.apache.xml.internal.utils.WrappedRuntimeException: Content is not allowed in prolog.
    

    Es fehlen die Parameter für den JMeter Task (siehe auch oben):

    <property name="jmeter.save.saveservice.assertion_results" value="all"/>
    <property name="jmeter.save.saveservice.output_format" value="xml"/>
    
  • Loopback address:
    Created remote object: UnicastServerRef [liveRef: [endpoint:[127.0.1.1:54756](local),objID:[-2cfbeed9:15022d06348:-7fff, -3076280909307198561]]]
    Server failed to start: java.rmi.RemoteException: Cannot start. xxxxxxxxxxxxxx is a loopback address.
    

    Starten des Servers mit der korrekten IP oder des Hostnames mit

    ./jmeter-server -Djava.rmi.server.hostname=xxx.xxx.xxx.xxx
    
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s