Building a Load Test with Tsung for a Login and Post Session with dynamic url-encoded variables

This blogpost covers a step-by-step instruction on how to build a loadtest for a Ruby on Rails app with a login and post session and dynamic variables with Tsung, a distributed load testing tool.

Writing a load test with Tsung for a Ruby on Rails app for the first time was a little bit confusing. There are some tutorials, but I could not find a tutorial on testing a login session or HTTP post with an authenticity token.

First, I will briefly describe why we chose Tsung, and then I will provide a step-by-step instruction for how to implement a Tsung load test for a login and post session. Don’t we all love XML? ;)

Why Tsung?

Tsung is “an open-source multi-protocol distributed load testing tool which can simulate users in order to test the scalability and performance of IP based client/server applications”.

It is written in Erlang - but don’t be afraid, I don’t have a clue about Erlang and still got it working - thanks to my colleagues who I bombarded with questions…:)

We used Tsung because:

  • it works for remote and distributed load tests;
  • it has a high performance;
  • it works for complete and complex user sessions with dynamic data;
  • it uses arrival rates and random thinktimes;
  • it has a live web interface for status and reports (This interface would be even greater with better explanations for the graphs.)

Using Tsung for a Login and Post Session

Installation for Mac OS X

Please see the Tsung Docs if you are not using Mac. To install Tsung, use Homebrew and this prompt: brew install tsung.

For graphical output, you need to install Template Toolkit which is used for HTML reports. This step is optional. If you have CPAN, use cpan: sudo cpan Template. If not, install from the source code.

Configuration

The configuration files your are creating should be located in yourPath/.tsung. Log files and recorded sessions are saved in ~/.tsung/log/, and a new subdirectory is created for each test using the current date and time for the directory name, e.g. ~/.tsung/log/20180717-0940.

Creating your first config Tsung file

You can now create your config file:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE tsung SYSTEM "/usr/local/Cellar/tsung/1.7.0/share/tsung/tsung-1.0.dtd" [
<!ENTITY login_post_session SYSTEM "tsung_recorder20180717.xml">
]>

<tsung loglevel="debug">

  <clients>
    <client host="localhost" use_controller_vm="true" maxusers='10000'/>
  </clients>

  <servers>
    <server host="yourWebsite.de" port="443" type="ssl"/>
  </servers>

  <!-- test is finished after 2 minutes -->
  <load duration="2" unit="minute">
    <!-- during the first second of this test 1 new user per second is created,
    maximum of users is 1 -->
    <arrivalphase phase="1" duration="2" unit="minute">
      <users arrivalrate="1" unit="second" maxnumber="1"></users>
    </arrivalphase>
  </load>

  <options>
  <!-- adding csv file with user-email and user-password for login -->
  <option name="file_server" id="userlist" value=".tsung/example_userlist.csv"/>
  </options>

  <sessions>
    &login_post_session;
  </sessions>

</tsung>

Next, you need to adapt the following code:

  • Change /usr/local/Cellar/tsung/1.7.0/share/tsung/tsung-1.0.dtd to fit your system.
  • Change tsung_recorder20180717.xml according to your tsung_recorder file name (you will create that file in a minute ;).
  • Change host and port in <servers> according to your requirements. For HTTP you need to use tcp, for HTTPS you need to use ssl. Types could also be tcp, udp, tcp6, ssl6, udp6, websocket.
  • The clients are the servers that execute the actual load test. Be aware that we used the basic setup for a non-distributed load - localhost as client. If you need to change this, you can find more information in the Tsung Docs.
  • You also need to create an example_userlist.csv with your user-email and user-password for the login of your application. Then change the value parameter of the <option> according to your csv-file name.

Example csv-list:

user1;password1
user2;password2
  • You can change the maxnumber to the number of users you have added to the csv file. If you have 10 users in your csv file, you can change it to maxnumber="10". To test how many concurrent users can do a post, you have to adapt this number and change the csv file to the amount of users you want.
  • You can also change the load duration if your session is longer than two minutes.
  • There are even more options. Multiple user agents can be setup and given a probability. Please also consult the documentation.

Creating a session

You should now be using the Tsung proxy recorder which allows you to record your login and post session to a configuration file. A session defines the content of the scenario itself. The proxy is listening to port 8090, and you can change the port with -L portnumber.

To start, set the manual browser configuration (see connection settings in your preferred browser via preferences) for SSL or HTTP to localhost (or your url) and port 8090. Now, start the recorder with: tsung-recorder start and start clicking through your application. To stop the recorder just type tsung-recorder stop.

Keep in mind that there will be a lot of overhead in your recording session. You can delete and adjust many GET requests for your usage.

Changing your session

To run this session repeatedly with different users from your csv file and with different authentication tokens, the file needs to be adjusted. What I will cover is how to handle authenticity tokens, user-email and user-password. If the content-type of your POST request is application/x-www-form-urlencoded, these need to be changed into dynamic variables, and then parsed to url-encoded variables which were used for login and post requests.

First, you have to set dynamic variables for user-email and user-password from your csv file directly after the session tag in the beginning.

<session name='rec20180924-1355' probability='100'  type='ts_http'>

<!-- sets dynamic variables from csv list with id userlist -->
<setdynvars sourcetype="file" fileid="userlist" delimiter=";" order="iter">
  <var name="user_email" />
  <var name="user_password" />
</setdynvars>

If you are also using an authenticity token in a hidden form field on your site, you can grab it and just put it in as a child element directly after your request: <dyn_variable name="name_of_your_hidden_field_form"></dyn_variable> and before the http tag.

Now there is a need for parsing those variables. Otherwise your dynamic variables could have an empty space or something else which would not work for our content-type.

Therefore, you have to set new dynamic variables with <setdynvars> for authenticity-token, user-email and user-password, and parse those to url-encoded variables. You can do this by using a tiny bit of Erlang code and this method: http_uri:encode(example_var).

<request>
  <!-- sets dynamic variable from hidden form field authenticity_token -->
  <dyn_variable name="authenticity_token"></dyn_variable>
  <http url='/login' version='1.1' method='GET'>
    <http_header name='Accept-Encoding' value='gzip, deflate' />
  </http>
</request>

<!-- parses dynamic variables user_email, user_password and authenticity_token to url-encoded variables -->
<setdynvars sourcetype="eval"
            code="fun({Pid,DynVars})->
                      {ok,Val}=ts_dynvars:lookup(user_email,DynVars),
                      http_uri:encode(Val) end.">
  <var name="encoded_user_email" />
</setdynvars>

<setdynvars sourcetype="eval"
            code="fun({Pid,DynVars})->
                      {ok,Val}=ts_dynvars:lookup(user_password,DynVars),
                      http_uri:encode(Val) end.">
  <var name="encoded_user_password" />
</setdynvars>

<setdynvars sourcetype="eval"
            code="fun({Pid,DynVars})->
                      {ok,Val}=ts_dynvars:lookup(authenticity_token,DynVars),
                      http_uri:encode(Val) end.">
  <var name="encoded_authenticity_token" />
</setdynvars>

Now, you have to integrate your dynamic variables in your POST request. First append subst="true" to the <request> for a dynamic variable request.

Afterwards you adapt the content of your POST and integrate the variables like this: authenticity_token=%%_encoded_authenticity_token%%&amp;. One thing to remember is that all dynamic variables must be prefixed with an underscore in the requests. When you define a variable with the name test_var, you need to call it via %%_test_var%% in your request.

This giant query string example shows what it could look like:

contents='authenticity_token=%%_encoded_authenticity_token%%&amp;user_session%5Bemail%5D=%%_encoded_user_email%%&amp;user_session%5Bpassword%5D=%%_encoded_user_password%%&amp;commit=Log+in'

<!-- for using dynamic variables the request has to be subst="true"-->
<request subst="true">
  <!-- sets encoded dynamic variables for authenticity_token, user-email and user-password-->
  <http url='/user_startpage' version='1.1'
    contents='authenticity_token=%%_encoded_authenticity_token%%&amp;user_session%5Bemail%5D=%%_encoded_user_email%%&amp;user_session%5Bpassword%5D=%%_encoded_user_password%%&amp;commit=Log+in'
    content_type='application/x-www-form-urlencoded'
    method='POST'>
    <http_header name='Accept-Encoding' value='gzip, deflate' />
  </http>
</request>

If you have to use an authenticity token again, you are doing the same procedure again and again.

Congrats! You now have adapted your session file into a properly working file. You just need to run it :). See the full session here:

<session name='rec20180924-1355' probability='100'  type='ts_http'>

<!-- sets dynamic variables from csv list with id userlist -->
<setdynvars sourcetype="file" fileid="userlist" delimiter=";" order="iter">
  <var name="user_email" />
  <var name="user_password" />
</setdynvars>

<request>
  <http url='yourWebsite.de' version='1.1' method='GET'>
    <http_header name='Accept-Encoding' value='gzip, deflate' />
  </http>
</request>

<!-- thinktime of user is random but between 2 and 5 seconds -->
<thinktime random="true" min="2" max="5"/>

<request>
  <!-- sets dynamic variable from hidden form field authenticity_token -->
  <dyn_variable name="authenticity_token"></dyn_variable>
  <http url='/login' version='1.1' method='GET'>
    <http_header name='Accept-Encoding' value='gzip, deflate' />
  </http>
</request>

<thinktime random="true" min="2" max="8"/>

<!-- parses dynamic variables to url-encoded variables -->
<setdynvars sourcetype="eval"
            code="fun({Pid,DynVars})->
                      {ok,Val}=ts_dynvars:lookup(user_email,DynVars),
                      http_uri:encode(Val) end.">
  <var name="encoded_user_email" />
</setdynvars>

<setdynvars sourcetype="eval"
            code="fun({Pid,DynVars})->
                      {ok,Val}=ts_dynvars:lookup(user_password,DynVars),
                      http_uri:encode(Val) end.">
  <var name="encoded_user_password" />
</setdynvars>

<setdynvars sourcetype="eval"
            code="fun({Pid,DynVars})->
                      {ok,Val}=ts_dynvars:lookup(authenticity_token,DynVars),
                      http_uri:encode(Val) end.">
  <var name="encoded_authenticity_token" />
</setdynvars>

<!-- for using dynamic variables the request has to be subst="true"-->
<request subst="true">
  <!-- sets encoded dynamic variables for authenticity_token, user-email and user-password-->
  <http url='/user_startpage' version='1.1' contents='authenticity_token=%%_encoded_authenticity_token%%&amp;user_session%5Bemail%5D=%%_encoded_user_email%%&amp;user_session%5Bpassword%5D=%%_encoded_user_password%%&amp;commit=Log+in'
  content_type='application/x-www-form-urlencoded'
  method='POST'>
    <http_header name='Accept-Encoding' value='gzip, deflate' />
  </http>
</request>

<thinktime random="true" min="5" max="10"/>

<request>
  <http url='/my_overview' version='1.1' method='GET'>
    <http_header name='Accept-Encoding' value='gzip, deflate' />
  </http>
</request>

<thinktime random="true" min="4" max="6"/>

<request>
  <!-- sets dynamic variable from hidden form field authenticity_token -->
  <dyn_variable name="authenticity_token"></dyn_variable>
  <http url='/my_posts' version='1.1' method='GET'>
    <http_header name='Accept-Encoding' value='gzip, deflate' />
  </http>
</request>

<thinktime random="true" min="4" max="10"/>

<!-- parses dynamic variable authenticity_token to url-encoded variable -->
<setdynvars sourcetype="eval"
            code="fun({Pid,DynVars})->
                      {ok,Val}=ts_dynvars:lookup(authenticity_token,DynVars),
                      http_uri:encode(Val) end.">
  <var name="encoded_post_authenticity_token" />
</setdynvars>

<request subst="true">
  <http url='/posts' version='1.1'
  contents='authenticity_token=%%_encoded_post_authenticity_token%%&amp;post%5Btitle%5D=Yeah&amp;post%5Bmessage%5D=HelloWorld&amp;commit=Post'
  content_type='application/x-www-form-urlencoded'
  method='POST'>
    <http_header name='Accept-Encoding' value='gzip, deflate' />
  </http>
</request>

<request>
  <http url='/my_posts/1' version='1.1' method='GET'>
    <http_header name='Accept-Encoding' value='gzip, deflate' />
  </http>
</request>

<thinktime random="true" min="4" max="6"/>

</session>

Running and Reporting

Step 1: Run your chosen configuration file: cd to the folder where this configuration file is saved e.g. yourPath/.tsung and start the file with: tsung -f nameOfYourLoadtest.xml start (tsung -h for man page)

Step 2: Go to log directory: cd ~/.tsung/log/nameOfYourLog

Step 3: Generate graphical report: execute /usr/local/Cellar/tsung/1.7.0/lib/tsung/bin/tsung_stats.pl to generate static graph (you might adapt this to your system).

Step 4: Open report in browser: e.g firefox report.html

I hope this tutorial can help you to set up your Tsung load test.

TAGS

Kommentare

Um die Kommentare zu sehen, bitte unserer Cookie Vereinbarung zustimmen. Mehr lesen