引用網址:http://blog.codecp.org/2016/08/24/centos%E4%B8%8B%E5%AE%89%E8%A3%85Tsung/
Centos下安装Tsung
近期公司openfire相关产品频繁出现问题,需要测试一下Openfire服务器的性能,同事推荐了Tsung这个鼎鼎大名的工具来进行测试,这里分享下这次测试的情况。
首先介绍下Tsung在Centos下的安装:
一、安装环境
1
2
3
4
5
6
|
[root@minion2 ~]# lsb_release -a
LSB Version: :core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch:languages-4.1-amd64:languages-4.1-noarch:printing-4.1-amd64:printing-4.1-noarch
Distributor ID: CentOS
Description: CentOS Linux release 7.0.1406 (Core)
Release: 7.0.1406
Codename: Core
|
二、安装系统依赖软件
1
2
3
4
|
yum install gcc -y
yum install perl -y
yum install unixODBC
yum install unixODBC-devel
|
三、安装Erlang/OTP R14B及以上版本
下载地址:http://www.erlang.org/downloads
1
2
3
4
5
|
[root@minion2 src]# wget http://www.erlang.org/download/otp_src_19.0.tar.gz
[root@minion2 src]# cd otp_src_19.0
[root@minion2 otp_src_19.0]# ./configure --prefix=/usr/local/erlang
[root@minion2 otp_src_19.0]# make
[root@minion2 otp_src_19.0]# make install
|
四、编译安装Tsung
1
2
3
4
5
6
|
[root@minion2 src]# wget http://tsung.erlang-projects.org/dist/tsung-1.6.0.tar.gz
[root@minion2 src]# tar xvf tsung-1.6.0.tar.gz
[root@minion2 src]# cd tsung-1.6.0
[root@minion2 tsung-1.6.0]# ./configure --prefix=/usr/local/tsung --with-erlang=/usr/local/erlang
[root@minion2 tsung-1.6.0]# make
[root@minion2 tsung-1.6.0]# make install
|
五、下载并安装perl Template,用于生成报告模版
1
2
3
4
5
6
7
|
[root@minion2 src]# wget http://www.cpan.org/modules/by-module/Template/Template-Toolkit-2.26.tar.gz
[root@minion2 src]# tar xvf Template-Toolkit-2.26.tar.gz
[root@minion2 src]# cd Template-Toolkit-2.26
[root@minion2 Template-Toolkit-2.26]# perl Makefile.PL
[root@minion2 Template-Toolkit-2.26]# make
[root@minion2 Template-Toolkit-2.26]# make test
[root@minion2 Template-Toolkit-2.26]# make install
|
六、下载并安装用于聊天生成的gnuplot
1
|
[root@minion2 ~]# yum install -y gnuplot gd libpng zlib
|
七、配置erlang和tsung的环境变量
1
2
3
4
5
6
7
|
[root@minion2 ~]# vim /etc/profile
#添加
export ERLANG_PATH=/usr/local/erlang
export TSUNG_PATH=/usr/local/tsung
export PATH=.:$PATH:$ERLANG_PATH/bin:$TSUNG_PATH/bin/
[root@minion2 ~]# source /etc/profile
|
到目前为止安装过程完成。
八、Tsung使用
Tsung使用这里简单介绍下,后续我会更新具体测试的博文。
1、配置文件
默认配置文件是 ~/.tsung/tsung.xml
因此在用户宿主目录下新建目录.tsung,用于存放xml配置文件和log。
配置文件示例在 /usr/local/tsung/share/doc/tsung/examples
2、运行
安装完tsung后有两个commands可使用:tsung和tsung-recorder
典型的tsung运行命令是:
1
|
tsung -f myconfigfile.xml start
|
3、查看报告
运行完成后进入Log目录下可以看到生成的log信息
1
2
3
4
|
[root@minion2 .tsung]# cd log/20160824-1723/
[root@minion2 20160824-1723]# ls
http_simple.xml index.html inets_error.log match.log style tsung-123456.dump tsung_controller@minion2.log tsung.log
[root@minion2 20160824-1723]#
|
进入需要生成图形报表的Log目录,通过下面命令生成报告:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
[root@minion2 20160824-1723]# /usr/local/tsung/lib/tsung/bin/tsung_stats.pl
creating subdirectory data
creating subdirectory gnuplot_scripts
creating subdirectory images
No data for Bosh
No data for Match
No data for Event
No data for Async
No data for Errors
[root@minion2 20160824-1723]# ll
总用量 60
drwxr-xr-x. 2 root root 4096 8月 24 17:25 data
-rw-r--r--. 1 root root 6288 8月 24 17:25 gnuplot.log
drwxr-xr-x. 2 root root 4096 8月 24 17:25 gnuplot_scripts
-rw-r--r--. 1 root root 6636 8月 24 17:25 graph.html
-rw-r--r--. 1 root root 1533 8月 24 17:23 http_simple.xml
drwxr-xr-x. 2 root root 4096 8月 24 17:25 images
-rw-r--r--. 1 root root 63 8月 24 17:23 index.html
-rw-r--r--. 1 root root 0 8月 24 17:23 inets_error.log
-rw-r--r--. 1 root root 62 8月 24 17:23 match.log
-rw-r--r--. 1 root root 7737 8月 24 17:25 report.html
drwxr-xr-x. 2 root root 4096 8月 24 17:23 style
-rw-r--r--. 1 root root 0 8月 24 17:23 tsung-123456.dump
-rw-r--r--. 1 root root 3247 8月 24 17:23 tsung_controller@minion2.log
-rw-r--r--. 1 root root 3632 8月 24 17:23 tsung.log
|
打开report.html就可以查看报告了。
Tsung配置文件解析-文件结构
上篇Tsung的安装部署成功后,按照网上文档已对openfire进行了相关测试,但对其中一些内容还是似懂非懂,甚至云里雾里,远远达不到会用的目的。
最近刚好也对性能测试比较感兴趣,就抽时间在官网上查看介绍文档。下面就把这两天的成果与大家分享下,从本篇开始将对Tsung的配置文件进行介绍,内容基本上是对官方内容的翻译,有不对的地方欢迎大家不吝赐教。
Tsung官方文档地址:http://tsung.erlang-projects.org/user_manual/index.html
针对tsung.xml配置文件主要按照以下7个部分开始,同时会开7篇文章来介绍:
- 文件结构
- Clients and server
- Monitoring
- load progression
- options
- Sessions
- 高级特性
开始文件结构的介绍
一、默认编码
默认编码为utf-8,可以设置成其他编码,比如:
<?xml version=”1.0” encoding=”ISO-8859-1”?>
二、xml文件的结构
先整体了解xml文件的结构,它是由下面这些标签构成:
- 顶级标签<tsung>
- 客户端标签<clients>
- 服务端标签<servers>
- 监控标签<monitor>
- 负载标签<load>
- 选项标签<options>
- 过程标签<sessions>
三、顶级标签
3.1、顶级标签是tsung
例如
1
2
3
4
5
|
<?xml version="1.0"?>
<!DOCTYPE tsung SYSTEM "/usr/share/tsung/tsung-1.0.dtd" [] >
<tsung loglevel="info">
...
</tsung>
|
3.2、参数
3.2.1 dumptraffic
true:所有的通信都会被记录,注:这会大大降低tsung速度,一般是用于调试
light:只转储前44字节
3.2.2、loglevel
可设置值为:
- emergency
- critical
- error
- warning
- notice (default)
- info
- debug
loglevel对性能有很大影响,在高负载下建议使用warning级别
要设置很详细的log,需要在重新编译tsung使用make debug,然后设置loglevel为debug。
Tsung配置文件解析-Clients和Server
Clients and server
一、基本设置
对于非分布式负载,可以使用一个简单配置:
1
2
3
4
5
6
7
|
<clients>
<client host="localhost" use_controller_vm="true"/>
</clients>
<servers>
<server host="192.168.1.1" port="80" type="tcp"></server>
</servers>
|
注:最基本的设置,在同一机器上。
server是进入集群的入口点,可以添加多个servers。
默认每个server将有一个为1的weight,并且每个会话都会根据weight随机选择一个server。您可以为每台server设置一个weight(weight可以是一个整数或一个浮点数):
1
2
3
4
|
<servers>
<server host="server1" port="80" type="tcp" weight="4"></server>
<server host="server2" port="80" type="tcp" weight="1"></server>
</servers>
|
type可以是tcp、udp、ssl或者websocket
这里有一个特殊类型的type:BOSH,bosh是非加密的BOSH,bosh_ssl是加密连接。
二、高级设置
下个例子是更复杂的,使用先进的分布式测试的几个功能:
1
2
3
4
5
6
7
8
9
10
11
|
<clients>
<client host="louxor" weight="1" maxusers="800">
<ip value="10.9.195.12"></ip>
<ip value="10.9.195.13"></ip>
</client>
<client host="memphis" weight="3" maxusers="600" cpu="2"/>
</clients>
<servers>
<server host="10.9.195.1" port="8080" type="tcp"></server>
</servers>
|
可以用多个虚拟IP来模拟出更多的机器,在负载均衡中使用客户端IP把流量分配到服务端集群中时,这点十分有用。
IP不是强制性的,如果没有设置将会使用默认IP。
可以设置<ip scan=”true” value=”eth0”/>来扫描给指定网络接口上的所有IP(比如eth0)。
在这个例子中,第二个client机器在tsung集群中,它被设定更高的权重(weight)和2个cpu。它用两个erlang虚拟机来充分利用cpu数量。
注意:即使现在erlang vm能处理多个cpu(erlang SMP),测试显示,对一个tsung客户端来说,一个cpu用一个vm更高效。因此你的cpu数最好和你的节点数相等。如果你还坚持用erlang SMP,在启动tsung加上-s选项,并且不要在配置文件中设定cpu个数。
默认情况下,负载被统一的分配到所有的cpu上(默认一个客户端一个cpu)。参数的权重(weight)可以用来衡量客户端机器的处理速度。
例如,有两台真实机器,一台比重为1,另一台为2,那么第二台机器会比第一台机器多启动一倍的用户(比重是1/3,2/3)。
在上面的例子中,第二台机器有2个cpu权重(weight)是3,这就相当于每个cpu的比重是1.5。
2.1、maxusers
maxusers参数被用来忽略单个进程对套接字打开数的限制(默认是1024)。当用户数高于这个限制后,就会启动一个新的vm来处理新的用户。
参数maxusers的默认值是800。现在的机器开启kernel poll功能后,你就可以对maxusers设定一个最高的值(如30000)而没有性能损失(但注意不要忘记用ulimit -n提高os的限制)。
注意:如果使用主从架构的tsung,master将分配session给slave。如果一个session包含多个请求,slave将按顺序执行每一个。
2.2、error_connect_emfile错误
这通常是由于你设置的maxuser值太大(在<client>),默认值是800。
这个错误意味着你当前运行的数量超过了file descriptors,必须检查maxusers保证小于操作系统设定的每个进程可打开的最大文件数(ulimit -m 查看)。
你可以提高操作系统的限制(Linux下/etc/security/limits.conf)或者减少tsung的maxusers,在同一个机器行启动多个虚拟机来均衡maxusers限制。
如果你想运行大量用户测试,一个好的方法是对操作系统配置进行修改:
增加打开文件的最大数量和自定义TCP设置:
1
2
3
4
5
|
vim /etc/sysctl.conf
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.ip_local_port_range = 1024 65000
fs.file-max = 65000
|
2.4、运行一个带有作业调度器的tsung
(不理解)
Tsung可以从一个batch/job 调度器获取客户端节点列表,目前处理PBS/torque, LSF 和 OAR。为此,设置type属性为batch,如:
1
|
<client type="batch" batch="torque" maxusers="30000">
|
如果你需要在batch调度器指定的节点上扫描IP别名,使用scan_intf,例如:
1
|
<client type="batch" batch="torque" scan_intf='eth0' maxusers="30000">
|
Tsung配置文件解析-Monitoring
Tsung可监控多个远程服务器,每个远程服务器都是由多个用远程agent与后台交互,在<monitor>标签中配置。可统计的数据有:cpu使用情况、系统负载和内存使用情况。
请注意,您可以从作业调度器得到监视节点,例如:
1
|
<monitor batch="true" host="torque" type="erlang"></monitor>
|
支持多种类型的远程代理(默认是erlang)。
一、erlang
erlang是默认值,使用它要保证监控的机器和server可以互相访问,并且装有erlang。
由Tsung启动远程代理,在server上使用erlang communications获取统计数据。
例如下面是一个使用erlang代理的监控cluster定义,cluster内有6台机器:
1
2
3
4
5
6
7
8
|
<monitoring>
<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>
</monitoring>
|
注意:被监控机必须在网络上可访问,并且erlang communications必须通过ssh无密码可访问。在节点上必须使用相同版本的 Erlang/OTP,否则可能出问题。
如果远程server上不能安装erlang,就需要考虑使用其他可用代理。
erlang监控还支持用mysqladmin监控mysql数据库的配置选项,使用方法如下:
1
2
3
|
<monitor host="db" type="erlang"></monitor>
<mysqladmin port="3306" username="root" password="sesame" />
</monitor>
|
可监控项:mysql客户端连接数、Questions(查询数)
二、SNMP
如果使用SNMP监控,type值用snmp代替erlang,也可以混合使用。
可以自定义SNMP版本、团体名和端口,它使用的管理信息库(MIB)在net-snmp提供。
1
2
3
4
5
6
7
|
<monitoring>
<monitor host="geronimo" type="snmp"/>
<monitor host="f14-2" type="erlang"></monitor>
<monitor host="db" type="snmp">
<snmp version="v2" community="mycommunity" port="11161"/>
</monitor>
</monitoring>
|
默认版本是v1,默认团体名是public,默认端口为161。
在1.4.2版本以后,你也可以自定义object identifiers (OID)从服务器获取数据,可以使用一个或多个oid标签:
1
2
3
4
5
6
|
<monitor host="127.0.0.1" type="snmp">
<snmp version="v2">
<oid value="1.3.6.1.4.1.42.2.145.3.163.1.1.2.11.0"
name="heapused" type="sample" eval="fun(X)-> X/100 end."/>
</snmp>
</monitor>
|
类型可以是sample, counter 或 sum,你可以定义一个函数(Erlang语法)被应用到value(属性eval)。
三、 Munin
Tsung可以从munin代理节点获取数据。type字段必须设置为munin,例如:
1
2
3
4
|
<monitoring>
<monitor host="geronimo" type="munin"/>
<monitor host="f14-2" type="erlang"></monitor>
</monitoring>
|
Tsung配置文件解析-Load
定义负载标签
一、随机生成用户
load标签定义了几个用户到达阶段
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<load>
<arrivalphase phase="1" duration="10" unit="minute">
<users interarrival="2" unit="second"></users>
</arrivalphase>
<arrivalphase phase="2" duration="10" unit="minute">
<users interarrival="1" unit="second"></users>
</arrivalphase>
<arrivalphase phase="3" duration="10" unit="minute">
<users interarrival="0.1" unit="second"></users>
</arrivalphase>
</load>
|
上面的配置文件表示:用户的生成分三个阶段,在第一个10分钟阶段每2秒钟生成1个新用户,在第二个10分钟阶段每秒钟生成1个用户,最后一个10分钟阶段每秒钟生成10个新用户。这些测试将在所有用户执行完它们的过程(后面的session标签代表的过程)之后,整个测试就结束了。
上面的第三阶段是每秒生成10个用户,但它字面上理解是每0.1秒生成一个用户,你还可以使用”arrivalrate”代替”interarrival”,例如:你需要每秒生成10个用户:
1
2
3
|
<arrivalphase phase="1" duration="10" unit="minute">
<users arrivalrate="10" unit="second"></users>
</arrivalphase>
|
可以使用maxnumber属性限制每阶段生成用户的最大数量,例如下面:
1
2
3
4
5
6
|
<arrivalphase phase="1" duration="10" unit="minute">
<users maxnumber="100" arrivalrate="10" unit="second"></users>
</arrivalphase>
<arrivalphase phase="2" duration="10" unit="minute">
<users maxnumber="200" arrivalrate="10" unit="second"></users>
</arrivalphase>
|
按照上面配置,第一阶段将只能生成100个用户,第二阶段200个用户。
还可以用load标签中用loop属性来让整个过程执行多次,如:loop=’2′的意思是这序列被循环两次,所以完整负载被执行三次。(这个要在版本1.2.2之后可用)。
负载每秒http的请求数依赖于每个session的request的个数,例如:每个session有100个request, 每秒生成10个新用户,那么理论的平均吞吐量是每秒1000个request。
还可以重写特定阶段中会话的概率设置,使用session_setup:
1
2
3
4
5
|
<arrivalphase phase="3" duration="1" unit="minute">
<session_setup name="http_test_1" probability="80"/>
<session_setup name="fake" probability="20"/>
<users interarrival="1" unit="second"/>
</arrivalphase>
|
二、静态生成用户
你想在测试的过程中在指定的时间上启动给定的session,在1.3.1版本之后可以实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<load>
<arrivalphase phase="1" duration="10" unit="minute">
<users interarrival="2" unit="second"></users>
</arrivalphase>
<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>
</load>
<sessions>
<session name="http-example" probability="0" type="ts_http">
<request> <http url="/" method="GET"></http> </request>
</session>
<session name="foobar" probability="0" type="ts_http">
<request> <http url="/bar" method="GET"></http> </request>
</session>
<session name="foo" probability="100" type="ts_http">
<request> <http url="/" method="GET"></http> </request>
</session>
<sessions>
|
在这个例子中,有2ession,一个的probability为“0”(因此在第一阶段不会被执行,就是随机生成用户部分), 而另一个是100。
在测试开始之后,我们设置3个用户分别启动,第一个在3分5秒(执行http-example session)启动,第二个在10分钟后启动(http-example session),最后一个在11分钟后启动(foo session)。
如果你想要同时启动多个sessions,并且这些sessions的名字前缀都一样,你可以使用通配符。
鉴于上面例子的sessions,这个例子我们将在10秒后开始2个用户(foo和foobar session)。
1
|
<user session="foo*" start_time="10" unit="second"/>
|
三、load test持续时间
默认情况下,tsung在所有用户都完成他们的session之后结束,因此这个时间可能会长于<arrivalphase>定义的持续时间。
如果你想要停止tsung而不管阶段是否完成,也不管是否有session正处于激活状态。那么你可以在load标签中增加duration属性(版本1.3.2后有效)。
1
2
3
4
5
|
<load duration="1" unit="hour">
<arrivalphase phase="1" duration="10" unit="minute">
<users interarrival="2" unit="second"></users>
</arrivalphase>
</load>
|
目前这个duration的值不能大于50天,unit可以设置为second,minut或者hour。
Tsung配置文件解析-Options
本篇开始介绍options配置
一、Thinktimes,SSL,Buffers
全局变量的默认值可以在这儿进行设定,比如:场景中两次请求间的思考时间(thinktime),ssl加密算法,tcp/udp缓存大小(默认是32KB)。如果override设置为true,这些值会把session配置文件中的对应值覆盖。
1
2
3
4
5
6
7
|
<option name="thinktime" value="3" random="false" override="true"/>
<option name="ssl_ciphers"
value="EXP1024-RC4-SHA,EDH-RSA-DES-CBC3-SHA"/>
<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>
|
可以禁用SSL session缓存(默认是启用的)
1
|
<option name="ssl_reuse_sessions" value="false"/>
|
你还可以使用命令行选项 -L 来改变session的缓存时间(默认是10分钟),value必须使用seconds。
二、TCP连接超时时间
你可以为一个创建TCP连接设置以毫秒为单位的超时时间,默认是无穷大:
1
|
<option name="connect_timeout" value="5000" />
|
还可以基于每个session更改超时时间,使用set_option:
1
|
<set_option name="connect_timeout" value="1000" />
|
也可以全局启用TCP REUSEADDR 选项:
1
|
<option name=”tcp_reuseaddr” value=”true” />
|
REUSEADDR解释:一般来说,一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用。
三、重试和超时
可以指定重试的总次数,默认是3:
1
|
<option name="max_retries" value="5" />
|
要关闭重试,可以将这个值设置为0。
此外,retry_timeout(毫秒;默认为10)可以简单的计算出总超时时间(retry* retry_timeout):
1
|
<set_option name="retry_timeout" value="1000" />
|
四、acknowledgments 消息超时时间
用来设置空闲(idle)超时时间(用在’parse’和’local’ ack)和global ack超时时间(用在’global’ ack)。默认空闲(idle)超时时间是10分钟(600000),global ack 超时时间是无限大。可以这样更改设置:
1
2
|
<option name="idle_timeout" value="300000"></option>
<option name="global_ack_timeout" value="6000000"></option>
|
五、Hibernate
Hibernate选项是用来减少模拟用户在thinktimes期间的内存消耗。
默认情况下,当thinktimes超过10s之后hibernate就会被激活,这个可以进行修改,如:
1
|
<option name="hibernate" value="5"></option>
|
要禁用hibernation,必须设置此值为infinity。
六、Rate_limit
rate_limit会限制每个客户端的带宽(使用令牌桶算法token bucket algorithm),单位为KBytes/s。
可以设置一个最大值(比如max=’2048),默认情况下最大值等同于rate_limit(下面例子是1024KB)。
注意:当前只能限制进入的流量的速率。
1
|
<option name="rate_limit" value="1024"></option>
|
七、Ports_range
如果你需要在同一个客户端机器上打开超过30000个并发连接,client的TCP端口数将会限制你,尽管你有多个IPs(存在于Linux OS)。
为了绕开这个限制,Tsung不能为每个客户端指派被选中的client端口和同时使用多IP,你必须定义一个client可用端口数范围,如下:
1
|
<option name="ports_range" min="1025" max="65535"/>
|
八、设置seed随机数生成器
默认Tsung使用当前时间来生成seed,因此每次测试使用的随机数量是不一样的。如果你想使用一个固定seed数量的随机数生成器,可用设置seed选项,如下:
1
|
<option name="seed" value="42"/>
|
九、BOSH路径
可用使用下面配置项来设置BOSH请求的路径:
1
|
<option name="bosh_path" value="/http-bind/"/>
|
BOSH即Bidirectional-streams Over Synchronous HTTP,是一种传输协议。它可以利用同步的HTTP协议模拟两个实体(例如客户端-服务端)双向流传输,而不需要轮询或异步组件。到目前为止,BOSH主要用于Jabber/XMPP客户端-服务器之间的数据传输(如web端和手机客户端之间的通讯)。
十、Websocket选项
使用Websocket类型服务的时候,你可以设置Websocker的如下选项:
1
2
3
4
|
<option name="websocket_path" value="/chat"/>
<!-- send websocket data with text frame, default binary-->
<option name="websocket_frame" value="text"/>
|
使用websocket_path设置Websocket请求的路径,使用websocket_frame设置发送websocket数据的frame类型(类型选项:binary和text,默认为binary)。
十一、XMPP/Jabber选项
协议特性的默认值可以自定义,下面是一个XMPP/Jabber选项值的例子:
1
2
3
4
5
6
|
<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="jabber.org" />
<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"/>
|
使用上面设置,用户将为myuserxxx格式,xxx是[1:userid_max]区间内的一个整数,密码为mypasswdxxx格式。
如果没有设置这个配置文件,这些值将默认为:
- global_number = 100
- userid_max = 10000
- domain = erlang-projects.org
- username = tsunguser
- passwd = sesame
如果你想用一个CSV文件的用户名密码,需要配置其他选项,下一篇session将会介绍。
十二、HTTP选项
对于HTTP,可以配置UserAgent,为每个值设置自己的probability,probability总数必须为100:
1
2
3
4
5
6
7
8
|
<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>
<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
</user_agent>
</option>
|
十三、AMQP 选项
可以设置AMQP心跳超时时间,下面例子设为30s(默认600s),添加:
1
|
<option type="ts_amqp" name="heartbeat" value="30" />
|
AMQP是一个提供统一消息服务的应用层标准协议,基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同开发语言等条件的限制。
Tsung配置文件解析-Sessions
Sessions定义了场景本身,它描述了要运行的请求。
每个session都有一个指定的probablity属性,这个被用来决定一个新用户要运行哪个session,所有session的probability属性和为100。
也可以用权重(weight)来代替probability,下面例子中,session s1是s2的两倍。
1
2
|
<session name="s1" weight="2" type="ts_http">
<session name="s2" weight="1" type="ts_http">
|
一个事务是一种手工统计数据的一种方式。比如说你想知道你网站登录页面的反应时间,你就需要把这个页面的所有请求(html和内嵌的图片等)放到这个事务里面。在下面HTTP的例子中你就会看到,名为index_request的事务统计数据和报告。
需要注意的是,如果你的事务中包含thinktime,那thinktime也是响应时间的一部分。
一、Thinktimes
思考时间理解:
用户访问某个网站或软件,一般不会不停地做个各种操作,例如一次查询,用户需要时间查看查询的结果是否是自己想要的。例如一次订单提交,用户需要时间核对自己填写的信息是否正确等。
也就是说用户在做某些操作时,是会有停留时间的,我把这个时间叫思考时间。但利用代码去执行的时候是没有时间的,当然,脚本运行本身是需要时间的,但比起人的思考时间要小很多。这也是我们为什么要用软件来代替人的某些工作。
但有时候,我们在做性能测试时,为了更真实的模拟用户的操作,需要给代码加入思考时间
可以设置固定或者随机的思考时间来分离请求,默认情况下,随机思考时间是以value为基准的指数分布:
1
|
<thinktime value="20" random="true"></thinktime>
|
这儿思考时间就是以20为基准的指数分布。
1.3.0版本之后,还增加了一个范围的控制,可以设定一个最大最小值,随机时间就在最大最小值之间波动:
1
|
<thinktime min="2" max="10" random="true"></thinktime>
|
1.4.0版本后,可以使用动态变量来设置thinktime值(不会用):
1
|
<thinktime value="%%_rndthink%%" random="true"></thinktime>
|
还可以同步所有用户使用wait_global值(不会用):
1
|
<thinktime value='wait_global'>
|
解释:等待所有用户(或指定N用户)连接并等待全局锁(value可以在option中设置
1
|
<thinktime value="wait_bidi"></thinktime>
|
二、HTTP
下面这个例子展示了http协议支持Tsung的多种特性:get和post请求、基本的认证、事务、数据统计定义、状态请求。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
<sessions>
<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">
</http></request>
<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>
</transaction>
<thinktime value="60" random="true"></thinktime>
<request>
<http url="/" method="POST" version="1.1"
contents="bla=blu">
</http> </request>
<request>
<http url="/bla" method="POST" version="1.1"
contents="bla=blu&name=glop">
<www_authenticate userid="Aladdin"
passwd="open sesame"/></http>
</request>
</session>
<session name="backoffice" probability="30" ...>
...
</session>
</sessions>
|
If-Modified-Since作用:If-Modified-Since是标准的HTTP请求头标签,在发送HTTP请求时,把浏览器端缓存页面的最后修改时间一起发到服务器去,服务器会把这个时间与服务器上实际文件的最后修改时间进行比较。
- 如果时间一致,那么返回HTTP状态码304(不返回文件内容),客户端接到之后,就直接把本地缓存文件显示到浏览器中。
- 如果时间不一致,就返回HTTP状态码200和新的文件内容,客户端接到之后,会丢弃旧文件,把新文件缓存起来,并显示到浏览器中。
如果使用绝对路径地址,该网址中使用的server将覆盖<server>部分中指定的server。在session中后面的相关请求也将使用这个新的server值(直到一个新的绝对的网址被设置)。
在1.2.2版本之后,你还可以像下面这样增加任何的http头:
1
2
3
4
5
6
7
|
<request>
<http url="/bla" method="POST" contents="bla=blu&name=glop">
<www_authenticate userid="Aladdin" passwd="open sesame"/>
<http_header name="Cache-Control" value="no-cache"/>
<http_header name="Referer" value="http://www.w3.org/"/>
</http>
</request>
|
在版本1.3.0你可以从外部文件中读取post和put的内容:
1
|
<http url="mypage" method="POST" contents_from_file="/tmp/myfile" />
|
在版本1.3.1之后,你还可以手动的设定一个cookie,由于cookie不是可持久的,所以你要在每个请求中<request>增加它的代码:
1
2
3
4
|
<http url="/">
<add_cookie key="foo" value="bar"/>
<add_cookie key="id" value="123"/>
</http>
|
2.1、Authentication
1.5.0版本之前只支持基本认证,现在可以使用Digest认证和OAuth 1.0认证。
使用Digest认证:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<!-- 1. First request return 401. We use dynvars to fetch nonce and realm -->
<request>
<dyn_variable name="nonce" header="www-authenticate/nonce"/>
<dyn_variable name="realm" header="www-authenticate/realm"/>
<http url="/digest" method="GET" version="1.1"/>
</request>
<!--
2. This request will be authenticated. Type="digest" is important.
We use the nonce and realm values returned from the previous
If the webserver returns the nextnonce we set it to the nonce dynvar
for use with the next request.
Else it stays set to the old value
-->
<request subst="true">
<dyn_variable name="nonce" header="authentication-info/nextnonce"/>
<http url="/digest" method="GET" version="1.1">
<www_authenticate userid="user" passwd="passwd" type="digest" realm="%%_realm%%" nonce="%%_nonce%%"/>
</http>
</request>
|
使用OAuth认证:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
<!-- Getting a Request Token -->
<request>
<dyn_variable name="access_token" re="oauth_token=([^&]*)"/>
<dyn_variable name="access_token_secret" re="oauth_token_secret=([^&]*)" />
<http url="/oauth/example/request_token.php" method="POST" version="1.1" contents="empty">
<oauth consumer_key="key" consumer_secret="secret" method="HMAC-SHA1"/>
</http>
</request>
<!-- Getting an Access Token -->
<request subst='true'>
<dyn_variable name="access_token" re="oauth_token=([^&]*)"/>
<dyn_variable name="access_token_secret" re="oauth_token_secret=([^&]*)"/>
<http url="/oauth/example/access_token.php" method="POST" version="1.1" contents="empty">
<oauth consumer_key="key" consumer_secret="secret" method="HMAC-SHA1" access_token="%%_access_token%%" access_token_secret="%%_access_token_secret%%"/>
</http>
</request>
<!-- Making Authenticated Calls -->
<request subst="true">
<http url="/oauth/example/echo_api.php" method="GET" version="1.1">
<oauth consumer_key="key" consumer_secret="secret" access_token="%%_access_token%%" access_token_secret="%%_access_token_secret%%"/>
</http>
</request>
|
三、Jabber/XMPP
这里是一个Jabber/XMPP协议的session定义实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
<sessions>
<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>
</transaction>
<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>
</transaction>
<thinktime value="30"></thinktime>
<transaction name="offline">
<request> <jabber type="chat" ack="no_ack" size="56" destination="offline"/><request>
</transaction>
<thinktime value="30"></thinktime>
<transaction name="close">
<request> <jabber type="close" ack="local"> </jabber></request>
</transaction>
</session>
</sessions>
|
3.1、消息邮戳
(不理解未验证)
通过配置<jabber>标签的stamped属性来设置聊天消息邮戳确保请求是正确的。
邮戳将包含发送节点的当前时间戳和ID。如果接受者准备识别节点ID,它将比较现在和消息里的时间戳,差异将以xmpp_msg_latency(毫秒)被报告。节点ID比较的目的是为了避免在不同Tsung节点的时间戳轻微的不一致。
只有一小部分的请求将达到相同的节点,但随着请求率足够高,这部分应该是够的。
stamped仅允许size属性,data将会导致stamped被忽略。stamp的最小长度大约是30bytes,如果size大于stamp长度,将随机生成一些填满stamp。如果stamp长度大于size,只有将stamp作为消息内容,保证超出的指定长度是有效的。
3.2、StartTLS
(不理解未验证)
使用STARTTLS来保证stream安全,使用:
1
|
<jabber type="starttls" ack="bidi_ack" />
|
1.5.1版本后可以使用客户端证书,例如,你可以使用动态变量,像这样:
1
2
3
4
|
<jabber type="starttls" ack="bidi_ack"
cacertfile="%%_cacert%%"
certfile="%%_certfile%%"
keyfile="%%_keyfile%%" />
|
注:STARTTLS 是对纯文本通信协议的扩展。它提供一种方式将纯文本连接升级为加密连接(TLS或SSL),而不是另外使用一个端口作加密通信。
3.3、Roster
(不理解未验证)
Tsung对Roster的支持:
- 添加新的联系人的到名单-新联系人添加到Tsung Group组,名字对应他们的JID。
- 发送订阅消息到新建联系人的JID。
- 重命名联系人。
- 删除以前的联系人。
新建一个联系人时,JID将被存储和用于后续操作。建议配置执行这些操作的每个session只做一次。换句话说,你不想为每个session添加多于一个联系人。在测试用如果你想改变rose函数的使用频率,最好使用session的probability来实现。
很棒的一点是当测试跑完时,名单表看起来应该和测试前是相同的。因此,如果您正确设置了它,您可以在测试之前加载需要的名单条目,然后使用这些方法来动态添加、修改和删除在测试过程中的名单条目。
示例,名单变更步骤:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
<session probability="100" name="jabber-rostermod" type="ts_jabber">
<!-- connect, authenticate, roster 'get', etc... -->
<transaction name="rosteradd">
<request>
<jabber type="iq:roster:add" ack="no_ack" destination="online"></jabber>
</request>
<request>
<jabber type="presence:subscribe" ack="no_ack"/>
</request>
</transaction>
<!-- ... -->
<transaction name="rosterrename">
<request> <jabber type="iq:roster:rename" ack="no_ack"></jabber> </request>
</transaction>
<!-- ... -->
<transaction name="rosterdelete">
<request> <jabber type="iq:roster:remove" ack="no_ack"></jabber> </request>
</transaction>
<!-- remainder of session... -->
</session>
|
3.4、SASL Plain
(不理解未验证)
SASL Plain认证例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<session probability="100" name="sasl" type="ts_jabber">
<request> <jabber type="connect" ack="local"></jabber> </request>
<thinktime value="10"></thinktime>
<transaction name="authenticate">
<request>
<jabber type="auth_sasl" ack="local"></jabber></request>
<request>
<jabber type="connect" ack="local"></jabber> </request>
<request>
<jabber type="auth_sasl_bind" ack="local" ></jabber></request>
<request>
<jabber type="auth_sasl_session" ack="local" ></jabber></request>
</transaction>
|
3.5、anonymous SASL
(不理解未验证)
anonymous SASL认证例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<session probability="100" name="sasl" type="ts_jabber">
<request> <jabber type="connect" ack="local"></jabber> </request>
<thinktime value="10"></thinktime>
<transaction name="authenticate">
<request>
<jabber type="auth_sasl_anonymous" ack="local"></jabber></request>
<request>
<jabber type="connect" ack="local"></jabber> </request>
<request>
<jabber type="auth_sasl_bind" ack="local" ></jabber></request>
<request>
<jabber type="auth_sasl_session" ack="local" ></jabber></request>
</transaction>
|
7.3.6、Presence
type:可设置为presence:broadcast 或 presence:directed。
show:可设置为away, chat, dnd, 或 xa。
status:可以是任何文本。
如果你省略了show和status属性,则默认为chat和Available。
广播发送例子(广播到你的Rose(名单)用户):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
<request>
<jabber type="presence:broadcast" show="away" status="Be right back..." ack="no_ack"/>
</request>
<thinktime value="5"></thinktime>
<request>
<jabber type="presence:broadcast" show="chat" status="Available
to chat" ack="no_ack"/>
</request>
<thinktime value="5"></thinktime>
<request>
<jabber type="presence:broadcast" show="dnd" status="Don't bother me!" ack="no_ack"/>
</request>
<thinktime value="5"></thinktime>
<request>
<jabber type="presence:broadcast" show="xa" status="I may never come back..."
ack="no_ack"/>
</request>
<thinktime value="5"></thinktime>
<request> <jabber type="presence:broadcast" ack="no_ack"/> </request>
<thinktime value="5"></thinktime>
|
单独发送例子(随机发送到在线用户):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
<request>
<jabber type="presence:directed" show="away" status="Be right back..." ack="no_ack"/>
</request>
<thinktime value="5"></thinktime>
<request>
<jabber type="presence:directed" show="chat" status="Available to chat" ack="no_ack"/>
</request>
<thinktime value="5"></thinktime>
<request>
<jabber type="presence:directed" show="dnd" status="Don't bother me!" ack="no_ack"/>
</request>
<thinktime value="5"></thinktime>
<request>
<jabber type="presence:directed" show="xa" status="I may never come back..."
ack="no_ack"/>
</request>
<thinktime value="5"></thinktime>
<request>
<jabber type="presence:directed" ack="no_ack"/>
</request>
<thinktime value="5"></thinktime>
|
3.7、MUC
Tsung支持4种MUC操作:
- 加入房间(属性type=’muc:join’)
- 发送一个消息到房间(属性type=’muc:chat’)
- 修改昵称(属性type=’muc:nick’)
- 退出房间(属性 type=’muc:exit’)
下面是个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
<-- First, choose an random room and random nickname: -->
<setdynvars sourcetype="random_number" start="1" end="100">
<var name="room"/>
</setdynvars>
<setdynvars sourcetype="random_string" length="10">
<var name="nick1"/>
</setdynvars>
<request subst="true">
<jabber type='muc:join' ack="local" room="room%%_room%%" nick="%%_nick1%%"/>
</request>
<!-- 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="room%%_room%%"/>
</request>
</for>
<!-- change nickname-->
<thinktime value="2"/>
<setdynvars sourcetype="random_string" length="10">
<var name="nick2"/>
</setdynvars>
<request subst="true">
<jabber type="muc:nick" room="room%%_room%%" nick="%%_nick2%%"
ack="no_ack"/>
</request>
|
3.8、PubSub
略
3.9、VHost
1.3.2版本后就开始支持VHost了。
Tsung可以在连接的时候从列表中选择一个vhost XMPP名称从而设置多个vhost实例。
vhost列表是从外部文件读取的:
1
2
3
4
5
6
7
|
<options>
...
<option name="file_server" value="domains.csv" id="vhostfileId"></option>
...
<option type="ts_jabber" name="vhost_file" value="vhostfileId"></option>
...
</options>
|
当每个client启动session时,它们随机选择一个domain(每个domain都有相同的probability)。
3.10、从CSV文件读取用户名和密码
1.4.0版本后就可以从csv文件读取用户名和密码了。
配置CSV文件:
1
2
3
|
<options>
<option name="file_server" id='userdb' value="/home/foo/.tsung/users.csv"/>
</options>
|
接下来你需要定义两个类型为file的变量,第一个jabber 请求(connect)必须包含该一个xmpp_authenticate标签:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
<session probability="100" name="jabber-example" type="ts_jabber">
<setdynvars sourcetype="file" fileid="userdb" delimiter=";" order="iter">
<var name="username" />
<var name="password" />
</setdynvars>
<request subst='true'>
<jabber type="connect" ack="no_ack">
<xmpp_authenticate username="%%_username%%" passwd="%%_password%%"/>
</jabber>
</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>
</transaction>
...
</session>
|
此外,当发送聊天信息到随机或离线用户时,你需要通过配置userid_max为0和设置fileid为离线或随机用户(也用户Pubsub)来禁用默认用户(不是CSV的):
1
2
3
4
5
6
|
<options>
<option type="ts_jabber" name="userid_max" value="0" />
<option type="ts_jabber" name="random_from_fileid" value='userdb'/>
<option type="ts_jabber" name="offline_from_fileid" value='userdb'/>
<option type="ts_jabber" name="fileid_delimiter" value=";"/>
</options>
|
用户名(密码)应该在每行CSV的第一格(第二格)分隔符是”;”,可以修改。
3.11、raw XML
可以使用类型为raw来发送raw XML数据到服务器。
1
|
<jabber type="raw" ack="no_ack" data="<stream>foo</stream>"></jabber>
|
当心:你必须对XML字符进行编码,如<,>,等待。
3.12、resource
默认情况下,XMPP资源设置为tsung。自从版本1.5.0,可以重写这个(在所有auth_ *和注册请求)使用的资源属性。
四、PostgreSQL
对于PostgreSQL,4种请求类型是可用的:
- connect(提供用户名)
- authenticate (提供密码或不提供)
- sql(基本协议)
- close
此外,下列额外部分协议也是支持的:
- copy
- parse, bind, execute, describe
- sync, flush
下面例子展示了postgresql session的多个特征:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
<session probability="100" name="pgsql-example" type="ts_pgsql">
<transaction name="connection">
<request>
<pgsql type="connect" database="bench" username="bench" />
</request>
</transaction>
<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>
</session>
|
下面是额外协议的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
<request><pgsql type='parse' name_prepared='P0_7'><![CDATA[BEGIN;]]></pgsql></request>
<request><pgsql type='sync'/></request>
<request><pgsql type='parse' name_prepared='P0_8'><![CDATA[UPDATE pgbench_accounts
SET abalance = abalance + $1 WHERE aid = $2;]]></pgsql></request>
<request><pgsql type='sync'/></request>
<request><pgsql type='parse' name_prepared='P0_9'><![CDATA[SELECT
abalance FROM pgbench_accounts
WHERE aid = $1;]]></pgsql></request>
<request><pgsql type='sync'/></request>
<request><pgsql type='parse' name_prepared='P0_10'><![CDATA[UPDATE pgbench_tellers
SET tbalance = tbalance + $1 WHERE tid = $2;]]></pgsql></request>
<request><pgsql type='sync'/></request>
<request><pgsql type='parse' name_prepared='P0_11'><![CDATA[UPDATE pgbench_branches
SET bbalance = bbalance + $1 WHERE bid = $2;]]></pgsql></request>
<request><pgsql type='sync'/></request>
<request><pgsql type='parse' name_prepared='P0_12'><![CDATA[INSERT
INTO pgbench_history (tid, bid, aid, delta, mtime)
VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP);]]></pgsql></request>
<request><pgsql type='sync'/></request>
<request><pgsql type='parse' name_prepared='P0_13'><![CDATA[END;]]></pgsql></request>
<request><pgsql type='sync'/></request>
<request><pgsql type='bind' name_prepared='P0_7' formats='none' formats_results='text' /></request>
<request><pgsql type='describe' name_portal=''/></request>
<request><pgsql type='execute'/></request>
<request><pgsql type='sync'/></request>
<request><pgsql type='bind' name_portal='' name_prepared='P0_8'
formats='none' formats_results='text'
parameters='2924,37801'/></request>
|
五、MysQL
对于mysql,4种类型的请求是支持的:
- connect(提供用户名)
- authenticate (提供密码或不提供)
- sql
- close
下面例子展示了mysql相关的sessions:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<session probability="100" name="mysql-example" type="ts_mysql">
<request>
<mysql type="connect" />
</request>
<request>
<mysql type="authenticate" database="test" username="test" password="test" />
</request>
<request>
<mysql type="sql">SHOW TABLES</mysql>
</request>
<request>
<mysql type="sql">SELECT * FROM mytable</mysql>
</request>
<request>
<mysql type="close" />
</request>
</session>
|
六、Websocket
对于Websocket,3种类型的请求是支持的:
- connect (提供path)
- message (发送消息到服务器,添加一个’ack’属性来指定是否Tsung应该等待响应)
- close
Websocke session类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<session probability="100" name="websocket-example" type="ts_websocket">
<request subst="true">
<websocket type="connect" path="/path/to/ws"></websocket>
</request>
<request>
<dyn_variable name="uid" jsonpath="uid"/>
<!-- send data with text frame, default binary-->
<websocket type="message" frame="text">{"user":"user", "password":"password"}</websocket>
</request>
<request subst="true">
<match do="log" when="nomatch">ok</match>
<websocket type="message">{"uid":"%%_uid%%", "data":"data"}</websocket>
</request>
<request>
<websocket type="message" ack="no_ack">{"key":"value"}</websocket>
</request>
<request>
<websocket type="close"></websocket>
</request>
</session>
|
你可以替换path属性和消息内容,使匹配响应或基于消息响应定义动态变量。
如果你使用change_type来启动一个websocket,不要忘了设置bidi=”true”,例如:
1
|
<change_type new_type="ts_websocket" host="127.0.0.1" port="8080" server_type="tcp" restore="true" store="true" bidi="true"/>
|
七、AMQP
对于AMQP,支持在多个信道上发送和消耗消息,可用请求类型:
- connection.open (given vhost)
- connection.close
- channel.open (一个指定和有效的channel id)
- channel.close (一个指定和有效的channel id)
- confirm.select (一个指定和已经打开的channel)
- basic.qos (在一个指定和已经打开的channel, 只支持‘prefetch_count’属性)
- basic.publish (with channel id, exchange name, routing_key and the payload)
- basic.consume (with channel id, queue name)
- waitForConfirms (从服务器确认超时)
- waitForMessages (向client确认消息交付超时)
AMQP session类型的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
<session probability="100" name="amqp-example" type="ts_amqp" bidi="true">
<request>
<amqp type="connection.open" vhost="/"></amqp>
</request>
<!-- open channel, channel id is from 1 to 10 -->
<for from="1" to="10" incr="1" var="loops">
<request>
<amqp type="channel.open"></amqp>
</request>
</for>
<!-- ignore this request if you don't need publisher confirm -->
<for from="1" to="10" incr="1" var="loops">
<request subst="true">
<amqp type="confirm.select" channel="%%_loops%%"></amqp>
</request>
</for>
<for from="1" to="10" incr="1" var="loops">
<for from="1" to="100" incr="1" var="counter">
<transaction name="publish">
<!-- specify payload_size to have tsung generate a payload for you -->
<request subst="true">
<amqp type="basic.publish" channel="%%_loops%%" exchange="test_exchange"
routing_key="test_queue" persistent="true" payload_size="100"></amqp>
</request>
<!-- substitutions are supported on the payload. Payload will override payload_size. -->
<request subst="true">
<amqp type="basic.publish" channel="%%_loops%%" exchange="test_exchange"
routing_key="test_queue" persistent="true" payload="Test Payload"></amqp>
</request>
</transaction>
</for>
<!-- if publish with confirm, add a waitForConfirms request as you need: timeout=1s -->
<request>
<amqp type="waitForConfirms" timeout="1"></amqp>
</request>
</for>
<for from="1" to="10" incr="1" var="loops">
<request subst="true">
<amqp type="basic.consume" channel="%%_loops%%" queue="test_queue" ack="true"></amqp>
</request>
</for>
<!-- wait to receive messages from the server: timeout=180s -->
<request>
<amqp type="waitForMessages" timeout="180"></amqp>
</request>
<for from="1" to="10" incr="1" var="loops">
<request subst="true">
<amqp type="channel.close" channel="%%_loops%%"></amqp>
</request>
</for>
<request>
<amqp type="connection.close"></amqp>
</request>
</session>
|
八、MQTT
支持发布消息,订阅和取消订阅话题,可用请求类型:
- connect (with options like clean_start, will_topic etc)
- disconnect
- publish (with topic name, qos level and retain flag)
- subscribe (with topic name and qos level)
- unsubscribe (with topic name)
- waitForMessages (with timeout for messages published from server to the client)
MQTT session类型的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
<session name="mqtt-example" probability="100" type="ts_mqtt">
<request>
<mqtt type="connect" clean_start="true" keepalive="10" will_topic="will_topic" will_qos="0" will_msg="will_msg" will_retain="false"></mqtt>
</request>
<for from="1" to="10" incr="1" var="loops">
<request subst="true">
<mqtt type="publish" topic="test_topic" qos="1" retained="true">test_message</mqtt>
</request>
</for>
<request subst="true">
<mqtt type="subscribe" topic="test_topic" qos="1"></mqtt>
</request>
<request>
<!-- wait for 60s -->
<mqtt type="waitForMessages" timeout="60"></mqtt>
</request>
<request subst="true">
<mqtt type="unsubscribe" topic="test_topic"></mqtt>
</request>
<request>
<mqtt type="disconnect"></mqtt>
</request>
</session>
|
注:MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,有可能成为物联网的重要组成部分。
九、LDAP
9.1、Authentication
推荐机制用于LDAP库对用户进行身份验证,需要下面两个步骤。给定一个用户名和密码,然后:
- 在库的树结构对用户搜索,使用用户名(用户可以驻留在不同组织的子树)
- 尝试绑定为用户,使用在第一步中发现的名称和用户的密码
如果绑定成功,此用户就验证通过(这是使用的方案,其中,LDAP认证模块由Apache支持)。
9.2、LDAP Setup
对于这个例子,我们将使用下面的层次结构来使用一个简单的存储库。
LDAP层次结构:
库中有两个组织单位的用户
- users(4个成员)
- users2(3个成员)
简单起见,我们为每个用户设置和名称一样的密码,在测试中Tsung setup我们将使用CSV文件作为输入,包含user:password对。因此我们从编写这个开始,在这种情况下我们对文件命名users.csv:
1
2
3
4
5
6
7
8
|
user1;user1
user2;user2
user3;user3
user4;user4
jane;jane
mary;mary
paul;pablo
paul;paul
|
这对用户名密码对paul:pablo应该验证失败,我们将在Tsung报告中看到。现在在Tsung配置单元,我们先引人这个文件:
1
2
3
4
5
6
7
8
|
<options>
<option name="file_server" id="users" value="users.csv"/>
</options>
<!-- 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" />
</setdynvars>
|
启动认证过程,我们通知Tsung执行搜索,找到我们试图验证的用户的可分辨名称:
1
2
|
<ldap type="search" base="dc=pablo-desktop" filter="(cn=%%_username%%)"
result_var="search_result" scope="wholeSubtree"></ldap>
|
当需要访问搜索结果的时候,我们定义它使用result_var属性,这个属性告诉Tsung哪些变量我们想要存储(如果result_var属性没有被设置,Tsung不能再任何地方存储搜索结果)。最后我们试图绑定这个用户:
1
2
3
4
|
<request subst="true">
<ldap type="bind" user="%%ldap_auth:user_dn%%"
password="%%_password%%"></ldap>
</request>
|
唯一剩下要做的是实现ldap_auth:user_dn功能,从搜索结果提取的可分辨名称:
1
2
3
4
5
6
|
-module(ldap_auth).
-export([user_dn/1]).
user_dn({_Pid,DynVars}) ->
[SearchResultEntry] = proplists:get_value(search_result,DynVars),
{_,DN,_} = SearchResultEntry,
DN.
|
我们不在这里覆盖错误。假设总有一个(只有一个)用户被发现,这是我们从search_result变量提取的(在前面搜索操作定义的)。在结果集中的每个条目是一个searchresultentry记录。记录定义可以在 /include/ELDAPv3.hrl 中找到。
事实上,test运行后我们可以在Tsung报告里确认(见LDAP结果图),绑定操作包含两个计时器:ldap_bind_ok和ldap_bind_error,成功数量和不成功的尝试。
LDAP结果:
9.3、其他示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
<session probability="100" name="ldap-example" type="ts_ldap">
<request>
<ldap type="bind" user="uid=foo" password="bar"/>
</request>
<request>
<ldap type="search" base="dc=pablo-desktop" filter="(cn=user2)"
scope="wholeSubtree"></ldap>
</request>
<!-- Add. Adds a new entry to the directory* -->
<request subst="true">
<ldap type="add" dn="%%_new_user_dn%%" >
<attr type="objectClass">
<value>organizationalPerson</value>
<value>inetOrgPerson</value>
<value>person</value>
</attr>
<attr type="cn"><value>%%_new_user_cn%%</value></attr>
<attr type="sn"><value>fffs</value></attr>
</ldap>
</request>
<!-- 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>some@mail.com</value></attr>
</modification>
</ldap>
</request>
</session>
|
十、 混合session type
1.3.2版本后,一个新标记change_type用在session中来改变它的类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<request>
<jabber type="chat" ack="no_ack" size="16"
destination="offline"/>
</request>
<thinktime value="3"/>
<change_type new_type="ts_http" host="foo.bar" port="80"
server_type="tcp" store="true"/>
<request> <http url="http://foo.bar/"/> </request>
<request> <http url="/favicon"/> </request>
<change_type new_type="ts_jabber" host="localhost" port="5222"
server_type="tcp" restore="true"/>
<request> <jabber type="chat" ack="no_ack" size="16"
destination="previous"/> </request>
|
store=”true”可用来存储session当前的状态(socket, cookies for http等),而restore=”true”是切换到旧的协议,重新使用以前的状态。
可以使用bidi=”true”表示新协议是双向的,或者bidi=”false”表示非双向协议。
一个动态变量设置在session的第一部分,可以在<change_type>后仍然可用。这是目前一个警告:在
警告:在<change_type>后你的第一个HTTP请求必须使用一个完整的URL。
十一、Raw
ts_raw插件允许在没有任何相关协议标识的情况下,发送数据到任何类型的TCP/UDP服务器。你可以通过data属性设置数据,也可以通过data_size属性设置数据大小(这种情况下,发送的数据大小为0)。data和 datasize都可以是动态值。
从服务器控制响应的唯一方法是使用ack属性(也可用Jabber插件):
- ack=”local” 一旦从服务器接收到一个数据包,该请求被认为是完成的。因此,如果你使用一个local ack的请求,服务器就不需要响应,它将永远等待(或直到到达超时)。
- ack=”no_ack” 一旦请求发送,它就被认为是完成(不要等待传入的数据)。
- ack=”global” 用户同步。它的主要用途是在发送消息前等待所有用户连接。要做到这一点,建立一个global ack类型的请求(这个值可以使用option设置
1
2
3
4
5
6
7
8
9
10
11
12
|
<session probability="100" name="raw" type="ts_raw">
<transaction name="open">
<request> <raw data="HELLO" ack="local"></raw> </request>
</transaction>
<thinktime value="4"/>
<request> <raw datasize="2048" ack="local"></raw> </request>
<transaction name="bye">
<request> <raw data="BYEBYE" ack="local"></raw> </request>
</transaction>
</session>
|
Tsung配置文件解析-高级配置
一、动态替换
动态替换标记被放置在场景元素中,对于HTTP,这种标记可以放在基本认证(www_authenticate标签:userid 和passwd 属性),URL(改变GET参数)和POST内容中。
这些标记的格式为 %%Module:Function%% ,只有request标签有 subst=”true” 属性时,替换才是针对每个请求执行的。
当请求替换时,替换标记被调用Erlang函数:Module:Function({Pid, DynData}) 的结果取代,前面PID是当前虚拟用户的Erlang进程ID并且DynData是所有动态变量的列表。
这个是Tsung中使用替换的例子:
1
2
3
4
5
|
<session name="rec20040316-08:47" probability="100" type="ts_http">
<request subst="true">
<http url="/echo?symbol=%%symbol:new%%" method="GET"></http>
</request>
</session>
|
1.5.1版本后对于http插件,你可以使用特殊的值subst=’all_except_body’ 代替 ‘true’ 在HTTP响应的正文部分跳过替换。
这里是使用动态替换的Erlang模块代码:
1
2
3
4
5
6
7
8
9
|
-module(symbol).
-export([new/1]).
new({Pid, DynData}) ->
case random:uniform(3) of
1 -> "IBM";
2 -> "MSFT";
3 -> "RHAT"
end.
|
使用erlc编译这段代码并得到结果文件,把文件放到所有客户端机器的$PREFIX/lib/erlang/lib/tsung-X.X.X/ebin/目录。
正如你所看到的那样,在scenario里编写动态替换是很简单的。它还可以更简单,使用动态变量(见下文)。
如果你想设置唯一的ID,你可以使用内置函数ts_user_server:get_unique_id。
1
2
3
4
5
|
<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" />
</request>
</session>
|
二、外部文件读取
1.0.3版本后,出现一个新的可用模块ts_file_server,可以使用它来读取外部文件。
例如,如果你需要从一个CSV文件读取用户名和密码,就可以使用它来完成(目前只能读取一个文件)。
1
|
<option name="file_server" value="/tmp/userlist.csv"></option>
|
1.2.2版本后你可以读取多个文件,使用id属性来辨识每一个文件:
1
2
|
<option name="file_server" value="/tmp/userlist.csv"></option>
<option name="file_server" id='random' value="/tmp/randomnumbers.csv"></option>
|
现在可以构建自己的函数来使用它,例如创建一个名为readcsv.erl的文件:
1
2
3
4
5
6
7
|
-module(readcsv).
-export([user/1]).
user({Pid,DynVar})->
{ok,Line} = ts_file_server:get_next_line(),
[Username, Passwd] = string:tokens(Line,";"),
"username=" ++ Username ++"&password=" ++ Passwd.
|
此函数的输出将是一个字符串username=USER&password=PASSWORD。
使用erlc readcsv.erl 命令来编译此函数,然后把readcsv.beam放到$prefix/lib/erlang/lib/tsung-VERSION/ebin目录(如果这个文件有ID并且是随机的,改变上面代码ts_file_server:get_next_line(random))。
然后在你的session中这样设置:
1
2
3
|
<request subst="true">
</http>
</request>
|
两个函数是可用的:ts_file_server:get_next_line 和 ts_file_server:get_random_line。对于ts_file_server:get_next_line函数,当到达文件末尾的时候,下一行就是文件的第一行。
1.3.0版本后你不需要创建一个额外函数来解析CSV文件,可以使用setdynvars:
1
2
3
4
|
<setdynvars sourcetype="file" fileid="userlist.csv" delimiter=";" order="iter">
<var name="username" />
<var name="user_password" />
</setdynvars>
|
定义了两个动态变量username和user_password字段和CSV文件的入口。使用上面例子,请求现在是这样:
1
2
3
4
5
6
|
<request subst="true">
<http url='/login.cgi' version='1.0'
contents='username=%%_username%%&password=%%_user_password%%&op=login'
content_type='application/x-www-form-urlencoded' method='POST'>
</http>
</request>
|
比使用老方法简单多了。
如果有多个测试阶段并且使用文件时加入了order=”iter”状态,在不同到达阶段文件不会被重置。当更改阶段时,您将不会返回到第一行。
1
2
3
4
5
6
|
<arrivalphase phase="1" duration="10" unit="minute">
<users maxnumber="10" arrivalrate="100" unit="second" />
</arrivalphase>
<arrivalphase phase="2" duration="10" unit="minute">
<users maxnumber="20" arrivalrate="100" unit="second"></users>
</arrivalphase>
|
这个例子中阶段1将读取10行左右,阶段2会读取下面的20行左右。
三、动态变量
在一些案例中,在session中你可能需要使用一个通过服务器响应给定的一个值,这个值是服务器为每个用户动态生成的。为此你可以在场景中使用<dyn_variable>。
举个HTTP的例子,你可以在HTML中很容易的抓取一个值:
1
2
3
|
<form action="go.cgi" method="POST">
<hidden name="random_num" value="42"></form>
</form>
|
并且:
1
2
3
4
|
<request>
<dyn_variable name="random_num"></dyn_variable>
<http url="/testtsung.html" method="GET" version="1.0"></http>
</request>
|
在所有用户的session中random_num将被设置为42,这个值将被所有含有%%_random_num%%标记并且请求标签属性为subst=”true”的替换,比如:
1
2
3
4
5
6
|
<request subst="true">
<http url="/go.cgi" version="1.0"
contents="username=nic&random_num=%%_random_num%%&op=login"
content_type="application/x-www-form-urlencoded" method="POST">
</http>
</request>
|
3.1、正则表达式
如果动态值不是一个形式变量,你可以通过手动设置一个正则表达式,例如获取一个HTML页面的标题:
正则表达式引擎使用re模块,Erlang中一个Perl形式的正则表达式模块:
1
2
3
4
5
|
<request>
<dyn_variable name="mytitlevar"
re="<title>(.*)</title>"/>
<http url="/testtsung.html" method="GET" version="1.0"></http>
</request>
|
1.4.0版本之前Tsung从Erlang使用老的regexp模块,这现在是不赞成使用的,语法格式如下:
1
2
3
4
5
|
<request>
<dyn_variable name="mytitlevar"
regexp="<title>\(.*\)</title>"/>
<http url="/testtsung.html" method="GET" version="1.0"></http>
</request>
|
3.2、XPath
在发行版本1.3.0中提出了一种新的分析服务器响应的方法。只适用于HTTP和XMPP的插件因为它是基于XML/HTML的。这些特性使用mochiweb库并且只能工作在Erlang R12B或者更高版本上。
特点:
- XPath可以很简单的读写,并很好匹配的HTML / XML页面。
- 解析器工作在binaries(),不产生任何string()。
- 解析HTML/XML树和构建成它是分摊在一个给定请求的所有dyn_variables定义中。
为了利用Xpath表达式,在定义dyn_variable的时候使用一个xpath属性,来替代re,例如:
1
2
|
<dyn_variable name="field1_value" xpath="//input[@name='field1']/@value"/>
<dyn_variable name="title" xpath="/html/head/title/text()"/>
|
Xpath引擎有一个bug,结果节点从“descendant-or-self”不能按顺序返回文档,这不是一个常见的问题。
然而这样使用//img[1]/@src是不被推荐的,顺序元素<img>将不能从//img返回正确结果。
为了避免没有“descendant-or-self”特性,这样使用:/html/body/div[2]/img[3]/@src 可以返回预期结果并可正常使用。
可以使用Xpath从一个html页面获取元素列表,允许对象动态检索。你可以创建嵌入式Erlang代码来分析产生的列表或使用foreach这个在1.4.0发行版本中有介绍。
对于XMPP,可以在一个动态变量中获取所有联系人列表:
1
2
3
4
5
|
<request subst="true">
<dyn_variable name="contactJids"
xpath="//iq[@type='result']/query[@xmlns='jabber:iq:roster']//item[string-length(@wr:type)=0]/@jid" />
<jabber type="iq:roster:get" ack="local"/>
</request>
|
3.3、JSONpath
当服务器发送JSON数据时还可以使用另一个方法来分析服务器响应(在1.3.2发行版本中)。这个只适用于HTTP的插件。这些特性使用mochiweb库并且只能工作在Erlang R13B或者更高版本上。
Tsung实现一个JOSNPath有限子集在这里定义http://goessner.net/articles/JsonPath/。
为了利用jsonpath表达式,在定义<dyn_variable>时使用jsonpath来替代re,例如:
1
|
<dyn_variable name="array3_value" jsonpath="field.array[3].value"/>
|
你仍然可以使用表达式Key=Val, 比如:
1
|
<dyn_variable name="myvar" jsonpath="field.array[?name=bar].value"/>
|
3.4、PostgreSQL
因为 PostgreSQL protocol is binary,regexp不能用来解析服务器的输出。相反,一个特定的解析可以从服务器的响应中提取内容;要这样做的话,使用pgsql_expr属性。使用data_row[L][C]提取L行C列输出数据。也可以使用该列的名称(比如表的字段名)。这个例子从服务器响应中提取3个动态变量:
首先提取第4行的第3列,然后从第2行提取mtime字段,最后通过row_description提取一些数据:
1
2
3
4
5
6
|
<request>
<dyn_variable name="myvar" pgsql_expr="data_row[4][3]"/>
<dyn_variable name="mtime" pgsql_expr="data_row[2].mtime"/>
<dyn_variable name="row" pgsql_expr="row_description[1][3][1]"/>
<pgsql type="sql">SELECT * from pgbench_history LIMIT 20;</pgsql>
</request>
|
行描述是这样的:
1
2
3
4
5
6
7
8
|
| =INFO REPORT==== 14-Apr-2010::11:03:22 ===
| ts_pgsql:(7:<0.102.0>) PGSQL: Pair={row_description,
| [{"tid",text,1,23,4,-1,16395},
| {"bid",text,2,23,4,-1,16395},
| {"aid",text,3,23,4,-1,16395},
| {"delta",text,4,23,4,-1,16395},
| {"mtime",text,5,1114,8,-1,16395},
| {"filler",text,6,1042,-1,26,16395}]}
|
因此在这个例子中,row变量等于“aid”。
3.5、 Decoding variables
解码变量包含HTML实体编码是可能的, 通过设置decode属性为html_entities来完成:
1
2
3
4
5
6
|
<request>
<dyn_variable name="mytitlevar"
re="<title>(.*)</title>"
decode="html_entities"/>
<http url="/testtsung.html" method="GET" version="1.0"></http>
</request>
|
3.6、set_dynvars
1.3.0版本后引入了许多功能强大的动态变量。
你可以不仅在分析服务器数据时设置动态变量,还可以使用外部文件构建,或者通过一个函数生成,或者生成随机 数字/字符串。
动态变量的一些类型(sourcetype属性):
3.6.1、通过调用一个Erlang函数定义的动态变量:
1
2
|
<setdynvars sourcetype="erlang" callback="ts_user_server:get_unique_id">
<var name="id1" />
|
3.6.2、通过解析外部文件定义的动态变量:
1
2
3
4
|
<setdynvars sourcetype="file" fileid="userdb" delimiter=";" order="iter">
<var name="user" />
<var name="user_password" />
</setdynvars>
|
分隔符可以是任何字符串, 并且order可设为iter或random
3.6.3、一个动态变量可以是一个随机数(均匀分布):
1
2
3
|
<setdynvars sourcetype="random_number" start="3" end="32">
<var name="rndint" />
</setdynvars>
|
3.6.4、一个动态变量可以是一个随机字符串:
1
2
3
|
<setdynvars sourcetype="random_string" length="13">
<var name="rndstring1" />
</setdynvars>
|
3.6.5、一个动态的变量可以是一个urandom字符串
这比随机字符串要快得多,但字符串是不是真正随机的:总是使用相同的字符集。
一个动态的变量可以通过Erlang代码动态赋值生成:
1
2
3
4
5
6
|
<setdynvars sourcetype="eval"
code="fun({Pid,DynVars})->
{ok,Val}=ts_dynvars:lookup(md5data,DynVars),
ts_digest:md5hex(Val) end.">
<var name="md5sum" />
</setdynvars>
|
在上面情况下,我们使用tsung函数ts_dynvars:lookup取回动态变量命名md5data。这个dyn_variable md5data可以设置在任何描述方式在在动态变量的动态变量部分。
3.6.6、一个动态的变量,可以通过运行jsonpath规范产生(见JSONPath)到一个现有的动态变量:
1
2
3
|
<setdynvars sourcetype="jsonpath" from="notification" jsonpath="result[?state=OK].node">
<var name="deployed" />
</setdynvars>
|
3.6.7、您可以创建动态变量当前服务器的主机名和端口:
1
2
3
4
|
<setdynvars sourcetype="server">
<var name="host" />
<var name="port" />
</setdynvars>
|
3.6.8、你可以定义一个动态变为固定值,使用它在一个插件(1.5.0版本后):
1
2
3
|
<setdynvars sourcetype="value" value="foobar">
<var name="constant" />
</setdynvars>
|
setdynvars可以在session的任何地方定义。
四、检查服务器响应
在<request>标签中定义math标签,可以检查服务器对一个给定字符串的响应,并根据结果做出一些动作。在任何情况下,如果匹配的话将增加匹配的计数器,如果不匹配的话,不匹配的技术器将增加。
例如,让我们说你想测试一个登录页面。如果登录成功,服务器将在HTML中响应Welcome !,否则不响应:
1
2
3
4
5
6
|
<request>
<match do="continue" when="match">Welcome !</match>
<http url="/login.php" version="1.0" method="POST"
contents="username=nic&user_password=sesame"
content_type="application/x-www-form-urlencoded" >
</request>
|
可以使用正则表达式代替这个简单字符串。
可用的动作清单如下:
- continue:什么都不做,continue(只更新匹配或不匹配计数器)
- log:把请求id, userid, sessionid, name记录到log中(在match.log中)
- abort:中断这个session
- restart:重启这个session,默认的最大重启次数是3
- loop:5秒后重复这个请求,默认最大loops数量是20
- dump:存储响应中的内容到一个文件中,文件名是match----.dump
您可以在一个请求中混合多个匹配标记:
1
2
3
4
5
|
<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'>
</request>
|
你也可以指定不匹配执行动作而不是匹配。
如果你想要跳过HTTP headers而只匹配body,可以使用skip_headers=’http’。
此外你也可以在匹配前对content应用一个函数;例如,下面的示例使用的特点,在一个HTTP响应body计算md5sum,并比较它和给定值:
1
2
|
<match do='log' when='nomatch' skip_headers='http' apply_to_content='ts_digest:md5hex'>01441debe3d7cc65ba843eee1acff89d</match>
<http url="/" method="GET" version="1.1"/>
|
也可以使用动态变量,使用subst属性:
1
2
|
<match do='log' when='nomatch' subst='true' >%%_myvar%%</match>
<http url="/" method="GET"/>
|
1.5.0版本后,可以在match标签中添加name属性用于在match.log中记录打印名称,如下:
1
2
|
<match do='log' when='match' name='http_match_200ok'>200OK</match>
<http url="/" method="GET" version="1.1"/>
|
五、Loops, If, Foreach
在1.3.0版本后,session中可以增加条件/非条件循环。
在1.4.0版本后,可以通过循环列表,轮询每一个动态变量。
5.1、for
重复一个封闭动作的固定次数。动态变量作为一个计数器使用,所以当前的迭代可以用于请求中。属性列表:
- from:初使值
- to:最终值
- incr:增量
- var:保存当数值的变量名
1
2
3
4
5
|
<for from="1" to="10" incr="1" var="counter">
...
<request> <http url="/page?id=%%_counter%%"></http> </request>
...
</for>
|
5.2、repeat
Repeat the enclosing action (while or until) some condition. This is intended to be used together with declarations. List of attributes:
重复一个封闭动作(while或until)一些条件。这个是和<dyn_variable>声明一起使用。属性列表:
- name:循环名
- max_repeat:循环次数(默认20)
注意:循环的最后一个标签必须是是<while>或<until> 例如:
1
2
3
4
5
6
7
8
9
|
<repeat name="myloop" max_repeat="40">
...
<request>
<dyn_variable name="result" re="Result: (.*)"/>
<http url="/random" method="GET" version="1.1"></http>
</request>
...
<until var="result" eq="5"/>
</repeat>
|
1.3.1版本后,也可以添加基于动态变量的语句。
5.3、if
1
2
3
4
|
<if var="tsung_userid" eq="3">
<request> <http url="/foo"/> </request>
<request> <http url="/bar"/> </request>
</if>
|
你可以用eq或neq来检查变量。
1.5.1版本后可以使用比较运算符gt,gte,lt和lte来做大于、大于等于、小于、小于等于的操作。
如果动态变量是一个列表(比如从XPath输出),你可以进入这样一个列表的第n个元素:
1
|
<if var="myvar[1]" eq="3">
|
这里我们比较列表的第一个元素和3。
5.4、foreach
为指定的列表中包含的所有元素重复封闭的动作。基本语法如下:
1
2
3
4
5
|
<foreach name="element" in="list">
<request subst="true">
<http url="%%_element%%" method="GET" version="1.1"/>
</request>
</foreach>
|
可以通过遍历过滤你想要的元素,在foreach语句中可以使用include和exclude属性。
如下例,你只想要包换一个本地路径的元素:
1
|
<foreach name="element" in="list" include="^/.*$">
|
如果想要排除包含指定URL的所有元素,可以这样写:
1
|
<foreach name="element" in="list" exclude="http:\/\/.*\.tld\.com\/.*$">
|
你可以结合XPath查询。例如,下面的场景将检索在网页上指定的所有图像:
1
2
3
4
5
6
7
8
9
|
<request subst="true">
<dyn_variable name="img_list" xpath="//img/@src"/>
<http url="/mypage.html" method="GET" version="1.1"/>
</request>
<foreach name="img" in="img_list">
<request subst="true">
<http url="%%_img%%" method="GET" version="1.1"/>
</request>
</foreach>
|
六、rate limiting
1.4.0版本后就可以使用限速功能,无论是全局范围内(见options选项),或为每个session。
例如,为一个给定的会话限制速度64kb/秒:
1
2
3
4
|
<session name="http-example" probability="70" type="ts_http">
<set_option name="rate_limit" value="64" />
...
</session>
|
当前只有进入的速率可以限制。
七、请求排除
测试运行的时候可用来排除一些请求。要实现这个功能你必须给他们打上tag标签并且在启动的时候使用-x选项。
下面例子中,排除GET foo.png,为要排除的请求添加tag:
1
2
3
4
5
6
|
<request>
<http url="/" method="GET"></http>
</request>
<request tag="image">
<http url="/foo.png" method="GET"></http>
</request>
|
然后下面命令开启测试:
1
|
tsung -f SCENARIO.xml -x image start
|
只有GET到/将被执行。
注意,请求标签还可以通过dumptraffic =”protocol”写入到log中(见文件结构)。
八、客户端证书
可以通过使用客户端证书来进行ssl验证。你可以使用动态变量来设置证书的一些参数(key password是可选的):
1
2
3
4
5
6
|
<session name="https-with-cert" probability="70" type="ts_http">
<set_option name="certificate">
<certificate cacertfile="/etc/ssl/ca.pem"
keyfile="%%_keyfile%%" keypass="%%_keypass%%" certfile="/home/nobody/.tsung/client.pem"/>
</set_option>
|
Tsung数据统计与报告
一、文件格式
默认情况下,Tsung使用自己的文件格式。
1.4.2版本后,可以设置Tsung使用JSON格式:但这样设置后,tsung_stats.pl和tsung_plotter将不能和JOSN文件一块工作。
启用JSON输出,这样配置:
1
|
tsung backend="json" ...>
|
JSON输出文件实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
{
"stats": [
{"timestamp": 1317413841, "samples": []},
{"timestamp": 1317413851, "samples": [
{"name": "users", "value": 0, "max": 0},
{"name": "users_count", "value": 0, "total": 0},
{"name": "finish_users_count", "value": 0, "total": 0}]},
{"timestamp": 1317413861, "samples": [
{"name": "users", "value": 0, "max": 1},
{"name": "load", "hostname": "requiem", "value": 1, "mean":
0.0,"stddev": 0,"max": 0.0,"min": 0.0 ,"global_mean": 0
,"global_count": 0},
{"name": "freemem", "hostname": "requiem", "value": 1, "mean":
2249.32421875,"stddev": 0,"max": 2249.32421875,"min":
2249.32421875 ,"global_mean": 0 ,"global_count": 0},
{"name": "cpu", "hostname": "requiem", "value": 1, "mean":
4.790419161676647,"stddev": 0,"max": 4.790419161676647,"min":
4.790419161676647 ,"global_mean": 0 ,"global_count": 0},
{"name": "session", "value": 1, "mean": 387.864990234375,"stddev":
0,"max": 387.864990234375,"min": 387.864990234375
,"global_mean": 0 ,"global_count": 0},
{"name": "users_count", "value": 1, "total": 1},
{"name": "finish_users_count", "value": 1, "total": 1},
{"name": "request", "value": 5, "mean": 75.331787109375,"stddev":
46.689242405019954,"max": 168.708984375,"min": 51.744873046875
,"global_mean": 0 ,"global_count": 0},
{"name": "page", "value": 1, "mean": 380.7548828125,"stddev":
0.0,"max": 380.7548828125,"min": 380.7548828125 ,"global_mean":
0 ,"global_count": 0},
{"name": "connect", "value": 1, "mean": 116.70703125,"stddev":
0.0,"max": 116.70703125,"min": 116.70703125 ,"global_mean": 0
,"global_count": 0},
{"name": "size_rcv", "value": 703, "total": 703},
{"name": "size_sent", "value": 1083, "total": 1083},
{"name": "connected", "value": 0, "max": 0}, {"name": "http_304", "value": 5, "total": 5}]}]}
|
二、可用属性
- request 每个请求的响应时间
- page 一系列请求的响应时间(一个页面是不包含“思考时间”的一系列请求)
- connect 建立连接过程的时间
- reconnect 重新连接的数量
- size_rcv 响应的大小(单位是byte)
- szie_sent 请求的大小(单位是byte)
- session 一个用户会话的持续时间
- users 同时的用户数量(已经开始session,但还没有结束的用户)
- connected 打开TCP/UDP连接的用户(HTTP例子,在think time期间,一个TCP连接可以被服务器关闭,但直到think time过期都不会重新打开连接)
- custom transcations
平均响应时间(请求,页面等)是按照每10秒计算的(然后复位)。这就是为什么有最高的平均值和最低的平均值的统计报告。自从版本1.3.0,整个测试平均计算了。
2.1、HTTP特定属性
- count for each response status 每个响应状态的计数器(200,404等)
2.2、jabber特定属性
- request_noack no_ack请求的数量。由于no_ack和响应时间是没有意义的,我们保持一个单独的统计。
- async_unknow_data_rcv Only if bidi is true for a session. counter the number of messages received from the server without doing anything.
- async_data_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 the server.
2.3 系统监控属性
- {load,<host>} 最后一分钟的系统负载
- {cpu,<host>} CPU百分比(最大是100%,比如在双核cpu系统,100%的意思的两个核都占用100%)
- {freemen,<host>} 空闲内存
三、设计
一些设计情况和内部统计引擎:
Tsung设计每秒处理数千请求,所以很长一段时间(几个小时)不写数据到磁盘(出于性能原因)。相反,它计算对每一种类型的数据的平均值和估算标准的变化,并每10秒写入这些估算到磁盘(然后开始一个新的估计为下10秒)。这些计算是做两种数据:
- sample 比如响应时间
- sample_counter 当输入是一个渐增的(例如包发送数量)
也有其他两种类型的有用数据(这些没有平均的说法):
- counter:一个简单的计数器,比如HTTP状态代码
- sum:累积HTTP响应的大小(它给出了一个估算的带宽使用)
四、生成报告
进入到你的测试log目录(“~/.tsung/log/yyyyMMdd-hhmm/”),然后在控制台执行脚本tsung_stats.pl:
1
|
/usr/lib/tsung/bin/tsung_stats.pl
|
你甚至可以在测试还在运行的时候就生成统计报告。
使用-help可以查看所有的可变选项:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
Available options:
[--help] (this help text)
[--verbose] (print all messages)
[--debug] (print receive without send messages)
[--dygraph] use dygraphs (http://danvk.org/dygraphs/) to render graphs
[--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)
[--rotate-xtics (rotate legend of x axes)
[--stats <file>] (stats file to analyse, default=tsung.log)
[--img_format <format>] (output format for images, default=png
available format: ps, svg, png, pdf)
|
五、Tsung概要
六、图形概述
七、Tsung Plotter
Tsung Plotter(tsplot命令)是一个最近增加到Tsung中的一个可选的工具(它是用Python写的),用来比较不同的Tsung测试。tsplot可以把几个tsung.log文件绘制到同一图表,进行进一步比较和分析。你可以轻松地自定义生成的图。可以在工具手册页获得更多信息(man tsplot)。
使用例子:
1
|
tsplot "First test" firsttest/tsung.log "Second test" secondtest/tsung.log -d outputdir
|
八、RRD
一个能从tsung.log创建red文件的perl脚步tsung-rrd.pl,脚本在/usr/lib/tsung/bin/tsung-rrd.pl。
5、tsung結果分析
tsung生成的測試報告都放在$HOME/.tsung/log下,以日期加時間的方式命名,如:.tsung/log/20150407-1951,其中最重要的幾張圖是
tsung產生的用戶數曲線圖 .tsung/log/20150407-1951/images/graphes-Users-simultaneous.png
Y軸代表每秒用戶數,tsung每秒會產生一批用戶,這個統計結果是每十秒統計一次,所有的圖的起始位置顯示的是0,其實是第一個10秒
http接口響應數曲線圖(TPS) .tsung/log/20150407-1951/images/graphes-HTTP_CODE-rate.png
Y軸是每秒響應數,右上角的200是http狀態碼,如果有多個狀態碼,會有多條不同顏色的曲線。
http接口響應時間曲線圖 .tsung/log/20150407-1951/images/graphes-Perfs-mean.png
Y軸是接口響應時間,單位是毫秒,request的線代表請求響應總耗時,connect的線代表tcp鏈接建立的時間。
6、主要統計信息
Tsung統計數據是平均每十秒重置一次,所以這裡的響應時間(連接、請求、頁面、會話)是指每十秒的平均響應時間;
connect: 表示 每個連接持續時間;
Hightest 10sec mean 連接最長持續時間
Lowest 10sec mean 連接最短持續時間
Highest rate 每秒最高建立連接速率
Mean 平均每個連接持續時間
Count 總連接數
page: 表示 每個請求集合的響應時間,(一個頁面表示一組沒有被thinktime間隔的請求)
request: 表示 每個請求的響應時間;
Hightest 10sec mean 請求最長響應時間
Lowest 10sec mean 請求最短響應時間
Highest rate 請求最快發送速率
Mean 平均每個請求響應時間
Count 總請求數
session: 表示 每個用戶會話持續時間;
Hightest 10sec mean 會話最長持續時間
Lowest 10sec mean 會話最短持續時間
Highest rate 每秒最高進行會話速率
Mean 平均每個會話持續時間
Count 總會話數
7、數據流量統計
size_rcv: 表示 響應請求數據量
size_sent:表示 發送請求數據量
Hightest rate 每秒最高 響應/發送 請求數據量
Total 響應/發送 請求總數據量
8、計數統計
connected 表示會話開始且尚未結束,並且已建立連接的最大用戶數
finished_user_count 表示已經完成會話的最大用戶數
users 表示會話開始且尚未結束的最大用戶數
users_count 表示Tsung總共生成的用戶總數
9、錯誤統計
Error_abort_max_conn_retries 重新嘗試連接錯誤
Error_connect_timeout 連接超時錯誤
Error_connect_nxdomain 不存在的域錯誤
Error_unknown 位置錯誤
Highest rate 發生錯誤最高速率
Total number 發生該錯誤總個數
10、http返回狀態碼統計
200:表示客戶端請求已成功響應
Highest rate 狀態碼返回最高速率
Total number 返回狀態碼的總個數
留言列表