本来想用LoaderRunner对Narya的网络性能再做一次测试,LR的java vuser可以直接用java来编写测试脚本,本来还是挺方便的,但是不知道为什么脚本一跑起来就报这么个错:
Error (-17998): Failed to get [param not passed in call] thread TLS entry.
实在是有点郁闷的,网上搜索了很久也没找到个所以然,只是有人模模糊糊的指出,有可能是LR的java vuser脚本不支持多线程。想来也是,LR是靠license的并发数来卖钱的,如果你一个vuser可以开多个线程,每个vuser开100个线程,那一个100人的license就可以当1万人来用了,但我们narya的一个client本身就要开几个线程,看来是无法用LR来测了。
好在开源的社区里还有一个叫JMeter的性能测试工具。我们希望通过工具能够更灵活的来配置我们的测试用例,包括并发用户数,每个用户发送的请求数,和每个请求之间的间隔时间等。JMeter内置了一种叫做java sampler的sampler,我们需要继承AbstractJavaSamplerClient这个类,实现它的下面这个方法。
public SampleResult runTest(JavaSamplerContext javasamplercontext)
完成后将自定义的Sampler打包成jar放到jmeter_home/lib/ext目录下
我们希望整个测试用例第一步登录虚拟用户,然后设置一个集合点,等所有的用户都登录完了进入第二步,高并发发送请求。等所有请求发送完后再登出。
对应的我们设置三个sampler,分别用来登录、发送请求和登出。
LogonSampler,ActionSampler,LogoffSampler
因为跑Sampler的线程和具体的测试代码的线程并不是同一个,所以我们还需要设置一种同步方式,当具体的测试完成后能够通知Sampler,这里我们就用Java的wait()和notify()好了。
以登录为例,client跑在runQueue线程上,Sampler作为一个listener,当client任务完成时回调listener方法改变success状态并唤醒Sampler的线程。而Sampler线程启动client线程就进入等待状态。
TestClient tclient = new TestClient(); UsernamePasswordCreds creds = new UsernamePasswordCreds(new Name(username), password); BasicRunQueue rqueue = new BasicRunQueue(); Client client = new Client(creds, rqueue); tclient.setClient(client); tclient.addListener(this); client.addClientObserver(tclient); client.setServer("202.75.*.*", new int[]{45312}); client.logon(); // start up our event processing loop rqueue.start(); synchronized(this) { while (!success) { try { this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
client线程唤醒Sampler线程
if (listener != null) { synchronized(listener) { listener.actionPerformed(); listener.notify(); listener = null; } }
整个GUI看起来如下。其中,在线程组中可以设置需要运行的虚拟用户数。
我们分别把三个Sampler放到三个Controller下,通过Controller来控制Sampler的运行。其中发送请求的Java Request Sampler放到一个Loop Controller下,这样可以通过Controller来控制请求的次数。Synchronizing Timer可以作为集合点来使用,高斯随机定时器可以设定每个请求之间的间隔。
另外我们这里还可以用参数的方式来设定每个虚拟用户的登录名和密码,这个账号数据可以放在一个外部的csv文件中,通过JMeter提供的CSV Data Set Config配置元件来配置。
在GUI中配置好文件名和变量信息,然后在Sampler的配置中就通过${...}来引用。
而GUI中的参数信息是在我们继承的Sampler中配置
public Arguments getDefaultParameters(){ Arguments params = new Arguments(); params.addArgument("username", "test"); params.addArgument("password", "test"); return params; }
这些都准备好之后,我们就可以在test plan中加入我们所需要获得的反馈数据的种类了,只要添加自己感兴趣的Listener就可以了。但是不要忘了在Sampler中对sampleResult的设置。
如果测试顺利完成就把result设为successful,否则一部分的listener无法正确显示sampler是否执行成功。
public SampleResult runTest(JavaSamplerContext context) { SampleResult result = new SampleResult(); result.sampleStart(); ...... ...... result.sampleEnd(); result.setSuccessful(true); }
不过JMeter所有内置的Listener都只是对服务器返回的数据进行分析,它是无法直接获得服务器的CPU,内存使用等数据的。如果要获得服务器数据,简单的办法是直接登录到服务器(Unix/Linux)上运行top,sar等命令。其次是通过jmeter sampler for rstatd的插件来完成。首先得在服务器上安装并开启rpc rstatd服务。rstatd进程可以搜集内核提供的CPU,内存等的使用数据,然后客户端可以通过rpc(远程方法调用)来获得这些数据。
可以用这个链接http://swirstatdsample.sourceforge.net/installation.html来获得jmeter sampler for rstatd的插件,下载的jar包需要安装到 jmeter_home/lib/ext下,另外还要修改一下ApacheJMeter_core.jar中的org/apache/jmeter/resources/messages.properties的一些数据,只要按照指示来做就可以了。
meter sampler for rstatd的插件中有一个rstatd:sampler,这个sampler通过rpc访问服务器获得数据,然后再配合定时器我们就可以以固定时间间隔获得服务器的性能数据了。但要注意的是我们需要另外再开一个线程组,这样才可以在跑测试用例的同时来收集数据(在同一个线程组下所有的test element是顺序执行的)。另外再添加一个rstatd:listener来显示数据就可以了。
整个测试计划这样设置基本就可以了,最后需要注意的一点是JMeter本身在运行的时候会占用一部分资源,这样会一定程度的影响到测试的结果,特别是开启大量的线程的情况下更是如此,这种情况下可以考虑直接用命令行来执行。
jmeter -n -t <testplan.jmx> -l <logfile.jtl>
testplan.jmx是测试计划,logfile.jtl是测试结果的数据文件,可以测试结束后在GUI里打开分析。