1Advanced Features 2----------------- 3 4Dynamic substitutions 5^^^^^^^^^^^^^^^^^^^^^ 6 7Dynamic substitution are mark-up placed in element of the scenario. 8For HTTP, this mark-up can be placed in basic authentication (www\_authenticate 9tag: userid and passwd attributes), URL (to change GET parameter) 10and POST content. 11 12Those mark-up are of the form ``%%Module:Function%%``. 13Substitutions are executed on a request-by-request basis, only if the 14request tag has the attribute ``subst="true"``. 15 16When a substitution is requested, the substitution mark-up is replaced by 17the result of the call to the Erlang function: 18``Module:Function({Pid, DynData})`` where ``Pid`` is the Erlang process 19id of the current virtual user and DynData the list of all Dynamic 20variables (**Warn: before version 1.1.0, the argument was just the 21Pid!**). 22 23Here is an example of use of substitution in a Tsung scenario: 24 25.. code-block:: xml 26 27 <session name="rec20040316-08:47" probability="100" type="ts_http"> 28 <request subst="true"> 29 <http url="/echo?symbol=%%symbol:new%%" method="GET"></http> 30 </request> 31 </session> 32 33For the http plugin, and since version 1.5.1, you can use the special value 34``subst='all_except_body'`` instead of ``'true'`` to skip the substitutions in 35the body part of the HTTP response. 36 37Here is the Erlang code of the module used for dynamic substitution: 38 39.. code-block:: erlang 40 41 -module(symbol). 42 -export([new/1]). 43 44 new({Pid, DynData}) -> 45 case random:uniform(3) of 46 1 -> "IBM"; 47 2 -> "MSFT"; 48 3 -> "RHAT" 49 end. 50 51 52Use :command:`erlc` to compiled the code, and put the resulting .beam 53file in :file:`\$PREFIX/lib/erlang/lib/tsung-X.X.X/ebin/` on all client 54machines. 55 56As you can see, writing scenario with dynamic substitution is 57simple. It can be even simpler using dynamic variables (see later). 58 59.. index:: ts_user_server 60 61If you want to set unique id, you can use the built-in function 62**ts_user_server:get_unique_id**. 63 64.. code-block:: xml 65 66 <session name="rec20040316-08:47" probability="100" type="ts_http"> 67 <request subst="true"> 68 <http url="/echo?id=%%ts_user_server:get_unique_id%%" method="GET" /> 69 </request> 70 </session> 71 72 73Reading external file 74^^^^^^^^^^^^^^^^^^^^^ 75 76**New in 1.0.3**: A new module ``ts_file_server`` is available. You 77can use it to read external files. For example, if you need to read user 78names and passwd from a CSV file, you can do it with it (currently, 79you can read only a single file). 80 81 82You have to add this in the XML configuration file: 83 84.. code-block:: xml 85 86 <option name="file_server" value="/tmp/userlist.csv"></option> 87 88 89**New in 1.2.2**: You can read several files, using the **id** 90attribute to identify each file: 91 92.. code-block:: xml 93 94 <option name="file_server" value="/tmp/userlist.csv"></option> 95 <option name="file_server" id='random' value="/tmp/randomnumbers.csv"></option> 96 97 98Now you can build your own function to use it, for example, create a 99file called :file:`readcsv.erl`: 100 101.. code-block:: erlang 102 103 -module(readcsv). 104 -export([user/1]). 105 106 user({Pid,DynVar})-> 107 {ok,Line} = ts_file_server:get_next_line(), 108 [Username, Passwd] = string:tokens(Line,";"), 109 "username=" ++ Username ++"&password=" ++ Passwd. 110 111 112The output of the function will be a string ``username=USER&password=PASSWORD`` 113 114Then compile it with :command:`erlc readcsv.erl` and put 115:file:`readcsv.beam` in :file:`$prefix/lib/erlang/lib/tsung-VERSION/ebin` directory (if the 116file has an id set to ``random``, change the call to ``ts_file_server:get_next_line(random)``). 117 118Then use something like this in your session: 119 120.. code-block:: xml 121 122 <request subst="true"> 123 </http> 124 </request> 125 126 127Two functions are available: ``ts_file_server:get_next_line`` 128and ``ts_file_server:get_random_line``. For the 129``get_next_line`` function, when the end of file is reached, the 130first line of the file will be the next line. 131 132**New in 1.3.0**: you no longer have to create an external 133function to parse a simple csv file: you can use ``setdynvars`` 134(see next section for detailed documentation): 135 136.. code-block:: xml 137 138 <setdynvars sourcetype="file" fileid="userlist.csv" delimiter=";" order="iter"> 139 <var name="username" /> 140 <var name="user_password" /> 141 </setdynvars> 142 143 144This defines two dynamic variables **username** and 145**user_password** filled with the next entry from the csv 146file. Using the previous example, the request is now: 147 148.. code-block:: xml 149 150 <request subst="true"> 151 <http url='/login.cgi' version='1.0' 152 contents='username=%%_username%%&password=%%_user_password%%&op=login' 153 content_type='application/x-www-form-urlencoded' method='POST'> 154 </http> 155 </request> 156 157 158Much simpler than the old method! 159 160In case you have several arrival phases programmed and if you use file with 161``order="iter"`` the position in the file will not be reset between different 162arrival phase. You will not be returned to the first line when changing phase. 163 164.. code-block:: xml 165 166 <arrivalphase phase="1" duration="10" unit="minute"> 167 <users maxnumber="10" arrivalrate="100" unit="second" /> 168 </arrivalphase> 169 <arrivalphase phase="2" duration="10" unit="minute"> 170 <users maxnumber="20" arrivalrate="100" unit="second"></users> 171 </arrivalphase> 172 173 174In this example phase 1 will read about 10 lines and phase 2 will read the next 17520 lines. 176 177.. TODO explain, that file servers are synchronized between tsung nodes in a distributed setup. 178 179.. index:: dyn_variable 180.. _sec-dynamic-variables-label: 181 182Dynamic variables 183^^^^^^^^^^^^^^^^^ 184 185In some cases, you may want to use a value given by the server in a 186response later in the session, and this value is **dynamically 187generated** by the server for each user. For this, you can use 188``<dyn_variable>`` in the scenario 189 190Let's take an example with HTTP. You can easily grab a value in a HTML 191form like: 192 193.. code-block:: html 194 195 <form action="go.cgi" method="POST"> 196 <hidden name="random_num" value="42"></form> 197 </form> 198 199with: 200 201.. code-block:: xml 202 203 <request> 204 <dyn_variable name="random_num"></dyn_variable> 205 <http url="/testtsung.html" method="GET" version="1.0"></http> 206 </request> 207 208 209Now ``random_num`` will be set to 42 during the users session. Its 210value will be replace in all mark-up of the form 211``%%_random_num%%`` if and only if the ``request`` tag has the 212attribute ``subst="true"``, like: 213 214.. code-block:: xml 215 216 <request subst="true"> 217 <http url="/go.cgi" version="1.0" 218 contents="username=nic&random_num=%%_random_num%%&op=login" 219 content_type="application/x-www-form-urlencoded" method="POST"> 220 </http> 221 </request> 222 223 224Regexp 225"""""" 226 227If the dynamic value is not a form variable, you can set a regexp by 228hand, for example to get the title of a HTML page: the regexp engine 229uses the ``re`` module, a Perl like regular expressions module 230for Erlang. 231 232.. code-block:: xml 233 234 <request> 235 <dyn_variable name="mytitlevar" 236 re="<title>(.*)</title>"/> 237 <http url="/testtsung.html" method="GET" version="1.0"></http> 238 </request> 239 240 241Previously (before 1.4.0), Tsung uses the old ``regexp`` module 242from Erlang. This is now deprecated. The syntax was: 243 244.. code-block:: xml 245 246 <request> 247 <dyn_variable name="mytitlevar" 248 regexp="<title>\(.*\)</title>"/> 249 <http url="/testtsung.html" method="GET" version="1.0"></http> 250 </request> 251 252.. index:: xpath 253 254XPath 255""""" 256 257A new way to analyze the server response has been introduced in the 258release **1.3.0**. It is available only for the HTTP and XMPP plugin since it is 259based on XML/HTML parsing. This feature uses the mochiweb library 260and **only works with Erlang R12B and newer version**. 261 262This give us some benefices: 263 264* XPath is simple to write and to read, and match very well with 265 HTML/XML pages 266 267* The parser works on ``binaries()``, and doesn't create any 268 ``string()``. 269 270* The cost of parsing the HTML/XML and build the tree is amortized 271 between all the dyn_variables defined for a given request 272 273 274To utilize XPath expression, use a ``xpath`` attribute when 275defining the ``dyn_variable``, instead of ``re``, like: 276 277.. code-block:: xml 278 279 <dyn_variable name="field1_value" xpath="//input[@name='field1']/@value"/> 280 <dyn_variable name="title" xpath="/html/head/title/text()"/> 281 282 283There is a bug in the XPath engine, result nodes from 284"descendant-or-self" aren't returned in document order. This isn't a 285problem for the most common cases. 286 287However, queries like ``//img[1]/@src`` are not recommended, 288as the order of the ``<img>`` elements returned from ``//img`` is 289not the expected. 290 291The order is respected for paths without "descendant-or-self" axis, so 292this: ``/html/body/div[2]/img[3]/@src`` is interpreted as 293expected and can be safely used. 294 295It is possible to use XPath to get a list of elements from an html page, 296allowing dynamic retrieval of objects. You can either create embedded 297Erlang code to parse the list produced, or use foreach that was introduced 298in release **1.4.0**. 299 300For XMPP, you can get all the contacts in a dynamic variable: 301 302.. code-block:: xml 303 304 <request subst="true"> 305 <dyn_variable name="contactJids" 306 xpath="//iq[@type='result']/query[@xmlns='jabber:iq:roster']//item[string-length(@wr:type)=0]/@jid" /> 307 <jabber type="iq:roster:get" ack="local"/> 308 </request> 309 310 311.. index:: jsonpath 312 313.. _sec-jsonpath-label: 314 315JSONPath 316"""""""" 317 318Another way to analyze the server response has been introduced in the 319release **1.3.2** when the server is sending JSON data. It is 320only for the HTTP plugin. This feature uses the mochiweb library and 321**only works with Erlang R13B and newer version**. 322 323Tsung implements a (very) limited subset of JSONPath as defined here 324http://goessner.net/articles/JsonPath/ 325 326To utilize jsonpath expression, use a **jsonpath** attribute when 327defining the ``<dyn_variable>>``, instead of ``re``, like: 328 329.. code-block:: xml 330 331 <dyn_variable name="array3_value" jsonpath="field.array[3].value"/> 332 333 334You can also use expressions ``Key=Val``, e.g.: 335 336.. code-block:: xml 337 338 <dyn_variable name="myvar" jsonpath="field.array[?name=bar].value"/> 339 340 341PostgreSQL 342"""""""""" 343 344.. versionadded:: 1.3.2 345 346Since the PostgreSQL protocol is binary, regexp are not useful to 347parse the output of the server. Instead, a specific parsing can be 348done to extract content from the server's response; to do this, use the 349``pgsql_expr`` attribute. Use ``data_row[L][C]`` to 350extract the column C of the line L of the data output. You can also use 351the literal name of the column (ie. the field name of the 352table). This example extract 3 dynamic variables from the server's 353response: 354 355First one, extract the 3rd column of the fourth row, then the ``mtime`` 356field from the second row, and then it extract some data of the 357``row_description``. 358 359.. code-block:: xml 360 361 <request> 362 <dyn_variable name="myvar" pgsql_expr="data_row[4][3]"/> 363 <dyn_variable name="mtime" pgsql_expr="data_row[2].mtime"/> 364 <dyn_variable name="row" pgsql_expr="row_description[1][3][1]"/> 365 <pgsql type="sql">SELECT * from pgbench_history LIMIT 20;</pgsql> 366 </request> 367 368 369A row description looks like this:: 370 371 | =INFO REPORT==== 14-Apr-2010::11:03:22 === 372 | ts_pgsql:(7:<0.102.0>) PGSQL: Pair={row_description, 373 | [{"tid",text,1,23,4,-1,16395}, 374 | {"bid",text,2,23,4,-1,16395}, 375 | {"aid",text,3,23,4,-1,16395}, 376 | {"delta",text,4,23,4,-1,16395}, 377 | {"mtime",text,5,1114,8,-1,16395}, 378 | {"filler",text,6,1042,-1,26,16395}]} 379 380 381So in the example, the **row** variable equals "aid". 382 383Decoding variables 384"""""""""""""""""" 385 386It's possible to decode variable that contains html entities encoded, 387this is done with **decode** attribute set to **html_entities**. 388 389.. code-block:: xml 390 391 <request> 392 <dyn_variable name="mytitlevar" 393 re="<title>(.*)</title>" 394 decode="html_entities"/> 395 <http url="/testtsung.html" method="GET" version="1.0"></http> 396 </request> 397 398.. index:: setdynvars 399 400set_dynvars 401""""""""""" 402 403**Since version 1.3.0**, more powerful dynamic variables are implemented. 404 405You can set dynamic variables not only while parsing server data, but 406you can build them using external files or generate them with a function 407or generate random numbers/strings: 408 409Several types of dynamic variables are implemented (``sourcetype`` attribute): 410 411.. index:: callback 412 413* Dynamic variables defined by calling an Erlang function: 414 415 .. code-block:: xml 416 417 <setdynvars sourcetype="erlang" callback="ts_user_server:get_unique_id"> 418 <var name="id1" /> 419 420.. index:: delimiter 421.. index:: fileid 422.. index:: iter 423 424* Dynamic variables defined by parsing an external file: 425 426 .. code-block:: xml 427 428 <setdynvars sourcetype="file" fileid="userdb" delimiter=";" order="iter"> 429 <var name="user" /> 430 <var name="user_password" /> 431 </setdynvars> 432 433 *delimiter* can be any string, and *order* can be 434 **iter** or **random** 435 436* A dynamic variable can be a random number (uniform distribution) 437 438 .. code-block:: xml 439 440 <setdynvars sourcetype="random_number" start="3" end="32"> 441 <var name="rndint" /> 442 </setdynvars> 443 444* A dynamic variable can be a random string 445 446 .. code-block:: xml 447 448 <setdynvars sourcetype="random_string" length="13"> 449 <var name="rndstring1" /> 450 </setdynvars> 451 452* A dynamic variable can be a urandom string: this is much faster than 453 the random string, but the string is not really random: the same set 454 of characters is always used. 455 456* A dynamic variable can be generated by dynamic evaluation of erlang code: 457 458 .. code-block:: xml 459 460 <setdynvars sourcetype="eval" 461 code="fun({Pid,DynVars})-> 462 {ok,Val}=ts_dynvars:lookup(md5data,DynVars), 463 ts_digest:md5hex(Val) end."> 464 <var name="md5sum" /> 465 </setdynvars> 466 467 468 In this case, we use tsung function ``ts_dynvars:lookup`` to retrieve the 469 dynamic variable named ``md5data``. This dyn\_variable ``md5data`` 470 can be set in any of the ways described in the Dynamic variables 471 section :ref:`sec-dynamic-variables-label`. 472 473* A dynamic variable can be generated by applying a JSONPath 474 specification (see :ref:`sec-jsonpath-label`) to an existing dynamic 475 variable: 476 477 .. code-block:: xml 478 479 <setdynvars sourcetype="jsonpath" from="notification" jsonpath="result[?state=OK].node"> 480 <var name="deployed" /> 481 </setdynvars> 482 483* You can create dynamic variables to get the hostname and port of the current server 484 485 .. code-block:: xml 486 487 <setdynvars sourcetype="server"> 488 <var name="host" /> 489 <var name="port" /> 490 </setdynvars> 491 492 493* You can define a dynamic variable as constant value to use it in 494 a plugin (since version **1.5.0**) 495 496 .. code-block:: xml 497 498 <setdynvars sourcetype="value" value="foobar"> 499 <var name="constant" /> 500 </setdynvars> 501 502 503 504 505A **setdynvars** can be defined anywhere in a session. 506 507 508.. index:: match 509 510Checking the server's response 511^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 512 513With the tag ``match`` in a ``<request>`` tag, you can check 514the server's response against a given string, and do some actions 515depending on the result. In any case, if it matches, this will 516increment the ``match`` counter, if it does not match, the 517``nomatch`` counter will be incremented. 518 519For example, let's say you want to test a login page. If the login is 520ok, the server will respond with ``Welcome !`` in the 521HTML body, otherwise not. To check that: 522 523.. code-block:: xml 524 525 <request> 526 <match do="continue" when="match">Welcome !</match> 527 <http url="/login.php" version="1.0" method="POST" 528 contents="username=nic&user_password=sesame" 529 content_type="application/x-www-form-urlencoded" > 530 </request> 531 532 533You can use a regexp instead of a simple string. 534 535The list of available actions to do is: 536 537* **continue**: do nothing, continue (only update match or nomatch counters) 538 539* **log**: log the request id, userid, sessionid, name in a file (in :file:`match.log`) 540 541* **abort**: abort the session 542 543* **abort_test**: abort the whole test 544 545* **restart**: restart the session. The maximum number of 546 restarts is 3 by default. 547 548* **loop**: repeat the request, after 5 seconds. The maximum number of 549 loops is 20 by default. 550 551* **dump**: dump the content of the response in a file. The filename 552 is :file:`match-<userid>-<sessionid>-<requestid>-<dumpid>.dump` 553 554 555You can mixed several match tag in a single request: 556 557.. code-block:: xml 558 559 <request> 560 <match do="loop" sleep_loop="5" max_loop="10" when="match">Retry</match> 561 <match do="abort" when="match">Error</match> 562 <http url='/index.php' method=GET'> 563 </request> 564 565 566You can also do the action on **nomatch** instead of **match**. 567 568.. index:: skip_headers 569.. index:: apply_to_content 570 571If you want to skip the HTTP headers, and match only on the body, you 572can use **skip_headers='http'**. Also, you can apply a 573function to the content before matching; for example the following 574example use both features to compute the md5sum on the body of a HTTP 575response, and compares it to a given value: 576 577.. code-block:: xml 578 579 <match do='log' when='nomatch' skip_headers='http' apply_to_content='ts_digest:md5hex'>01441debe3d7cc65ba843eee1acff89d</match> 580 <http url="/" method="GET" version="1.1"/> 581 582 583You can also use dynamic variables, using the **subst** attribute: 584 585.. code-block:: xml 586 587 <match do='log' when='nomatch' subst='true' >%%_myvar%%</match> 588 <http url="/" method="GET"/> 589 590 591**Since 1.5.0**, it's now possible to add **name** attribute in **match** tag to name a record printed in match.log as follow: 592 593.. code-block:: xml 594 595 <match do='log' when='match' name='http_match_200ok'>200OK</match> 596 <http url="/" method="GET" version="1.1"/> 597 598 599Loops, If, Foreach 600^^^^^^^^^^^^^^^^^^ 601 602**Since 1.3.0**, it's now possible to add conditional/unconditional loops in a session. 603 604**Since 1.4.0**, it is possible to loop through a list of dynamic variables thanks to foreach. 605 606.. index:: for 607 608<for> 609""""" 610 611Repeat the enclosing actions a fixed number of times. A dynamic 612variable is used as counter, so the current iteration could be used in 613requests. List of attributes: 614 615``from`` 616 Initial value 617``to`` 618 Last value 619``incr`` 620 Amount to increment in each iteration 621``var`` 622 Name of the variable to hold the counter 623 624 625.. code-block:: xml 626 627 <for from="1" to="10" incr="1" var="counter"> 628 ... 629 <request> <http url="/page?id=%%_counter%%"></http> </request> 630 ... 631 </for> 632 633.. index:: repeat 634.. index:: while 635.. index:: until 636 637<repeat> 638"""""""" 639 640Repeat the enclosing action (while or until) some condition. This is 641intended to be used together with ``<dyn_variable>`` declarations. List of 642attributes: 643 644``name`` 645 Name of the repeat 646 647``max_repeat`` 648 Max number of loops (default value is 20) 649 650 651The last element of repeat must be either ``<while>`` or ``<until>`` example: 652 653.. code-block:: xml 654 655 <repeat name="myloop" max_repeat="40"> 656 ... 657 <request> 658 <dyn_variable name="result" re="Result: (.*)"/> 659 <http url="/random" method="GET" version="1.1"></http> 660 </request> 661 ... 662 <until var="result" eq="5"/> 663 </repeat> 664 665 666**Since 1.3.1**, it's also possible to add if statements based on 667dynamic variables: 668 669.. index:: if 670 671<if> 672"""" 673 674.. code-block:: xml 675 676 <if var="tsung_userid" eq="3"> 677 <request> <http url="/foo"/> </request> 678 <request> <http url="/bar"/> </request> 679 </if> 680 681 682You can use ``eq`` or ``neq`` to check the variable. 683 684**Since 1.5.1** you can also use the comparison operators ``gt``, 685``gte``, ``lt`` and ``lte`` to do respectively ``greater than``, 686``greater than or equal to``, ``less than`` and ``less than or equal to``. 687 688If the dynamic variable is a list (output from XPath for example), you 689can access to the n-th element of a list like this: 690 691.. code-block:: xml 692 693 <if var="myvar[1]" eq="3"> 694 695Here we compare the first element of the list to 3. 696 697.. index:: abort 698 699<abort> 700"""""""" 701**Since 1.7.0** you can abort the session or the whole test by using an ``<abort/>`` element in a session (can be used inside an <if> statement for example). By default it will abort the current user session, but you can abort the whole test by setting the `type` attribute to `all` ``<abort type='all'/>`` 702 703.. index:: foreach 704 705<foreach> 706""""""""" 707 708Repeat the enclosing actions for all the elements contained in the list specified. The basic syntax is as follows: 709 710.. code-block:: xml 711 712 <foreach name="element" in="list"> 713 <request subst="true"> 714 <http url="%%_element%%" method="GET" version="1.1"/> 715 </request> 716 </foreach> 717 718 719It is possible to limit the list of elements you're looping through, thanks to the use of the ``include`` or ``exclude`` attributes inside the foreach statement. 720 721As an example, if you want to include only elements with a local path you can write: 722 723.. code-block:: xml 724 725 <foreach name="element" in="list" include="^/.*$"> 726 727 728If you want to exclude all the elements from a specific URI, you would write: 729 730.. code-block:: xml 731 732 <foreach name="element" in="list" exclude="http:\/\/.*\.tld\.com\/.*$"> 733 734 735You can combine this with a XPath query. For instance the following scenario will retrieve all the images specified on a web page: 736 737 738.. code-block:: xml 739 740 <request subst="true"> 741 <dyn_variable name="img_list" xpath="//img/@src"/> 742 <http url="/mypage.html" method="GET" version="1.1"/> 743 </request> 744 <foreach name="img" in="img_list"> 745 <request subst="true"> 746 <http url="%%_img%%" method="GET" version="1.1"/> 747 </request> 748 </foreach> 749 750Rate limiting 751^^^^^^^^^^^^^ 752 753Since version **1.4.0**, rate limiting can be enabled, either globally 754(see :ref:`sec-options-label`), or for each session separately. 755 756For example, to limit the rate to 64KB/sec for a given session: 757 758.. code-block:: xml 759 760 <session name="http-example" probability="70" type="ts_http"> 761 <set_option name="rate_limit" value="64" /> 762 ... 763 </session> 764 765 766Only the incoming traffic is rate limited currently. 767 768.. index:: tag 769 770Requests exclusion 771^^^^^^^^^^^^^^^^^^ 772 773.. versionadded:: 1.5.1 774 775It is possible to exclude some request for a special run. To do this 776you have to tag them and use the option ``-x`` when launching the run. 777 778For example, to exclude the GET of foo.png, add a ``tag`` to the 779respective request: 780 781.. code-block:: xml 782 783 <request> 784 <http url="/" method="GET"></http> 785 </request> 786 <request tag="image"> 787 <http url="/foo.png" method="GET"></http> 788 </request> 789 790Then launch the run with:: 791 792 tsung -f SCENARIO.xml -x image start 793 794Only the GET to ``/`` will be performed. 795 796Note that request tags also get logged on **dumptraffic="protocol"** (see :ref:`sec-file-structure-label`) 797 798Client certificate 799^^^^^^^^^^^^^^^^^^ 800.. versionadded:: 1.5.1 801 802It is possible to use a client certificate for ssl authentication. You 803can use dynamic variables to set some parameters of the certificate 804(and the key password is optional). 805 806.. code-block:: xml 807 808 <session name="https-with-cert" probability="70" type="ts_http"> 809 810 <set_option name="certificate"> 811 <certificate cacertfile="/etc/ssl/ca.pem" 812 keyfile="%%_keyfile%%" keypass="%%_keypass%%" certfile="/home/nobody/.tsung/client.pem"/> 813 </set_option> 814