Monday, November 2, 2009

Tsung - Load Testing Tool in Erlang

You've must have heard of Tsung - a load testing tool. Well i've got the opportunity to learn and use it in my current employment and its really versatile in conducting load tests for web-based applications. It has many features that i've come to appreciate after spending a couple of years with Mercury Interactive (its defunct now after being bought over by HP in 2006); admittedly Mercury's solutions were more versatile than what Tsung can offer but considering that web is the common platform where many applications are being hosted on; i think its a safe bet :)

So, IMO i think
  1. Tsung is good for conducting load testing scenarios and executing them in a local / distributed manner
  2. Load Testing can be relatively light weight (Erlang processes are hitting the target app) and hence the cost of using relatively heavy weight machines is likely to reduce since there is lesser need to use those machines since more concurrent users can be simulated on 1 machine in Tsung. (I'll see whether this assumption is correct)
Good starting points (URLs of interest):
A load testing tool would be useless if it didn't know how to capture server response data (e.g. checking whether an expected string is returned) and reuse it (e.g. session ids returned from servers which you can use subsequently in a HTTP POST/GET in an URL-rewrite type of string) subsequently, generate dynamic data, read data from an external file (commonly used in storing <username, password> pairs)
So my example would illustrate the logging in to a website (e.g. and logging out from it - simple enough to illustrate my point.
Next what i did was to record the series of events that mimick my user logging and logging out of the website and this is captured in the tsung_recorder_timestamp.xml
and i used that XML file and included some other stuff so that it looks like what i have for you below (this is a basic load test scenario)
<?xml version="1.0"?>
<!DOCTYPE tsung SYSTEM "/usr/local/share/tsung/tsung-1.0.dtd">

<tsung loglevel="info" dumptraffic="false" version="1.0">

<client host="localhost" use_controller_vm="true"/>
<server host="" port="80" type="tcp"></server>

<arrivalphase phase="1" duration="2" unit="minute">
<users interarrival="1" unit="minute"></users>
<user session="rec20091102-01:30" start_time="0" unit="second"></user>

<option name="file_server" value="/tmp/userlist.csv"></option>

<session name='rec20091102-01:30' probability='100'  type='ts_http'>
<request><http url='' version='1.1' method='GET'></http></request>
<request><http url='/style_main.css' version='1.1' if_modified_since='Sat, 29 Nov 2008 20:04:00 GMT' method='GET'></http></request>
<request><http url='/images/logo.jpg' version='1.1' if_modified_since='Thu, 28 Dec 2006 14:12:43 GMT' method='GET'></http></request>
<request><http url='/images/icon_register.png' version='1.1' if_modified_since='Fri, 31 Dec 2004 12:48:16 GMT' method='GET'></http></request>
<request><http url='/images/icon_about.png' version='1.1' if_modified_since='Fri, 31 Dec 2004 12:48:28 GMT' method='GET'></http></request>
<request><http url='/images/icon_problems.png' version='1.1' if_modified_since='Fri, 31 Dec 2004 12:48:26 GMT' method='GET'></http></request>
<request><http url='/images/icon_login.png' version='1.1' if_modified_since='Fri, 31 Dec 2004 12:48:32 GMT' method='GET'></http></request>
<request><http url='' version='1.1' if_modified_since='Thu, 10 Apr 2008 19:35:02 GMT' method='GET'></http></request>
<request><http url='/images/corner_tl.gif' version='1.1' if_modified_since='Thu, 10 Apr 2008 19:34:41 GMT' method='GET'></http></request>
<request><http url='/images/corner_br.gif' version='1.1' if_modified_since='Thu, 10 Apr 2008 19:35:10 GMT' method='GET'></http></request>
<request><http url='/images/corner_bl.gif' version='1.1' if_modified_since='Thu, 10 Apr 2008 19:34:55 GMT' method='GET'></http></request>
<request><http url='/images/euler_main.jpg' version='1.1' if_modified_since='Mon, 21 Jan 2002 19:18:20 GMT' method='GET'></http></request>

<thinktime random='true' value='2'/>

<request><http url='' version='1.1' method='GET'></http></request>

<thinktime random='true' value='6'/>

<request subst="true">
<match do="continue" when="match">Logged in as %%readcsv:getUsername%%</match>
<http url='/index.php' version='1.1'  contents='%%readcsv:getUserString%%' content_type='application/x-www-form-urlencoded' method='POST'></http>

<thinktime random='true' value='4'/>

<request><http url='/images/icon_tick.png' version='1.1' method='GET'></http></request>

<thinktime random='true' value='4'/>

<thinktime random='true' value='3'/>

<request><http url='' version='1.1' method='GET'></http></request>


Hence, the main thing you should note is the use of dynamic substitution (e.g. %%readcsv:getUsername%%) where i wrote a simple erlang program to read my username and password from a file (see the XML tag option above) and replacing each simulated user with a valid user id and password.
Next, i checked that the server response contains a string Logged in as XXX where XXX would be dynamically generated by the function (Check out the erlang code for the function, simple stuff).
The erlang program is shown below.
1 -module(readcsv).
2 -export([getUserString/1, getUsername/1]).
4 getUserString({Pid, DynVar}) ->
5  {ok, Line} = ts_file_server:get_next_line(),
6  [Uid,Pwd] = string:tokens(Line, ","),
7  "username=" ++ Uid ++ "&password=" ++ Pwd ++ "&login=Login".
9 getUsername({Pid, DynVar}) ->
10  {ok, Line} = ts_file_server:get_next_line(),
11  [Uid,_] = string:tokens(Line, ","),
12  Uid.
The above erlang code must be compiled via erlc and you place the .beam file into the directory via a command like this
sudo mv readcsv.beam /usr/local/lib/erlang/lib/tsung-1.3.1/ebin/

Now, running the load test should be alright.
Note: In this load test, i defined a duration of 2 minutes with 2 users since load testing using 800 gazillion users is considered a chargeable offense so DON'T DO IT.

Have fun!