这是一次简单的性能测试实战,但其中包含了许多性能测试要点,写出来是为了理清思路和分享讨论
废话少说,直接上菜~
【测试工具】loadrunner9.52
【被测系统说明】以Flex为前端应用,JAVA为后台处理的B/S系统
【性能测试目标】
1.测试系统常用功能在极大数据量下的响应时间以及查询效率
2.系统定时自动刷新数据,多用户并发的情况下,服务器资源利用率与响应时间
3.负载测试,找出系统可以承受的极限数据量和并发量
4.压力测试,以接近极限数据量和并发量的情况下,长时间运行
5.数据量极大的前提下,报表统计的效率
============什么性能指标获取,与开发、配置管理员协调,其实这部分是整个性能测试的指导,但本文只是想与大家讨论一下整个性能测试的一个流程与其中思想,所以这里就不细说了。
【测试网络拓扑结构】
===========说明:被测系统服务器上只运行web应用,数据库和其他平台在其他系统服务器上,这里只用一个图标表示其他系统
【测试脚本录制】
我们项目是Flex技术作为前端开发的应用,所以我们需要启动loadrunner的Flex/Web脚本。
录制之前,有必要说一下Flex的交互机制。浏览器作为客户端与服务器之间交互的媒介是amf,amf将服务器返回的数据编译后,到客户端进行解析,而本项目服务器端返回的数据解析后都是xml。录制完成后,让我们看一下生成的部分代码以及服务器返回的xml数据
loadrunner代码:
vuser_init() { web_url("OMM", "URL=http://162.0.0.107:9180/OMM", "Resource=0", "RecContentType=text/html", "Referer=", "Snapshot=t197.inf", "Mode=HTTP", LAST); web_url("swfobject.js", "URL=http://162.0.0.107:9180/OMM/swfobject.js", "Resource=1", "RecContentType=text/javascript", "Referer=http://162.0.0.107:9180/OMM/", "Snapshot=t198.inf", LAST); web_add_header("x-flash-version", "10,3,181,14"); web_url("OMM.swf", "URL=http://162.0.0.107:9180/OMM/OMM.swf", "Resource=1", "RecContentType=application/x-shockwave-flash", "Referer=http://162.0.0.107:9180/OMM/", "Snapshot=t199.inf", LAST); web_add_auto_header("x-flash-version", "10,3,181,14"); flex_amf_call( "AMF3_call", "Gateway=http://162.0.0.107:9180/OMM/messagebroker/amf", "Snapshot=t200.inf", "ResponseParameter=amf_dsid", MESSAGE, "Method=null", "TargetObjectId=/1", BEGIN_ARGUMENTS, "\n " "\n5092B71C-A982-EC3C-979C-C5000BD59ED4\n " "0\n0\n\n " "\nDSMessagingVersion\n1\n " "\n\nDSId\nnil" "\n\n\n \n\n\n\n\n " "false\nfalse\n " "\n\n \n" "\n5\n" "", END_ARGUMENTS, LAST); lr_xml_get_values("XML={amf_dsid}", "FastQuery=/AMFPacket/Messages/Message/AMF3/object-externalizable-custom/flex.messaging.messages.AcknowledgeMessageExt/flex.messaging.io.amf.ASObject/map/string[3]", "ValueParam=dsid", LAST); flex_amf_call( "AMF3_call_1", "Gateway=http://162.0.0.107:9180/OMM/messagebroker/amf;jsessionid=E2D06A0CD5542BA5FF834EEC4B179851", "Snapshot=t201.inf", MESSAGE, "Method=null", "TargetObjectId=/1", BEGIN_ARGUMENTS, "\n " "HttpSessionService\n" "D74084C6-F8C9-2996-68E6-C5000B0ACA00\n0\n " "0\n\n\n" "DSId\n{dsid}\n " "\n\nDSEndpoint\n" "my-amf\n\n\ngetSession" "\n\nOMM_USER_SESSION\n" "\n" "", END_ARGUMENTS, LAST); .....} |
解析后的的服务器响应
vuser_init.c(36): AMF response message received. The following XML describes the message content:
vuser_init.c(36):
<AMFPacket AMF_version="3">
<AMFHeaders>
<AMFHeader name="AppendToGatewayUrl" must_understand="true">
<string>;jsessionid=C234853CAA9C12E2C2F93D17B2CD880A</string>
</AMFHeader>
</AMFHeaders>
<Messages>
<Message method="/1/onResult" target="">
<AMF3>
<object-externalizable-custom>
<flex.messaging.messages.AcknowledgeMessageExt>
<byte>-88</byte>
<byte>3</byte>
<flex.messaging.io.amf.ASObject serialization="custom">
<unserializable-parents/>
<map>
<default>
<loadFactor>0.75</loadFactor>
<threshold>12</threshold>
</default>
<int>16</int>
<int>2</int>
<string>DSMessagingVersion</string>
<double>1.0</double>
<string>DSId</string>
<string>870BCE6D-4408-EB78-FBBF-BD16FC2038AA</string>
</map>
<flex.messaging.io.amf.ASObject>
<default>
<inHashCode>false</inHashCode>
<inToString>false</inToString>
</default>
</flex.messaging.io.amf.ASObject>
</flex.messaging.io.amf.ASObject>
<long>1322101201796</long>
<byte-array>hwvObUQf7Lg4fqUpf+9AHg==</byte-array>
<byte-array>hwvObUQuc6aXE9CvnjwC3A==</byte-array>
<byte>2</byte>
<byte-array>UJK3HKmC7DyXnMUAC9We1A==</byte-array>
<byte>0</byte>
</flex.messaging.messages.AcknowledgeMessageExt>
</object-externalizable-custom>
</AMF3>
</Message>
</Messages>
</AMFPacket>
vuser_init.c(36): FLEX AMF Call ("AMF3_call") was successful
回放前,先设置一下输出日志的内容,Vuser-->Run-time Settings-->log-->log messages at the detail level of-->Extended log中勾选Data returned by server
注意:这个日志选项会带来一些麻烦,如果服务器返回的是一个.swf页面的话,回放时,loadrunner解析返回输出数据的速度很慢,所以如果代码中有请求.swf资源,最好先注释掉这段代码,待调试结束后,再将代码还原 |
回放时,在flex_amf_call("AMF3_call_1"...)处会发生错误,这说明我们需要对提交给服务器数据的某个值进行参数化,我们翻回来看一下flex_amf_call("AMF3_call"...)这段对应的回放日志
<AMFPacket AMF_version="3">
<AMFHeaders/>
<Messages>
<Message method="/1/onStatus" target="">
<AMF3>
<object-externalizable-custom>
<flex.messaging.messages.ErrorMessage>
<destination>HttpSessionService</destination>
<messageId>8736B554-CD0D-900E-199C-BBBA1D663CAC</messageId>
<timestamp>1322102353437</timestamp>
<timeToLive>0</timeToLive>
<correlationId>F748F4F4-310A-4298-710C-D36D9D9AA8B7</correlationId>
<faultCode>Server.Processing.DuplicateSessionDetected</faultCode>
<faultString>Detected duplicate HTTP-based FlexSessions, generally due to the remote host disabling session cookies. Session cookies must be enabled to manage the client connection correctly.</faultString>
</flex.messaging.messages.ErrorMessage>
</object-externalizable-custom>
</AMF3>
</Message>
</Messages>
</AMFPacket>
Action.c(61): Error: Server returned error for message #1 : description="AMF call returned an error, described in XML seen in extended log"
从日志中可以看到,出错的原因faultCode以及这个错误的解释faultString,correlationId则表示是哪个请求造成了报错,我首先去查了faultCode这个报错是什么原因造成的,结果收获不大,在51坛子里找到几篇同样问题的文章,其中有一个已经解决的了问题,但没有说明解决办法。只知道需要做参数化,关联。接着通过correlationId找到了提交请求的代码
想看回放时,loadrunner提交的数据和服务器响应,可以进入Tree视图--snapshot页签内查看repaly时,loadrunner的request和服务器响应reponse |
?xml version="1.0" encoding="UTF-8" ?>
<AMFPacket AMF_version="3">
<AMFHeaders />
<Messages>
<Message method="null" target="/1">
<Arguments>
<Argument>
<AMF3>
<object-externalizable-custom>
<flex.messaging.messages.RemotingMessage>
<destination>HttpSessionService</destination>
<messageId>F748F4F4-310A-4298-710C-D36D9D9AA8B7</messageId>
<timestamp>0</timestamp>
<timeToLive>0</timeToLive>
<headers>
<entry>
<string>DSId</string>
<string>87307160-021B-F330-799A-97CD0AF390DC</string>
</entry>
<entry>
<string>DSEndpoint</string>
<string>my-amf</string>
</entry>
</headers>
<operation>getSession</operation>
<parameters>
<string>OMM_USER_SESSION</string>
</parameters>
</flex.messaging.messages.RemotingMessage>
</object-externalizable-custom>
</AMF3>
</Argument>
</Arguments>
</Message>
</Messages>
</AMFPacket>
请求中有DSId,这个立刻引起了我的注意;因为在DSID下面有getSession字样,这个DSId可能就是flex的SeesionId,下面我们就开始进行参数化;
先整理一下思路,问题:当flex_amf_call("AMF_call_1"...)提交请求时,服务器返回了错误,错误的原因是提交的DSId不正确。那么这个正确的DSId应该去哪里获取呢?这时,我注意到flex_amf_call("AMF_call"...),从回放日志中看出是它请求服务器授予loadrunner一个DSId,那么服务器返回给它的数据中就应该包含DSId的值;有了这个思路,接下来就是如何将服务器返回的DSId进行参数化。flex应用的参数化比较不同,它使用了loadrunner函数lr_xml_get_values,这个函数是利用xpath查找服务器返回数据中节点的值,将这个值保存到一个变量中;
lr_xml_get_values(
"XML={amf_dsid}",
"FastQuery=/AMFPacket/Messages/Message/AMF3/object-externalizable-custom/flex.messaging.messages.AcknowledgeMessageExt/flex.messaging.io.amf.ASObject/map/string[3]",
"ValueParam=dsid",
LAST);
这个函数有3个参数,XML={amf_dsid}代表要查找的数据源,FastQuery=...代表xpath(也就是数据所在的节点路径),ValueParam=dsid则代表将查找出的值保存到dsid这个变量中,我们将这个函数放到flex_amf_call("AMF_call"...)的后面,这样在服务器发出获取DSId的请求,服务器返回DSId后,我们的函数就会将这个值赋给dsid这个变量,我们用dsid这个变量将所有代码中出现DSId值得地方全部替换为{dsid},就完成了参数化;
函数的参数XML={amf_dsid},数据源{amf_dsid}是由测试者自己设置的,设置方法为:在Tree视图的snapshot中,在左侧列表中找到AMF_call这项,双击后在弹出的对话框中有Response Parameter这项,这里就是设置数据源名称的地方,填写后保存即可。例如:我填写的是amf_dsid,那么在函数中XML={amf_dsid} |
至此,flex的脚本录制就应该没有什么问题了。下一节,会写怎么设计loadrunner的测试场景,对应的loadrunner工具loadrunner controller。