Skip to content
Fetching contributors…
Cannot retrieve contributors at this time
2514 lines (1998 sloc) 87.8 KB
%%% user_manual.tex ---
%% Author: Nicolas Niclausse
%% Version: $Id$
\def\RCS$#1: #2 ${\expandafter\def\csname RCS#1\endcsname{#2}}
% --------------------------------------------
% Title
% --------------------------------------------
\doctitle{Tsung User's manual}
\doccopyright{Nicolas Niclausse.}
\subsection{What is Tsung ?}
\program{Tsung} (formerly IDX-Tsunami) is a distributed load testing tool. It is
protocol-independent and can currently be used to stress HTTP, WebDAV, SOAP,
PostgreSQL, MySQL, LDAP, and Jabber/XMPP servers.
It is distributed under the GNU General Public License version 2.
\subsection{What is Erlang and why is it important for Tsung ?}
\program{Tsung's} main strength is its ability to simulate a huge number of
simultaneous user from a single CPU. When used on cluster you can
generate a really impressive load on a server with a modest cluster,
easy to set-up and to maintain.
\program{Tsung} is developed in Erlang and this is where the power
of \program{Tsung} resides.
\par Erlang is a \emph{concurrency-oriented} programming language.
Tsung is based on the Erlang OTP (Open Transaction Platform) and
inherits several characteristics from Erlang:
\item \emph{Performance}: Erlang has been made to support hundred thousands
of lightweight processes in a single virtual machine.
\item \emph{Scalability}: Erlang runtime environment is naturally
distributed, promoting the idea of process's location transparency.
\item \emph{Fault-tolerance}:Erlang has been built to develop robust,
fault-tolerant systems. As such, wrong answer sent from the server
to \program{Tsung} does not make the whole running benchmark crash.
More information on Erlang on \url{} and
\subsection{Tsung background}
\item \program{Tsung} development was started by Nicolas Niclausse in
2001 as a distributed jabber load stress tool for internal use at
\url{}. It has evolved as an open-source
multi-protocol load testing tool several months later. The HTTP
support was added in 2003, and this tool has been used for several
industrial projects. It is now hosted by Erlang-projects, and
supported by \url{}. The list of contributors
is available in the source archive
\item It is an industrial strength implementation of a \emph{stochastic model}
for real users simulation. User events distribution is based on a
Poisson Process. More information on this topic in:
Z. Liu, N. Niclausse, and C. Jalpa-Villanueva. \strong{Traffic Model
and Performance Evaluation of Web Servers}. \emph{Performance Evaluation,
Volume 46, Issue 2-3, October 2001}.
\item This model has already been tested in the INRIA \emph{WAGON}
research prototype (Web trAffic GeneratOr and beNchmark). WAGON was
used in the \url{} project (Very High Broadband
IP/WDM test platform for new generation Internet applications, 2000-2004).
\program{Tsung} has been used for very high load tests:
\item \emph{Jabber/XMPP} protocol:
\item 90 000 simultaneous jabber users on a
4-node Tsung cluster (3xSun V240 + 1 Sun V440)
\item 10 000 simultaneous users.
\program{Tsung} was running on a 3-computers cluster (CPU
\item \emph{HTTP and HTTPS} protocol:
\item 12 000 simultaneous users.
\program{Tsung} were running on a 4-computers cluster. The
tested platform reached 3 000 requests per second.
\item 10 million simultaneous users running on a 75-computers cluster,
generating more than one million requests per second.
\program{Tsung} has been used at:
\item \emph{DGI} (Direction Générale des impôts): French finance ministry
\item \emph{Cap Gemini Ernst \& Young}
\item \emph{IFP} (Institut Français du Pétrole): French Research Organization
for Petroleum
\item \emph{LibertySurf}
\item Sun\texttrademark for their Mooddlerooms platform on Niagara processors:
\subsection{Tsung main features}
\item \emph{High Performance}: \program{Tsung} can simulate a
huge number of simultaneous users per physical computer: It can
simulates thousands of users on a single CPU (Note: a simulated user
is not always active: it can be idle during a \varname{thinktime}
period). Traditional injection tools can hardly go further than a
few hundreds (Hint: if all you want to do is requesting a single URL
in a loop, use \program{ab}; but if you want to build complex
scenarios with extended reports, \program{Tsung} is for you).
\item \emph{Distributed}: the load can be distributed on a cluster of
client machines
\item \emph{Multi-Protocols} using a plug-in system: HTTP (both standard
web traffic and SOAP), WebDAV, Jabber/XMPP and PostgreSQL are currently
supported. Experimental LDAP and MySQL plugins were included in the 1.3.0 release.
\item \emph{SSL} support
\item \emph{Several IP addresses} can be used on a single machine using
the underlying OS IP Aliasing
\item \emph{OS monitoring} (CPU, memory and network traffic) using Erlang
agents on remote servers or \emph{SNMP}
\item \emph{XML configuration system}: complex user's scenarios are written
in XML. Scenarios can be written with a simple browser using the
Tsung recorder (HTTP and PostgreSQL only).
\item \emph{Dynamic scenarios}: You can get dynamic data from the
server under load (without writing any code) and re-inject it in
subsequent requests. You can also loop, restart or stop a
session when a string (or regexp) matches the server response.
\item \emph{Mixed behaviours}: several sessions can be used to simulate
different type of users during the same benchmark. You can define
the proportion of the various behaviours in the benchmark scenario.
\item \emph{Stochastic processes}: in order to generate a realistic traffic,
user thinktimes and the arrival rate can be randomize using a probability
distribution (currently exponential)
\subsection{HTTP related features}
\item HTTP/1.0 and HTTP/1.1 support
\item GET, POST, PUT, DELETE and HEAD requests
\item Cookies: Automatic cookies management( but you can also manually
add more cookies)
\item \verb|'|GET If-modified since\verb|'| type of request
\item WWW-authentication Basic
\item User Agent support
\item Any HTTP Headers can be added
\item Proxy mode to record sessions using a Web browser
\item SOAP support using the HTTP mode (the SOAPAction HTTP header is
\item HTTP server or proxy server load testing.
\subsection{WEBDAV related features}
The WebDAV (RFC 4918) plugin is a superset of the HTTP plugin. It adds the
following features (some versionning extensions to WebDAV (RFC 3253)
are also supported):
\item Methods implemented: DELETE, CONNECT, PROPFIND, PROPPATCH, COPY,
\subsection{Jabber/XMPP related features}
\item Authentication (plain-text, digest and sip-digest)
\item presence and register messages
\item Chat messages to online or offline users
\item MUC: join room, send message in room, change nickname
\item Roster set and get requests
\item Global users\verb|'| synchronization can be set on specific actions
\item raw XML messages
\item PubSub
\subsection{PostgreSQL related features}
\item Basic and MD5 Authentication
\item Basic Queries (Extended queries not yet supported)
\item Proxy mode to record sessions
\subsection{MySQL related features}
This plugin is experimental. It works only with MySQL version 4.1 and higher.
\item Secured Authentication method only (MySQL >= 4.1)
\item Basic Queries
\subsection{LDAP related features}
This plugin is experimental.
\item bind
\item add, modify and search queries
\item starttls (only with R12B-0 Erlang and up)
\subsection{Complete reports set}
Measures and statistics produced by Tsung are extremely feature-full.
They are all represented as a graphic. \program{Tsung} produces
statistics regarding:
\item \emph{Performance}: response time, connection time, decomposition of the user scenario based on request grouping instruction (called \textit{transactions}), requests per second
\item \emph{Errors}: Statistics on page return code to trace errors
\item \emph{Target server behaviour}: An Erlang agent can gather information
from the target server(s). Tsung produces graphs for CPU and memory
consumption and network traffic. SNMP and munin is also supported to
monitor remote servers.
\par Note that \program{Tsung} takes care of the synchronization process
by itself. Gathered statistics are «synchronized».
It is possible to generate graphs during the benchmark as statistics
are gathered in real-time.
\program{Tsung} has several advantages over other injection tools:
\item \emph{High performance} and \emph{distributed benchmark}: You
can use Tsung to simulate tens of thousands of virtual users.
\item \emph{Ease of use}: The hard work is already done for all supported
protocol. No need to write complex scripts. Dynamic scenarios only
requires small trivial piece of code.
\item \emph{Multi-protocol support}: \program{Tsung} is for example one of
the only tool to benchmark SOAP applications
\item \emph{Monitoring} of the target server(s) to analyze the behaviour
and find bottlenecks. For example, it has been used to analyze cluster
symmetry (is the load properly balanced ?) and to determine the best
combination of machines on the three cluster tiers (Web engine, EJB
engine and database)
This package has been tested on Linux, FreeBSD and Solaris. A port is
available on MacOS X. It should
work on Erlang supported platforms (Linux, Solaris, *BSD, Win32 and
\item Erlang/OTP R10B-0 and up
(\url{}). Erlang is now
part of fedora and debian/ubuntu repositories.
\item extended regexp module (used for dynamic variables):
gregexp.erl available at
\url{} . The module is
included in the source and binary distribution of \program{Tsung}. It
is released under the EPL License.
\item pgsql module made by Christian Sunesson (for the PostgreSQL plugin):
sources available at
\url{} . The module is
included in the source and binary distribution of \program{Tsung}. It
is released under the EPL License.
\item mysql module made by Magnus Ahltorp \& Fredrik Thulin (for the mysql plugin):
sources available at
\url{} . The modified module is
included in the source and binary distribution of \program{Tsung}. It
is released under the three-clause BSD License.
\item eldap module (for the LDAP plugin):
sources available at
\url{} . The module is
included in the source and binary distribution of \program{Tsung}. It
is released under the GPL License.
\item mochiweb libs (for xpath parsing, optionally used for dynamic variables in
the HTTP plugin):
sources available at
\url{} . The module is
included in the source and binary distribution of \program{Tsung}. It
is released under the MIT License.
\item gnuplot and perl5 (optional; for graphical output with
\command{tsung\} script). The Template Toolkit is used for HTML
reports (see \url{})
\item python and mathplotlib (optional; for graphical output with
\item for distributed tests, you need an ssh access to remote
machines without password (use a RSA/DSA key without pass-phrase or
ssh-agent) (rsh is also supported)
\item bash
make install
The default configuration file is \file{~/.tsung/tsung.xml} (
there are several sample files in
Log files are saved in \file{~/.tsung/log/} . A new sub-directory
is created for each test using the current date as name
(\file{~/.tsung/log/20040217-09:40} for ex.)
Two commands are installed in the directory \varname{\$PREFIX/bin}:
\command{tsung} and \command{tsung-recorder}. A man page is available
for both commands.
>tsung -h
Usage: tsung <options> start|stop|debug|status
-f <file> set configuration file (default is ~/.tsung/tsung.xml)
-l <logfile> set log file (default is ~/.tsung/log/tsung.log)
-i <id> set controller id (default is empty)
-r <command> set remote connector (default is ssh)
-F use long names (FQDN) for erlang nodes
-v print version information and exit
-h display this help and exit
A typical way of using tsung is to run:
\command{tsung -f myconfigfile.xml start}.
The command will print the current log directory created for the test, and wait until the test is
Use the Tsung mailing list (see
\url{}) if you have
suggestions or questions about \program{Tsung}. You can also use the
bug-tracker available at \url{}.
You can also try the \#tsung IRC channel on Freenode.
\section{Benchmark approach}
\subsection{HTTP/WebDAV benchmark approach}
\subsubsection{Benchmarking a Web server}
\item Record one or more sessions: start the recorder with:
\command{tsung-recorder start}, and then configure your browser to use Tsung
proxy recorder (the listen port is 8090). A session file will be
created. For HTTPS recording, use \userinput{http://-} instead of
\userinput{https://} in your browser.
\item Edit / organize scenario, by adding recorded sessions in the
configuration file.
\item Write small code for dynamic parts if needed and place dynamic mark-up
in the scenario.
\item Test and adjust scenario to have a nice progression of the load. This
is highly dependent of the application and of the size of the target
server(s). Calculate the normal duration of the scenario and use the
interarrival time between users and the duration of the phase to estimate
the number of simultaneous users for each given phase.
\item Launch benchmark with your first application parameters set-up:
\command{tsung start} (run \command{man tsung} for more options)
\item Wait for the end of the test or stop by hand with
\command{tsung stop} (reports can also be generated during the
test (see § \ref{sec:statistics-reports}) : the statistics are
updated every 10 seconds). For a brief summary of the current
activity, use \command{tsung status}
\item Analyze results, change parameters and relaunch another benchmark
\subsubsection{WEBDAV }
It's the same approach as HTTP: first you start to record one or more
sessions with the recorder:
\command{tsung-recorder -p webdav start}
\subsubsection{Benchmarking a proxy server}
By default, the HTTP plugin is used to benchmark HTTP servers. But you
can also benchmark HTTP Proxy servers. To do that, you must add in the
\varname{options} section:
<option type="ts_http" name="http_use_server_as_proxy" value="true"></option>
\subsection{LDAP benchmark approach}
An LDAP plugin for the recorder is not yet implemented, so you have to
write the session by yourself; see section \ref{sec:session:ldap} for
more information.
\subsection{PostgreSQL benchmark approach}
It's the same approach as HTTP: first you start to record one or more
sessions with the recorder:
\command{tsung-recorder -p pgsql start}
This will start a proxy listening to port 8090 and will proxy requests
To choose another port and/or address:
\command{tsung-recorder -L 5432 -I -P 5433 -p pgsql start}
This will start a proxy listening to port 5432 and will proxy requests
\subsection{MySQL benchmark approach}
A MySQL plugin for the recorder is not yet implemented, so you have to
write the session by yourself; see section \ref{sec:session:mysql} for
more information.
\subsection{Jabber/XMPP benchmark approach}
This paragraph explains how to write a session for Jabber/XMPP.
There are two differences between HTTP and Jabber testing:
\item There is no recorder for Jabber, so you have to write your
sessions by hand (an example is provided in
\item the jabber plugin does not parse XML; instead it uses packet
\subsubsection{Acknowledgments of messages}
Since the jabber plugin does not parse XML (historically, it was for
performance reasons), you must have a way to tell when a request is
finished. There are 3 possibilities:
\item[ack=local] as soon as a packet is received from the server, the
request is considered as completed. Hence if you use a local ack with a request
that do not require a response from the server (presence for ex.), it
will wait forever (or until a timeout is reached).
\item[ack=no\_ack] as soon as the request is send, it is considered as completed (do
not wait for incoming data)
\item[ack=global] synchronized users. its main use is for waiting for all
users to connect before sending messages. To do that, set a request
with global ack (it can be the first presence msg:
<request> <jabber type="presence" ack="global"/> </request>
You also have to specify the number of users to be connected:
<option type="ts_jabber" name="global_number" value="100"></option>
To be sure that exactly \varname{global\_number} users are started, add the
\userinput{'maxnumber'} attribute to \varname{'users'}
<users maxnumber="100" interarrival="1.0" unit="second"></users>
If you do not specify \varname{maxnumber}, the global ack will be reset every
\varname{global\_number} users
\strong{New in 1.2.2:} This version adds an new option for a
session. if you set the attribute \varname{bidi} (for bidirectional)
in the \varname{session} tag: \userinput{<session ... bidi='true'>},
then incoming messages from the server will be analyzed. Currently,
only roster subscription requests are handled: if a user received a
subscription request (\userinput{<presence ... type='subscribe'>}), it
will respond with a \userinput{<presence ... type='subscribed'>}
\subsubsection{Status: Offline, Connected and Online}
You can send messages to offline or online users. A user is considered
online when he has send a \userinput{presence:initial} message (before
this message , the state of the user is \varname{connected}).
If you want to switch back to \varname{connected} before going
\varname{offline}, you can use a \userinput{presence:final} message:
\userinput{presence:final} does two things:
\item It removes the client from the list of Online users, and moves
them into the list of Connected users.
\item It sends a broadcast presence update of type='unavailable'.
\userinput{presence:final} is optional.
\emph{warn:} this is new in \strong{1.2.0}, in earlier version, only 2
status were available: online and offline; a user was considered
online as soon as it was connected.
Below are configuration examples for the possible authentication
methods. Note: the regular expressions used here are only examples -
they may need to be altered depending on how a particular server
implementation composes messages (see also ~\ref{sec:jabber-options}
for password settings).
\item \strong{plain authentication} - sends clear-text passwords:
<session probability="100" name="jabber-plain" type="ts_jabber">
<request> <jabber type="connect" ack="local"></jabber> </request>
<thinktime value="2"></thinktime>
<transaction name="auth_plain">
<request> <jabber type="auth_get" ack="local"></jabber> </request>
<request> <jabber type="auth_set_plain" ack="local"></jabber> </request>
\item \strong{digest authentication} as described in XMPP JEP-0078: Non-SASL Authentication
<session probability="100" name="jabber-digest" type="ts_jabber">
<!-- regexp captures stream ID returned by server -->
<dyn_variable name="sid" regexp="&lt;stream:stream id=&quot;\(.*\)&quot; xmlns:stream"/>
<jabber type="connect" ack="local"></jabber>
<thinktime value="2"></thinktime>
<transaction name="auth_digest">
<request> <jabber type="auth_get" ack="local"></jabber> </request>
<request subst='true'> <jabber type="auth_set_digest" ack="local"></jabber> </request>
\item \strong{sip-digest authentication}
<session probability="100" name="jabber-sipdigest" type="ts_jabber">
<request> <jabber type="connect" ack="local"></jabber> </request>
<thinktime value="2"></thinktime>
<transaction name="auth_sipdigest">
<!-- regexp captures nonce value returned by server -->
<dyn_variable name="nonce"
regexp="&lt;Nonce encoding=&quot;hex&quot;&gt;\(.*\)&lt;\/Nonce&gt;"/>
<jabber type="auth_get" ack="local"></jabber>
<request subst='true'> <jabber type="auth_set_sip" ack="local"></jabber> </request>
\section{Using the proxy recorder}
The recorder has three plugins: for HTTP, WebDAV and for PostgreSQL.
To start it, run \command{tsung-recorder -p <PLUGIN> start}, where \varname{PLUGIN} can be
\userinput{http}, \userinput{webdav} or \userinput{pgsql} for PostgreSQL. The default plugin is \userinput{http}.
The proxy is listening to port 8090. You can change the port with
\userinput{-L portnumber}.
To stop it, use \command{tsung-recorder stop}.
The recorded session is created as
\file{~/.tsung/tsung_recorderYYYMMDD-HH:MM.xml}; if it doesn't work,
take a look at \file{~/.tsung/log/tsung.log-tsung_recorder@hostname}
During the recording, you can add custom tag in the XML file, this can
be useful to set transactions or comments:
\command{tsung-recorder record\_tag "<transaction name='login'>''}
Once a session has been created, you can insert it in your main configuration
file, either by editing by hand the file, or by using an ENTITY
declaration, like:
<!DOCTYPE tsung SYSTEM "/usr/share/tsung/tsung-1.0.dtd" [
<!ENTITY mysession1 SYSTEM "/home/nniclausse/.tsung/tsung_recorder20051217-13:11.xml">
For PostgreSQL, the proxy will connect to the server at IP
and port 5432. Use \userinput{-I serverIP} to change the IP and
\userinput{-P portnumber} to change the port.
\subsection{HTTP and WEBDAV}
For HTTPS recording, use \userinput{http://-} instead of
\userinput{https://} in your browser
\strong{New in 1.2.2}: For HTTP, you can configure the recorder to
use a parent proxy (but this will not work for https). Add the -u
option to enable parent proxy, and use \userinput{-I serverIP} to set
the IP and \userinput{-P portnumber} to set the port of the parent.
\section{Understanding tsung.xml configuration file}
The default encoding is utf-8. You can use a different encoding, like in:
<?xml version="1.0" encoding="ISO-8859-1"?>
\subsection{File structure}
Scenarios are enclosed into Tsung tags:
<?xml version="1.0"?>
<!DOCTYPE tsung SYSTEM "/usr/share/tsung/tsung-1.0.dtd" [] >
<tsung loglevel="info">
If you add the attribute \userinput{dumptraffic="true"}, all the
traffic will be logged to a file. \emph{Warn:} this will considerably
slow down Tsung, so use with care. It is useful for debugging
purpose. You can use the attribute \userinput{dumptraffic="light"} to
dump only the first 44 bytes.
The \varname{loglevel} can also have a great impact on performance:
For high load, \userinput{warning} is recommended.
Possible values are:
\item emergency
\item critical
\item error
\item warning
\item notice (default)
\item info
\item debug
For REALLY verbose logging, recompile tsung with \command{make debug}
and set \varname{loglevel} to \userinput{debug}.
\subsection{Clients and server}
Scenarios start with clients (Tsung cluster) and server definitions:
For non distributed load, you can use a basic setup like:
<client host="localhost" use_controller_vm="true"/>
<server host="" port="80" type="tcp"></server>
This will start the load on the same host and on the same Erlang
virtual machine as the controller.
The server is the entry point into the cluster (\strong{New in 1.2.0:}
if several servers are defined, a round robin algorithm is used to
choose the server).
The next example is more complex, and use several features for
advanced distributed testing:
<client host="louxor" weight="1" maxusers="800">
<ip value=""></ip>
<ip value=""></ip>
<client host="memphis" weight="3" maxusers="600" cpu="2"/>
<server host="" port="8080" type="tcp"></server>
Several virtual IP can be used to simulate more machines. This is
very useful when a load-balancer use the client\verb|'|s IP to
distribute the traffic among a cluster of servers. \strong{New in
1.1.1:} IP is no longer mandatory. If not specified, the default IP will
be used.
In this example, a second machine is used in the Tsung cluster,
with a higher weight, and 2 cpus. Two Erlang virtual machines will be
used to take advantage of the number of CPU.
\strong{Note:} Even if an Erlang VM is now able to handle severals CPUs
(erlang SMP), benchmarks shows that it's more efficient to use one VM
per CPU (with SMP disabled) for tsung clients. Only the controller node is using SMP
erlang. Therefore, you cpu should be equal to the number of cores of
your nodes.
By default, the load is distributed uniformly on all CPU (one cpu
per client by default). The weight parameter (integer) can be used to
take into account the speed of the client machine. For instance, if
one real client has a weight of 1 and the other client has a weight
of 2, the second one will start twice the number of users as the
first (the proportions will be 1/3 and 2/3). In the earlier example
where for the second client has 2 CPU and weight=3, the weight is
equal to 1.5 for each CPU.
The \varname{maxusers} parameter is used to bypass the limit of
maximum number of sockets opened by a single process (usually 1024)
and the lack of scalability of the \varname{select} system call. When
the number of users is higher than the limit, a new erlang virtual
machine will be started to handle new users. The default value of
\varname{maxusers} is 800 . Nowadays, with kernel polling enable, you
can and should use a very large value for \varname{maxusers} (30000 for example) without
performance penalty (but don't forget to raise the limit of the OS with
\command{ulimit -n}, see also FAQ \ref{sec:faq:emfile}).
Tsung is able to monitor remote servers using several backends that
communicates with remote agent; This
is configured in the \varname{<monitoring>} section. Available
statistics are: cpu activity, load average, memory usage.
Several types of remote agents are supported (\userinput{erlang} is the default) :
The remote agent is started by Tsung. It use erlang communications to
retrieve statistics of activity on the server. For example, here is a
cluster monitoring definition based on Erlang agents, for a cluster of
6 computers:
<monitor host="geronimo" type="erlang"></monitor>
<monitor host="bigfoot-1" type="erlang"></monitor>
<monitor host="bigfoot-2" type="erlang"></monitor>
<monitor host="f14-1" type="erlang"></monitor>
<monitor host="f14-2" type="erlang"></monitor>
<monitor host="db" type="erlang"></monitor>
Note: monitored computers needs to be
accessible through the network, and erlang communications must be
allowed (no firewall is better ). SSH (or rsh) needs to be configured to
allow connection without password on. \strong{You must use the same
version of Erlang/OTP on all nodes otherwise it may not work
properly !}
If you can't have erlang installed on remote servers, you can use one
of the other available agents:
The type keyword \userinput{snmp} can replace the erlang keyword, if SNMP monitoring
is preferred. They can be mixed. Since version 1.2.2, you can customize the SNMP version,
community and port number. It uses the MIB provided in
\program{net-snmp} (see also \ref{sec:faq:snmp}).
<monitor host="geronimo" type="snmp"/>
<monitor host="f14-2" type="erlang"></monitor>
<monitor host="db" type="snmp">
<snmp version="v2" community="mycommunity" port="11161"/>
The default version is \userinput{v1}, default community
\userinput{public} and default port \userinput{161}.
Since version \strong{1.3.1}, Tsung is able to retrieve data from a munin-node agent
(see \url{}). The \varname{type}
keyword must be set to \userinput{munin}, for example:
<monitor host="geronimo" type="munin"/>
<monitor host="f14-2" type="erlang"></monitor>
\subsection{Defining the load progression}
\subsubsection{Randomly generated users}
The load progression is set-up by defining several arrival phases:
<arrivalphase phase="1" duration="10" unit="minute">
<users interarrival="2" unit="second"></users>
<arrivalphase phase="2" duration="10" unit="minute">
<users interarrival="1" unit="second"></users>
<arrivalphase phase="3" duration="10" unit="minute">
<users interarrival="0.1" unit="second"></users>
With this setup, during the first 10 minutes of the test, a new user
will be created every 2 seconds, then during the next 10 minutes, a
new user will be created every second, and for the last 10 minutes,
10 users will be generated every second. The test will finish when
all users have ended their session.
The complete sequence can be executed several times using the
\varname{loop} attribute in the \varname{load} tag
(\userinput{loop='2'} means the sequence will be looped twice, so the
complete load will be executed 3 times) (feature available since
version 1.2.2).
The load generated in terms of HTTP requests / seconds will also
depend on the mean number of requests within a session (if you have a
mean value of 100 requests per session and 10 new users per seconds,
the theoretical average throughput will be 1000 requests/ sec).
\subsubsection{Statically generated users}
If you want to start a given session (see ~\ref{sec:sessions}) at a given time during the test,
it is possible since version \strong{1.3.1}:
<arrivalphase phase="1" duration="10" unit="minute">
<users interarrival="2" unit="second"></users>
<user session="http-example" start_time="185" unit="second"></user>
<user session="http-example" start_time="10" unit="minute"></user>
<user session="foo" start_time="11" unit="minute"></user>
<session name="http-example" probability="0" type="ts_http">
<request> <http url="/" method="GET"></http> </request>
<session name="foo" probability="100" type="ts_http">
<request> <http url="/" method="GET"></http> </request>
In this example, we have two sessions, one has a "0" probability (and
therefore will not be used in the first phase), and the other
100\%. We define 3 users starting respectively 3mn and 5 seconds
after the beginning of the test (using the \userinput{http-example}
session), one starting after 10 minutes, and a last one starting after
11 minutes (using the \userinput{foo} session this time)
\subsubsection{Duration of the load test}
By default, tsung will end when all started users have finished their
session. So it can be much longer than the duration of
arrivalphases. If you want to stop Tsung after a given duration
(even if phases are not finised or if some sessions are stil actives),
you can do this with the \varname{duration} attribute in \varname{load}:
<load duration="1" unit="hour">
<arrivalphase phase="1" duration="10" unit="minute">
<users interarrival="2" unit="second"></users>
Currently, the maximum value for duration is a little bit less than 50
days. \varname{unit} can be \userinput{second}, \userinput{minute} or
\subsection{Setting options}
\par Default values can be set-up globally: \varname{thinktime} between requests
in the scenario, ssl cipher algorithms, TCP/UDP buffer sizes (the default
value is 32KB). These values overrides
those set in session configuration tags if override is true.
<option name="thinktime" value="3" random="false" override="true"/>
<option name="ssl_ciphers"
<option name="tcp_snd_buffer" value="16384"></option>
<option name="tcp_rcv_buffer" value="16384"></option>
<option name="udp_snd_buffer" value="16384"></option>
<option name="udp_rcv_buffer" value="16384"></option>
A new option is available in version \strong{1.3.1}: hibernate. This
is used to reduced memory consumption of simulated users during
thinktimes. By default, hibernation will be activated for thinktimes
higher than 10sec. This value can be changed like this:
<option name="hibernate" value="5"></option>
To disable hibernation, you must set the value to \userinput{infinity}.
\subsubsection{XMPP/Jabber options}
Default values for specific protocols can be defined. Here is an
example of option values for Jabber/XMPP:
<option type="ts_jabber" name="global_number" value="5" />
<option type="ts_jabber" name="userid_max" value="100" />
<option type="ts_jabber" name="domain" value="" />
<option type="ts_jabber" name="username" value="myuser" />
<option type="ts_jabber" name="passwd" value="mypasswd" />
<option type="ts_jabber" name="muc_service" value="conference.localhost"/>
Using these values, users will be \userinput{myuserXXX} where XXX is an integer in
the interval [1:userid\_max] and passwd \userinput{mypasswdXXX}
If not set in the configuration file, the values will be set to:
\item global\_number = 10000
\item userid\_max = 100
\item domain =
\item username = tsunguser
\item passwd = sesame
You can also set the \varname{muc\_service} here (see previous example).
\subsubsection{HTTP options}
For HTTP, you can set the \varname{UserAgent} values
(\strong{available since Tsung 1.1.0}), using a probability for each
value (the sum of all probabilities must be equal to 100)
<option type="ts_http" name="user_agent">
<user_agent probability="80">
Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.8) Gecko/20050513 Galeon/1.3.21
<user_agent probability="20">
Mozilla/5.0 (Windows; U; Windows NT 5.2; fr-FR; rv:1.7.8) Gecko/20050511 Firefox/1.0.4
Sessions define the content of the scenario itself. They describe
the requests to execute.
Each session has a given probability. This is used to decide which
session a new user will execute. The sum of all session\verb|'|s
probabilities must be 100.
A transaction is just a way to have customized statistics. Say if you
want to know the response time of the login page of your website, you
just have to put all the requests of this page (HTML + embedded
pictures) within a transaction. In the example above, the transaction
called \varname{index\_request} will gives you in the
statistics/reports the mean response time to get
\userinput{index.en.html + header.gif}. Be warn that If you have a
thinktime inside the transaction, the thinktime will be part of the
response time.
You can set static or random thinktimes to separate requests. By
default, a random thinktime will be a exponential distribution with
mean equals to \varname{value}.
<thinktime value="20" random="true"></thinktime>
In this case, the thinktime will be an exponential distribution with a
mean equals to 20 seconds.
\strong{Since version 1.3.0}, you can also use a range
\userinput{[min:max]} instead of a mean for random thinktimes (the
distribution will be uniform in the interval):
<thinktime min="2" max="10" random="true"></thinktime>
This example shows several features of the HTTP protocol support in
Tsung: GET and POST request, basic authentication, transaction for
statistics definition, conditional request (IF MODIFIED SINCE), ...
<session name="http-example" probability="70" type="ts_http">
<request> <http url="/" method="GET" version="1.1">
</http> </request>
<request> <http url="/images/logo.gif"
method="GET" version="1.1"
if_modified_since="Fri, 14 Nov 2003 02:43:31 GMT">
<thinktime value="20" random="true"></thinktime>
<transaction name="index_request">
<request><http url="/index.en.html"
method="GET" version="1.1" >
</http> </request>
<request><http url="/images/header.gif"
method="GET" version="1.1">
</http> </request>
<thinktime value="60" random="true"></thinktime>
<http url="/" method="POST" version="1.1"
</http> </request>
<http url="/bla" method="GET" version="1.1"
<www_authenticate userid="Aladdin"
passwd="open sesame"/></http>
<session name="backoffice" probability="30" ...>
... </session>
\strong{New in 1.2.2:} You can add any HTTP header now, as in:
<http url="/bla" method="GET" contents="bla=blu&amp;name=glop">
<www_authenticate userid="Aladdin" passwd="open sesame"/>
<http_header name="Cache-Control" value="no-cache"/>
<http_header name="Referer" value=""/>
\strong{New in 1.3.0:} You can also read the content of a POST or PUT
request from an external file:
<http url='mypage' method='POST' contents_from_file='/tmp/myfile' />
\par Here is an example of a session definition for the Jabber/XMPP protocol:
<session probability="70" name="jabber-example" type="ts_jabber">
<request> <jabber type="connect" ack="local" /> </request>
<thinktime value="2"></thinktime>
<transaction name="authenticate">
<request> <jabber type="auth_get" ack="local"></jabber> </request>
<request> <jabber type="auth_set_plain" ack="local"></jabber> </request>
<request> <jabber type="presence:initial" ack="no_ack"/> </request>
<thinktime value="30"></thinktime>
<transaction name="online">
<request> <jabber type="chat" ack="no_ack" size="16" destination="online"/></request>
<thinktime value="30"></thinktime>
<transaction name="offline">
<request> <jabber type="chat" ack="no_ack" size="56" destination="offline"/><request>
<thinktime value="30"></thinktime>
<transaction name="close">
<request> <jabber type="close" ack="local"> </jabber></request>
What you can do with rosters using Tsung:
You can
\item Add a new contact to their roster
- The new contact is added to the \userinput{Tsung Group} group, and their name matches their JID
\item Send a \userinput{subscribe} presence notification to the new contact's JID
- This results in a \emph{pending} subscription
\item Rename a roster contact
This changes the previously added contact's name from the default JID, to \userinput{Tsung Testuser}
\item Delete the previously added contact.
Note that when you add a new contact, the contact JID is stored and used for the operations that follow. It is recommended that for each session which is configured to perform these operations, only do so once. In other words, you would NOT want to ADD more than one new contact per session. If you want to alter the rate that these roster functions are used during your test, it is best to use the session 'probability' factor to shape this.
The nice thing about this is that when you test run is complete, your roster tables should look the same as before you started the test. So, if you set it up properly, you can have pre-loaded roster entries before the test, and then use these methods to dynamically add, modify, and remove roster entries during the test as well.
Example roster modification setup:
<session probability="100" name="jabber-rostermod" type="ts_jabber">
<!-- connect, authenticate, roster 'get', etc... -->
<transaction name="rosteradd">
<jabber type="iq:roster:add" ack="no_ack" destination="online"></jabber>
<jabber type="presence:subscribe" ack="no_ack"/>
<!-- ... -->
<transaction name="rosterrename">
<request> <jabber type="iq:roster:rename" ack="no_ack"></jabber> </request>
<!-- ... -->
<transaction name="rosterdelete">
<request> <jabber type="iq:roster:remove" ack="no_ack"></jabber> </request>
<!-- remainder of session... -->
See also \ref{bidi:presence} for automatic handling of subscribing requests.
\paragraph{SASL Anonymous}
SASL Anonymous authentication example:
<session probability="100" name="sasl" type="ts_jabber">
<request> <jabber type="connect" ack="local"></jabber> </request>
<thinktime value="10"></thinktime>
<transaction name="authenticate">
<jabber type="auth_sasl_anonymous" ack="local"></jabber></request>
<jabber type="connect" ack="local"></jabber> </request>
<jabber type="auth_sasl_bind" ack="local" ></jabber></request>
<jabber type="auth_sasl_session" ack="local" ></jabber></request>
\item \varname{type} can be either \userinput{presence:broadcast} or \userinput{presence:directed}.
\item \varname{show} value must be either \userinput{away}, \userinput{chat}, \userinput{dnd}, or \userinput{xa}.
\item \varname{status} value can be any text.
For more info, see section 2.2 of RFC 3921.
If you omit the \varname{show} or \varname{status} attributes, they default to \userinput{chat} and \userinput{Available} respectively.
Example of broadcast presence (broadcast to members of your roster):
<jabber type="presence:broadcast" show="away" status="Be right back..." ack="no_ack"/>
<thinktime value="5"></thinktime>
<jabber type="presence:broadcast" show="chat" status="Available
to chat" ack="no_ack"/>
<thinktime value="5"></thinktime>
<jabber type="presence:broadcast" show="dnd" status="Don't bother me!" ack="no_ack"/>
<thinktime value="5"></thinktime>
<jabber type="presence:broadcast" show="xa" status="I may never come back..."
<thinktime value="5"></thinktime>
<request> <jabber type="presence:broadcast" ack="no_ack"/> </request>
<thinktime value="5"></thinktime>
Example of directed presence (sent to random \userinput{online} users):
<jabber type="presence:directed" show="away" status="Be right back..." ack="no_ack"/>
<thinktime value="5"></thinktime>
<jabber type="presence:directed" show="chat" status="Available to chat" ack="no_ack"/>
<thinktime value="5"></thinktime>
<jabber type="presence:directed" show="dnd" status="Don't bother me!" ack="no_ack"/>
<thinktime value="5"></thinktime>
<jabber type="presence:directed" show="xa" status="I may never come back..."
<thinktime value="5"></thinktime>
<jabber type="presence:directed" ack="no_ack"/>
<thinktime value="5"></thinktime>
Tsung supports three MUC operations:
\item Join a room (attribute \userinput{type='muc:join'})
\item Send a message to a room (attribute \userinput{type='muc:chat'})
\item Change nickname (attribute \userinput{type='muc:nick'})
\item Exit a room (attribute \userinput{type='muc:exit})
Here's an example:
<-- First, choose an random room and random nickname: -->
<setdynvars sourcetype="random_number" start="1" end="100">
<var name="room"/>
<setdynvars sourcetype="random_string" length="10">
<var name="nick1"/>
<request subst="true">
<jabber type='muc:join' ack = "local" room = "room%%_room%%" nick = "%%_nick1%%"/>
<!-- use a for loop to send several messages to the room -->
<for from="1" to="6" var="i">
<thinktime value="30"/>
<request subst="true">
<jabber type="muc:chat" ack="no_ack" size="16" room =
<!-- change nickname-->
<thinktime value="2"/>
<setdynvars sourcetype="random_string" length="10">
<var name="nick2"/>
<request subst="true">
<jabber type="muc:nick" room="room%%_room%%" nick="%%_nick2%%"
MUC support is available since version 1.3.1
Experimental support for PubSub is available in version 1.3.1
You can read the following entry: \url{}
\paragraph{raw XML}
You can send raw XML date to the server using the \varname{raw} type:
<jabber type="raw" ack="no_ack" data="&lt;stream&gt;foo&lt;/stream&gt;"></jabber>
Beware: you must encode XML characters like \userinput{<}
,\userinput{>}, \userinput{\&}, etc.
For PostgreSQL, 4 types of requests are available:
\item connect (to a given database with a given username
\item authenticate (with password or not)
\item sql
\item close
This example shows most of the features of a PostgreSQL session:
<session probability="100" name="pgsql-example" type="ts_pgsql">
<transaction name="connection">
<pgsql type="connect" database="bench" username="bench" />
<request><pgsql type="authenticate" password="sesame"/></request>
<thinktime value="12"/>
<request><pgsql type="sql">SELECT * from accounts;</pgsql></request>
<thinktime value="20"/>
<request><pgsql type="sql">SELECT * from users;</pgsql></request>
<request><pgsql type='sql'><![CDATA[SELECT n.nspname as "Schema",
c.relname as "Name",
CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'i'
THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN '%_toto_% END as "Type",
u.usename as "Owner"
FROM pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r','v','S','')
AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
AND pg_catalog.pg_table_is_visible(c.oid)
ORDER BY 1,2;]]></pgsql></request>
<request><pgsql type="close"></pgsql></request>
For MySQL, 4 types of requests are available (same as PostgreSQL):
\item connect (to a given database with a given username
\item authenticate (with password or not)
\item sql
\item close
This example shows most of the features of a MySQL session:
<session probability="100" name="mysql-example" type="ts_mysql">
<mysql type="connect" />
<mysql type="authenticate" database="test" username="test" password="test" />
<mysql type="sql">SHOW TABLES</mysql>
<mysql type="sql">SELECT * FROM mytable</mysql>
<mysql type="close" />
The recommended mechanism used to authenticate users against a LDAP
repository requires two steps to follow. Given an username and
password, we:
\item Search the user in the repository tree, using the username (so users can reside in different subtrees of the organization)
\item Try to bind as the user, with the distinguished name found in the first step and the user's password
If the bind is successful, the user is authenticated (this is the
scheme used, among others, by the LDAP authentication module for
Apache \url{})
\paragraph{LDAP Setup}
For this example we are going to use a simple repository with the following hierarchy:
\caption{LDAP Hierarchy}
the repository has users in two organizational units
\item users (with four members)
\item users2 (with tree members)
For simplicity we set the password of each user to be the same as its common name (cn).
Tsung Setup
We will use a CSV file as input, containing the user:password pairs
for our test. So we start by writing it, in this case we name the file \file{users.csv}
(the pair paul:pablo should fail to authenticate, we will note that in the Tsung report)
Then, in our Tsung scenario, we let Tsung know about this file
<option name="file_server" id="users" value="users.csv"/>
We use two dynamic variables to hold the username and password
<setdynvars sourcetype="file" fileid="users" delimiter=";" order="iter">
<var name="username" />
<var name="password" />
To start the authentication process we instruct Tsung to perform a search, to find the distinguished name of the user we are trying to authenticate
<ldap type="search" base="dc=pablo-desktop" filter="(cn=%%_username%%)"
result_var="search_result" scope="wholeSubtree"></ldap>
As we need to access the search result, we specify it using the \varname{result\_var} attribute. This attribute tells Tsung in which dynamic variable we want to store the result (if the \varname{result\_var} attribute isn't set, Tsung doesn't store the search result in any place).
Finally, we try to bind as that user.
<request subst="true">
<ldap type="bind" user="%%ldap_auth:user_dn%%"
The only thing that remains to do is to implement the \varname{ldap\_auth:user\_dn} function, that extract the distinguished name from the search result.
user_dn({_Pid,DynVars}) ->
[SearchResultEntry] = proplists:get_value(search_result,DynVars),
{_,DN,_} = SearchResultEntry,
We aren't covering errors here. supposing that there is always one (and only one) user found, that we extract from the \varname{search\_result} variable (as defined in the previous search operation).
Each entry in the result set is a SearchResultEntry record. The record definition can be found in \file{<TSUNG_DIR>/include/ELDAPv3.hrl}.
As we only need to access the distinguished name of the object, we index into the result tuple directly. But if you need to access other attributes you probably will want to include the appropriate .hrl and use the record syntax instead. One of the eight user:password pairs in our users file was wrong, so we expect 1/8 of the authentication attempts to fail.
Indeed, after running the scenario we can confirm this in the Tsung
report (see figure \ref{fig:ldap:results}). The bind operation maintains two
counters: \varname{ldap\_bind\_ok} and \varname{ldap\_bind\_error},
that counts successful and unsuccessful bind attempts.
\caption{LDAP Results}
\paragraph{Other examples}
<session probability="100" name="ldap-example" type="ts_ldap">
<ldap type="bind" user="uid=foo" password="bar"/>
<ldap type="search" base="dc=pablo-desktop" filter="(cn=user2)"
<!-- Add. Adds a new entry to the directory* -->
<request subst="true">
<ldap type="add" dn="%%_new_user_dn%%" >
<attr type="objectClass">
<attr type="cn"><value>%%_new_user_cn%%</value></attr>
<attr type="sn"><value>fffs</value></attr>
<!-- Modify. Modifies an existing entry; type=add|delete|modify-->
<request subst="false">
<ldap type="modify" dn="cn=u119843,dc=pablo-desktop" >
<modification type="replace">
<attr type="sn"><value>SomeSN</value></attr>
<attr type="mail"><value></value></attr>
\subsection{Advanced features}
\subsubsection{Dynamic substitutions}
Dynamic substitution are mark-up placed in element of the scenario.
For HTTP, this mark-up can be placed in basic authentication (www\_authenticate
tag: userid and passwd attributes), URL (to change GET parameter)
and POST content.
Those mark-up are of the form \userinput{\%\%Module:Function\%\%}.
Substitutions are executed on a request-by-request basis, only if the
request tag has the attribute \userinput{subst="true"}.
When a substitution is requested, the substitution mark-up is replaced by
the result of the call to the Erlang function:
\userinput{Module:Function(\{Pid, DynData\})} where Pid is the Erlang process
id of the current virtual user and DynData the list of all Dynamic
variables (\strong{Warn: before version 1.1.0, the argument was just the
Pid !}).
Here is an example of use of substitution in a Tsung scenario:
<session name="rec20040316-08:47" probability="100" type="ts_http">
<request subst="true">
<http url="/echo?symbol=%%symbol:new%%" method="GET">
Here is the Erlang code of the module used for dynamic substitution:
new({Pid, DynData}) ->
case random:uniform(3) of
1 -> "IBM";
2 -> "MSFT";
3 -> "RHAT"
(use \command{erlc} to compiled the code, and put the resulting .beam
file in \file{\$PREFIX/lib/erlang/lib/tsung-X.X.X/ebin/} on all client
As you can see, writing scenario with dynamic substitution is
simple. It can be even simpler using dynamic variables (see later).
If you want to set unique id, you can use the built-in function
<session name="rec20040316-08:47" probability="100" type="ts_http">
<request subst="true">
<http url="/echo?id=%%ts_user_server:get_unique_id%%" method="GET">
\subsubsection{Reading external file}
\strong{New in 1.0.3}: A new module \varname{ts\_file\_server} is available. You
can use it to read external files. For example, if you need to read user
names and passwd from a CSV file, you can do it with it (currently,
you can read only a single file).
\strong{Note:} Reading usernames and password from a CSV file is not possible for Jabber.
You have to add this in the XML configuration file:
<option name="file_server" value="/tmp/userlist.csv"></option>
\strong{New in 1.2.2}: You can read several files, using the \varname{id}
attribute to identify each file:
<option name="file_server" value="/tmp/userlist.csv"></option>
<option name="file_server" id='random' value="/tmp/randomnumbers.csv"></option>
Now you can build you own function to use it, for example, create a
file called \file{readcsv.erl}:
{ok,Line} = ts_file_server:get_next_line(),
[Username, Passwd] = string:tokens(Line,";"),
"username=" ++ Username ++"&password=" ++ Passwd.
The output of the function will be a string \userinput{username=USER\&password=PASSWORD}
Then compile it with \command{erlc readcsv.erl} and put
\file{readcsv.beam} in
\file{\$prefix/lib/erlang/lib/tsung-VERSION/ebin} directory (if the
file has an id set to \userinput{random}, change the call to: \\
Then use something like this in your session:
<request subst="true">
<http url='/login.cgi' version='1.0' contents='%%readcsv:user%%&amp;op=login'
content_type='application/x-www-form-urlencoded' method='POST'>
Two functions are available: \varname{ts\_file\_server:get\_next\_line}
and \varname{ts\_file\_server:get\_random\_line}. For the
\varname{get\_next\_line} function, when the end of file is reached, the
first line of the file will be the next line.
\strong{New in 1.3.0}: you no longer have to create an external
function to parse a simple csv file: you can use \varname{setdynvars}
(see next section for detailed documentation):
<setdynvars sourcetype="file" fileid="userlist.csv" delimiter=";" order="iter">
<var name="username" />
<var name="user_password" />
This defines two dynamic variables \varname{username} and
\varname{user\_password} filled with the next entry from the csv
file. Using the previous example, the request is now:
<request subst="true">
<http url='/login.cgi' version='1.0'
content_type='application/x-www-form-urlencoded' method='POST'>
Much simpler than the old method !
\subsubsection{Dynamic variables}
In some cases, you may want to use a value given by the server in a
response later in the session, and this value is \strong{dynamically
generated} by the server for each user. For this, you can use
\userinput{<dyn\_variable>} in the scenario
Let's take an example with HTTP. You can easily grab a value in a HTML
form like:
<form action="go.cgi" method="POST">
<hidden name="random_num" value="42"></form>
<dyn_variable name="random_num" ></dyn_variable>
<http url="/testtsung.html" method="GET" version="1.0"></http>
Now \varname{random\_num} will be set to 42 during the user's session. It's
value will be replace in all mark-up of the form
\userinput{\%\%\_random\_num\%\%} if and only if the \varname{request} tag has the
attribute \userinput{subst="true"}, like:
<request subst="true">
<http url='/go.cgi' version='1.0'
content_type='application/x-www-form-urlencoded' method='POST'>
If the dynamic value is not a form variable, you can set a regexp by
hand, for example to get the title of a HTML page:
<dyn_variable name="mytitlevar"
<http url="/testtsung.html" method="GET" version="1.0"></http>
\strong{Since version 1.3.0}, more powerful dynamic variables are implemented:
You can set dynamic variables not only while parsing server data, but
you can build them using external files or generate them with a function
or generate random numbers/strings:
Six types of dynamic variables are currently implemented (\varname{sourcetype} tag):
\item Dynamic variables defined by calling an erlang function:
<setdynvars sourcetype="erlang" callback="ts_user_server:get_unique_id">
<var name="id1" />
\item Dynamic variables defined by parsing an external file:
<setdynvars sourcetype="file" fileid="userdb" delimiter=";" order="iter">
<var name="user" />
<var name="user_password" />
\varname{delimiter} can be any string, and \varname{order} can be
\userinput{iter} or \userinput{random}
\item A dynamic variable can be a random number (uniform distribution)
<setdynvars sourcetype="random_number" start="3" end="32">
<var name="rndint" />
\item A dynamic variable can be a random string
<setdynvars sourcetype="random_string" length="13">
<var name="rndstring1" />
\item A dynamic variable can be a urandom string: this is much faster than
the random string, but the string is not really random: the same set
of characters is always used.
\item A dynamic variable can be generated by dynamic evaluation of
erlang code:
<setdynvars sourcetype="eval"
ts_digest:md5hex(Val) end.">
<var name="md5sum" />
A \varname{setdynvars} can be defined anywhere in a session.
\subsubsection{Checking the server's response}
With the tag \varname{match} in a \varname{request} tag, you can check
the server's response against a given string, and do some actions
depending on the result. In any case, if it matches, this will
increment the \varname{match} counter, if it does not match, the
\varname{nomatch} counter will be incremented.
For example, let's say you want to test a login page. If the login is
ok, the server will respond with \computeroutput{Welcome !} in the
HTML body, otherwise not. To check that:
<match do="continue" when="match">Welcome !</match>
<http url='/login.php' version='1.0' method='POST'
content_type='application/x-www-form-urlencoded' >
You can use a regexp instead of a simple string.
The list of available actions to do is:
\item continue: do nothing, continue (only update match or nomatch counters)
\item log: log the request id, userid, sessionid in a file (in \file{match.log})
\item abort : abort the session
\item restart: restart the session. The maximum number of
restarts is 3 by default.
\item loop: repeat the request, after 5 seconds. The maximum number of
loops is 20 by default.
You can mixed several match tag in a single request:
<match do="loop" sleep_loop="5" max_loop="10" when="match">Retry</match>
<match do="abort" when="match">Error</match>
<http url='/index.php' method=GET'>
You can also do the action on "nomatch" instead of "match".
If you want to skip the HTTP headers, and match only on the body, you
can use \userinput{skip\_headers='http'}. Also, you can apply a
function to the content before matching; for example the following
example use both features to compute the md5sum on the body of a HTTP
response, and compares it to a given value:
<match do='log' when='nomatch' skip_headers='http'
<http url="/" method="GET" version="1.1"/>
A new way to analyze the server response has been introduced in the
release \strong{1.3.0}. It is only for the HTTP plugin since it is
based on XML/HTML parsing. This feature uses the mochiweb library
and \strong{only works with erlang R12B and newer version}.
This give us some benefices:
\item XPath is simple to write and to read, and match very well with
HTML pages
\item The parser works on binaries(), and doesn't create any
string(). In contrast, the regexp module works on string(), so the
entire page needs to be \varname{binary\_to\_list/1} transformed before
processing it.
\item The cost of parsing the HTML and build the tree is amortized
between all the dyn\_variables defined for a given request
To utilize xpath expression, use a \varname{xpath} attribute when
defining the dyn\_variable, instead of \varname{regexp}, like:
<dyn_variable name="field1_value" xpath="//input[@name='field1']/@value"/>
<dyn_variable name="title" xpath="/html/head/title/text()"/>
There is a bug in the xpath engine, result nodes from "descendant-or-self" aren't returned in document order. This isn't a problem for the most common cases.
However, queries like \userinput{//img[1]/@src} are not recommended, as the order of the \userinput{<img>} elements returned from //img is not the expected.
The order is respected for paths without "descendant-or-self" axis, so this: \userinput{/html/body/div[2]/img[3]/@src} is interpreted as expected and can be safely used.
Basic tests shows a x4 improvement in speed over the \emph{regexp} implementation.
\subsubsection{Loops, If}
\strong{Since 1.3.0}, it's now possible to add conditional/unconditional loops in a session:
Repeat the enclosing actions a fixed number of times. A dynamic
variable is used as counter, so the current iteration could be used in
requests. List of attributes:
\item[from] Initial Value
\item[to] Last value
\item[incr] Amount to increment in each iteration
\item[var] Name of the variable to hold the counter
<for from="1" to="10" incr="1" var="counter">
<request> <http url="/page?id=%%_counter%%"></http> </request>
Repeat the enclosing action (while|until) some condition. This is
intended to be used together with \userinput{dyn\_variable} declarations. List of
\item[name] Name of the repeat
\item[max\_repeat] Max number of loops
The last element of repeat must be either \userinput{<while>} or \userinput{<until>} example:
<repeat name="myloop" max_repeat="40">
<dyn_variable name="result" regexp="Result: (.*)"/>
<http url="/random" method="GET" version="1.1"></http>
<until var="result" eq="5"/> </repeat>
\strong{Since 1.3.1}, it's also possible to add if statements based on
dynamic variables:
<if var="tsung_userid" eq="3">
<request> <http url="/foo"/> </request>
<request> <http url="/bar"/> </request>
You can use \varname{eq} or \varname{neq} to check the variable.
\section{Statistics and reports}
\subsection{Available stats}
\item \varname{request} Response time for each request.
\item \varname{page} Response time for each set of requests (a page is a group
of request not separated by a thinktime).
\item \varname{connect} Duration of the connection establishment.
\item \varname{reconnect} number of reconnection.
\item \varname{size\_rcv} Size of responses in bytes.
\item \varname{size\_sent} Size of requests in bytes.
\item \varname{session} Duration of a user's session.
\item \varname{users} Number of simultaneous users.
\item \varname{connected} Number of simultaneous connected users. \strong{new
in 1.2.2}.
\item custom transactions
The mean response time (for requests, page, etc.) is computed every 10
sec (and reset). That's why you have the highest mean and lowest mean
values in the Stats report. \strong{Since version 1.3.0}, the mean for
the whole test is also computed.
HTTP specific stats:
\item counter for each response status (200, 404, etc.)
Jabber specific stats:
\item \varname{request\_noack} Counter of \varname{no\_ack} requests. Since
response time is meaningless with \varname{no\_ack}
requests, we keep a separate stats for this. \strong{new in 1.2.2}.
\item \varname{bidi\_msg\_skip} Only if bidi is true for a
session. counter the number of messages received from the server
without doing anything. \strong{new in 1.2.2}.
\item \varname{bidi\_msg\_sent} Only if bidi is true for a
session. Count the number of messages sent to the server in response
of a message received from from the server. \strong{new in 1.2.2}.
A bit of explanation on the design and internals of the statistics engine:
Tsung was designed to handle thousands of requests/sec, for very
long period of times (several hours) so it do not write all data to
the disk (for performance reasons). Instead it computes on the fly an
estimation of the mean and standard variation for each type of data,
and writes these estimations every 10 seconds to the disk (and then
starts a new estimation for the next 10 sec). These computations are
done for two kinds of data:
\item \varname{sample}, for things like response time
\item \varname{sample\_counter} when the input is a cumulative one (number of
packet sent for ex.).
There are also two other types of useful data (no averaging is done for
those) :
\item \varname{counter}: a simple counter, for HTTP status code for ex.
\item \varname{sum} for ex. the cumulative HTTP response's size (it gives an
estimated bandwidth usage).
\subsection{Generating the report}
cd to the log directory of your test (say
\file{~/.tsung/log/20040325-16:33/}) and use the script
\strong{You can generate the statistics even when the test is running !}
use \userinput{--help} to view all available options:
Available options:
[--help] (this help text)
[--noplot] (don't make graphics)
[--gnuplot <command>] (path to the gnuplot binary)
[--nohtml] (don't create HTML reports)
[--logy] (logarithmic scale for Y axis)
[--tdir <template_dir>] (Path to the HTML tsung templates)
[--noextra (don't generate graphics from extra data (os monitor, etc)
[--stats <file>] (stats file to analyse, default=tsung.log)
\subsection{Tsung summary}
Figure \ref{fig:report} shows an example of a summary report.
\subsection{Graphical overview}
Figure \ref{fig:graph} shows an example of a graphical report.
\caption{Graphical output}
\subsection{Tsung Plotter}
Tsung-Plotter (\command {tsplot} command) is an optional tool recently
added in the Tsung distribution (it is written in Python), useful to
compare different tests runned by Tsung. \command{tsplot} is able to
plot data from several \file{tsung.log} files onto the same charts,
for further comparisons and analyzes. You can easily customize the
plots you want to generate by editing simple configuration files. You
can get more information in the manual page of the tool (\command{man
Example of use:
tsplot "First test" firsttest/tsung.log "Second test" secondtest/tsung.log -d outputdir
Here's an example of the charts generated by tsplot (figure \ref{fig:graph:tsplot}):
\caption{Graphical output of tsplot}
A contributed perl script \command{} is able to create rrd
files from the tsung log files. It's available in \file{/usr/lib/tsung/bin/}
\item \program{Tsung} home page: \url{}
\item \program{Tsung} description (French)\footnote{\url{}}
\item Erlang web site \url{}
\item Erlang programmation, Mickaël Rémond, Editions Eyrolles, 2003
\item \emph{Making reliable system in presence of software errors}, Doctoral Thesis,
Joe Armstrong, Stockholm, 2003 \footnote{\url{}}
\item \emph{Tutorial on How to write a Tsung plugin}, written by t ty, \url{}
The first version of this document was based on a talk given by Mickael
Rémond\footnote{\email{}} during an Object
Web benchmarking workshop in April 2004 (more info at
\section{Frequently Asked Questions}
\subsection{Can't start distributed clients: timeout error }
Most of the time, when a crash happened at startup without any traffic
generated, the problem arise because the main Erlang controller node cannot
create a "slave" Erlang virtual machine. The message looks like:
Can't start newbeam on host 'XXXXX (reason: timeout) ! Aborting!
The problem is that the Erlang slave module cannot start a remote slave
You can test this using this simple command on the controller node
(remotehost is the name of the client node)
>erl -rsh ssh -sname foo -setcookie mycookie
Eshell V5.4.3 (abort with ^G)
(foo@myhostname)1>slave:start(remotehost,bar,"-setcookie mycookie").
You should see this: \computeroutput{\{ok,bar@remotehost\}}
If you got \computeroutput{\{error,timeout\}}, it can be caused by
several problems:
\item ssh in not working (you must have a key without passphrase, or
use an agent)
\item Tsung and Erlang are not installed on all clients nodes
\item Erlang version or location (install path) is not the same on all clients nodes
\item A firewall is dropping erlang packets: indeed erlang virtual machines use
several TCP ports (dynamically generated) to communicate.
\item SELinux: You should disable SELinux on all clients.
\item Bad \file{/etc/hosts:}
This one is wrong (real hostname should not refer to localhost/loopback):
\begin{Verbatim} localhost myhostname
This one is good:
\begin{Verbatim} localhost myhostname
\item sshd configuration:
For example, for SuSE 9.2 sshd is compiled with restricted set of
paths (\ie{} when you shell into the account you get the users shell,
when you execute a command via ssh you don't) and this makes it
impossible to start an erlang node (if erlang is installed in
\file{/usr/local} for example).
ssh myhostname erl
If the erlang shell doesn't start then check what paths sshd was compiled with
(in SuSE see \file{/etc/ssh/sshd_config}) and symlink from one of the approved paths
to the erlang executable (thanks to Gordon Guthrie for reporting this).
Note that you do not need to use the address in the configuration file.
It will not work if you use it as the injection interface. The shortname
of your client machine should not refer to this address.
\emph{Warn:}Tsung launches a new erlang virtual machine to do the actual injection
even when you have only one machine in the injection cluster (unless
\varname{'use\_controller\_vm'} is set to true). This is because it
needs to by-pass some limit with the number of open socket from a
single process (1024 most of the time). The idea is to have several
system processes (Erl beam) that can handle only a small part of the
network connection from the given computer. When the
\varname{maxusers} limit (simultaneous) is reach, a new Erlang beam
is launched and the newest connection can be handled by the new beam).
\strong{New in 1.1.0}: If you don't use the distributed feature of
Tsung and have trouble to start a remote beam on a local machine,
you can set the \varname{'use\_controller\_vm'} attribute to true, for ex.:
<client host="mymachine" use_controller_vm="true">
\subsection{Tsung crashes when I start it }
Does your Erlang system has ssl support enabled ?
to test it:
> erl
Eshell V5.2 (abort with ^G)
1> ssl:start().
you should see 'ok'
\subsection{Why do i have error\_connect\_emfile errors ?}
emfile error means : \emph{too many open files}
This happens usually when you set a high value for \varname{maxusers}
(\varname{in the <client>} section) (the default value is 800).
The errors means that you are running out of file descriptors; you
must check that \varname{maxusers} is less than the maximum number of
file descriptors per process in your system (see \command{ulimit -n})
You can either raise the limit of your operating system ( see
\file{/etc/security/limits.conf} for Linux ) or decrease \varname{maxusers}
(Tsung will have to start several virtual machine on the same host to
bypass the maxusers limit).
\subsection{Tsung still crashes/fails when I start it !}
First look at the log file
\file{~/.tsung/log/XXX/tsung_controller@yourhostname'} to see
if there is a problem.
If the file is not created and a crashed dump file is present, maybe
you are using a binary installation of Tsung not compatible with the
version of erlang you used.
If you see nothing wrong, you can compile \program{Tsung} with full
debugging: recompile with \command{make debug} , and
don't forget to set the loglevel to "debug" in the XML file.
To start the debugger or see what happen, start \program{tsung} with the
\userinput{debug} argument instead of \userinput{start}. You will have
an erlang shell on the \varname{tsung\_controller} node. Use
\command{toolbar:start().} to launch the graphical tools provided by
\subsection{Can I dynamically follow redirect with HTTP ?}
If your HTTP server sends 30X responses (redirect) with dynamic URLs,
you can handle this situation using a dynamic variable:
<dyn_variable name="redirect" regexp="Location: \(http://.*\)\r"/>
<http url="index.html" method="GET" ></http>
<request subst="true">
<http url="%%_redirect%%" method="GET"></http>
You can even handle the case where the server use several redirections
successively using a repeat loop (this works only with version 1.3.0 and up):
<dyn_variable name="redirect" regexp="Location: \(http://.*\)\r"/>
<http url='/test/redirect.html' method='GET'></http>
<repeat name="redirect_loop" max_repeat="5">
<request subst="true">
<dyn_variable name="redirect" regexp="Location: \(http://.*\)\r"/>
<http url="%%_redirect%%" method="GET"></http>
<until var="redirect" eq=""/>
\subsection{What is the format of the stats file tsung.log ?}
# stats: dump at 1218093520
stats: users 247 247
stats: connected 184 247
stats: users_count 184 247
stats: page 187 98.324 579.441 5465.940 2.177 9.237 595 58
stats: request 1869 0.371 0.422 5.20703125 0.115 0.431 7444062 581
stats: connect 186 0.427 0.184 4.47216796875 0.174 0.894 88665254 59
stats: tr_login 187 100.848 579.742 5470.223 2.231 56.970 91567888 58
stats: size_rcv 2715777 3568647
stats: 200 1869 2450
stats: size_sent 264167 347870
# stats: dump at 1218093530
stats: users 356 356
stats: users_count 109 356
stats: connected -32 215
stats: page 110 3.346 0.408 5465.940 2.177 77.234 724492 245
stats: request 1100 0.305 0.284 5.207 0.115 0.385 26785716 2450
stats: connect 110 0.320 0.065 4.472 0.174 0.540 39158164 245
stats: tr_login 110 3.419 0.414 5470.223 2.231 90.461 548628831 245
stats: size_rcv 1602039 5170686
stats: 200 1100 3550
stats: size_sent 150660 498530
the format is, for \varname{request}, \varname{page},
\varname{session} and transactions (\varname{tr\_XXX}:
\texttt{ \# stats:'name' 10sec\_count, 10sec\_mean, 10sec\_stdvar,
max, min, mean, count}
or for HTTP returns code, size ...
\texttt{ \# stats:'name' count(during the last 10sec), totalcount(since the beginning)}
\subsection{How can I compute percentile/quartiles/median for transactions or requests
response time ?}
It's not directly possible. But since \strong{version 1.3.0}, you can
use a new experimental statistic backend: set \userinput{backend="fullstats"}
This will print every statistics data in a raw format in a file named
\file{tsung-fullstats.log}. \strong{Warning}: this may impact the performance of
the controller node (a lot of data has to be written to disk).
The data looks like:
You will have to write your own script to analyze the output.
The format of the file may change in a future release.
\subsection{How can I specify the number of concurrent users ?}
You can't. But it's on purpose: the load generated by
\program{Tsung} is dependent on the arrival time between new
clients. Indeed, once a client has finished his session in
\program{tsung}, it stops. So the number of concurrent users is
a function of the arrival rate and the mean session duration.
For example, if your web site has $1000$ visits/hour, the arrival rate
is $1000/3600 = 0.2778$ visits/second. If you want to simulate the same
load, set the inter-arrival time is to $1/0.27778 = 3.6 sec$ (\texttt{<users
interarrival="3.6" unit="second">} in the \varname{arrivalphase} node in the
XML config file).
\subsection{SNMP monitoring doesn't work ?!}
It use SNMP v1 and the 'public' community. It has been tested with
You can try with \command{snmpwalk} to see if your snmpd config is ok:
>snmpwalk -v 1 -c public IP-OF-YOUR-SERVER .
UCD-SNMP-MIB::memTotalReal.0 = INTEGER: 1033436
SNMP doesn't work with erlang R10B and Tsung older than 1.2.0.
There is a small bug in the \file{snmp_mgr} module in old Erlang
release (R9C-0). You have to apply this patch to make it
work. This is fixed in erlang R9C-1 and up.
--- lib/snmp-3.4/src/snmp_mgr.erl.orig 2004-03-22 15:21:59.000000000 +0100
+++ lib/snmp-3.4/src/snmp_mgr.erl 2004-03-22 15:23:46.000000000 +0100
@@ -296,6 +296,10 @@
is_options_ok([{recbuf,Sz}|Opts]) when 0 < Sz, Sz =< 65535 ->
+is_options_ok([{receive_type, msg}|Opts]) ->
+ is_options_ok(Opts);
+is_options_ok([{receive_type, pdu}|Opts]) ->
+ is_options_ok(Opts);
is_options_ok([InvOpt|_]) ->
is_options_ok([]) -> true.
\section{Errors list}
\item[error\_closed] Only for non persistent session (XMPP); the
server unexpectedly closed the connection; the session is aborted.
\item[error\_inet\_<ERRORNAME>] Network error; see
\url{} for the list of all errors.
\item[error\_unknown\_data] Data received from the server during a
thinktime (not for unparsed protocol like XMPP). The session is
\item[error\_unknown\_msg] Unknown message received (see the log
files for more information). The session is aborted.
\item[error\_unknown] Abnormal termination of a session, see
log file for more information.
\item[error\_repeat\_<REPEATNAME>] Error in a repeat loop (undefined
dynamic variable usually).
\item[error\_send\_<ERRORNAME>] Error while sending data to the
server, see \url{} for the list of all errors.
\item[error\_send] Unexpected error while sending data to the server,
see the logfiles for more information.
\item[error\_connect\_<ERRORNAME>] Error while establishing a
connection to the server. See \url{} for the list of all errors.
\item[error\_no\_online jabber] XMPP: No online user available (usually for a
chat message destinated to a online user)
\item[error\_no\_offline jabber] XMPP: No offline user available (usually for a
chat message destinated to a offline user)
\item[error\_no\_free\_userid] For XMPP: all users Id are already used
(\varname{userid\_max} is too low ?)
\item[error\_next\_session] A clients fails to gets its session
parameter from the config\_server; the controller may be overloaded ?
\item[error\_mysql\_<ERRNO>] Error reported by the mysql server (see \url{})
\item[error\_mysql\_badpacket] Bad packet received for mysql server while parsing data.
\item[error\_pgsql] Error reported by the postgresql server.
%%% for AucTex/Emacs :
%%% Local Variables:
%%% eval:(setenv "TEXINPUTS" ":.:~/cvs/projetdoc//common/styles:./images:./figures:")
%%% mode: latex
%%% End:
Something went wrong with that request. Please try again.