loadrunner中的响应时间与事务响应时间

1. 响应时间

事务是指用户在客户端作一种或多种业务所须要的操做集,经过事务函数能够标记完成该业务所须要的操做内容;另外一方面事务能够用来统计用户操做的响应时间,事务响应时间是经过记录用户请求的开始时间和服务器返回内容到客户端时间的差值来计算用户操做响应时间的,如图1所示。html

这里的响应时间不包含客户端GUI时间(例如浏览器解释页面所消耗的时间)。 前面说响应时间是用户请求发出和服务器返回之间的时间差,那么获得这个时间就够了吗?
例如:如今有一场跑步比赛。当比赛完成后,能够获得每位运动员跑完整个比赛所须要消耗的时间,如今须要分析谁的起跑好、谁的冲刺好,能分析出来吗?答案是不能,虽然获得了最重要的完成比赛的响应时间,可是这对分析和优化几乎没有做用,由于只知道告终果而不知道过程。跑步的时间是由起跑、中途、冲刺等时间组成的,若是想要进行分析优化,必须先了解各个阶段所花费的时间和速度以及各个运动员的优缺点。
对于软件来讲,经过事务获得的系统响应时间也是由很是多的部分组成的,通常来讲响应时间由网络时间、服务器处理时间、网络延迟三大部分组成。先来看看当一个客户端发出请求到服务器返回须要经历哪些路径,如图2所示。

前端

1.网络时间
客户端发出请求首先经过网络来到Web Server上(消耗时间为N1);而后Web Server将处理后的请求发送给App Server(消耗时间为N2);App Server将操做数据指令发送给Database (消耗时间为N3);Database服务器将查询结果数据发送回App Server(消耗时间为N4);App Server将处理后的页面发给Web Server(消耗时间为N5);最后Web Server将HTML转发到客户端(消耗时间为N6)。这里的Nx都是网络传输上的时间开销,没有计算业务处理所须要花费的时间。
2.服务器处理时间
另一个方面还要考虑各个服务器处理所须要的时间WT、AT、DT。 3.网络延迟
除了上面两种时间开销之外,还要考虑网络延迟的问题。 因此最终的响应时间组成为:
响应时间 = 网络延迟时间 + WT+AT+DT +(N1+N2+N3)+(N4+N5+N6)+ WT+AT+DT 也能够简单认为响应时间由网络开销(前端)和服务器开销(后端)两大部分组成,如图3所示。

web

那么这些消耗的时间都花在什么事情上了呢?影响网络的因素通常包括如下内容: 1.后端

前端Network DNS Lookup
浏览器

Time to connect
服务器

Time to first buffer
网络

Network Time函数

 Download Time
性能

SSL handshake
测试

FTP authentication

Client Time 

Error Time  网络延迟

2.后端服务

 Web Server

Servlet Time

Method Time

静态动态压缩

App Server

 EJB Time 

Method Time

JNDI Lookup

Database Server

JDBC Time

Connect Time

Execute Time

这里会发现响应时间的组成是很是复杂的,当性能问题出现时,想要定位到具体的代码级别是至关困难的。
LoadRunner只能对本身发出的请求和服务器返回的内容进行网络级别的分析,也就是说LoadRunner可以分析的时间为客户到WWW服务器的时间N1和WWW服务器返回到客户的时间N6。这些时间主要和网络速度有关,能够用一个LoadRunner的名称来解释,叫作Web Page Breakdown。
也就是说VuGen能够分析的时间只有客户端到Web Server之间的部分,后面从Web Server到App Server再到Database Server的时间只能获得一个总和。
2.  事务时间
一个事务的时间是指持续时间,事务会彻底记录下从事务开始到事务结束之间的时间差,那么事务的时间能真实地反映业务操做的时间吗?不能,就好像人用手按秒表来记录短跑时间同样,得出的时间并非彻底准确,存在观察的偏差和操做的偏差,对于一个事务时间来讲,通常由四部分组成,如图4所示

    除了脚本自身浪费的时间,某些时候使用C语言等外部接口进行处理所消耗的时间也会影响事务的时间,而这个时间LoadRunner没法处理,在这种状况下就须要人为地计算第三方时间开销,而且将这个开销的时间记入Wasted Time中。 运行一下下面的代码: 1. Action()    2. {    3.     int i;   4.     int baseIter = 100;    5.     char dude[1000];    6.     merc_timer_handle_t timer;    7.     // Examine the total elapsed time of the action    8.     //Start transaction    9.     lr_start_transaction("Demo");    10.     timer=lr_start_timer();   11.     for (i=0;i<=baseIter*1000;i++) {   12.           sprintf(dude,"This is the way we waste time in a script =               %d", i);   13.            }   14.      wasteTime=lr_end_timer(timer); //时间单位为毫秒  15.     lr_wasted_time(wasteTime*1000);//将wasteTime转换为秒并计入lr的 Wasted Time,当在场景中运行的时候,事务的响应时间会自动扣除Wasted Time 16.     lr_end_transaction("Demo", LR_AUTO);    17.     return 0;    18. }  其中,lr_start_timer()是一个LoadRunner自带的时间计数器,它和lr_end_timer()相对应,可以返回这两个函数间的时间差。 运行脚本后,等待一段时间脚本运行结束,能够看到如下日志。 1. Action.c(18): Notify: Transaction "Demo" started.   2. Action.c(27): wasted time is 85.860000   3. Action.c(28): Notify: Transaction "Demo" ended with  "Pass" status (Duration: 85.8772 Wasted Time: 85.8600).  经过上面这个日志能够看到,在VuGen运行脚本的时候这个1000次的C语言操做所消耗的时间会被算在Transaction时间内,致使Transaction的时间变长。当经过lr_start_timer()计时函数将这个消耗时间加入Wasted Time后,这个脚本就能正确地计算出事务的时间和该   事务时间的Wasted Time了。当在场景中运行的时候,事务的响应时间会自动扣除Wasted Time。 为了确保响应时间的正确,须要扣除在运行脚本时自身的时间消耗,事务中尽可能避免出现非请求的处理内容,若是没法避免请使用lr_wasted_time()函数将多余的时间开销扣除。 例如这样的脚本: 1. merc_timer_handle_t timer; //变量声明   2. lr_start_transaction("Demo");    3. timer=lr_start_timer();   4. lr_load_dll("getkey.dll");   5. lr_save_string(getrandkey(),"key");   6. //经过调用dll得到密钥   7. wasteTime=lr_end_timer(timer);   8. lr_wasted_time(wasteTime*1000);    9. lr_end_transaction("Demo", LR_AUTO);   计算密钥是很消耗时间的,那么可使用timer这个变量来记录计算的时间,并将这个时间从整个事务中扣除。 在计算Wasted Time时不要直接使用lr_wasted_time()覆盖,而忘了加上脚本中LoadRunner函数的自身时间。经过lr_get_transaction_wasted_time()函数能够得到事务自身的Wasted Time,将这个时间累加上第三方统计的Wasted Time再经过lr_wasted_time()函数覆盖。 3.手工事务 前面都是使用LR_AUTO来自动判断事务状态,如今来作一个脚本,看看LoadRunner的事务是如何自动判断状态的。 录制一个论坛注册用户的脚本,在提交注册表单处添加事务开始及结束标志,而后回放该脚本。事务的结果是PASS仍是FAIL呢?虽然回放脚本注册用户是失败的(该用户已经存在),可是事务仍是在PASS状态下完成了,并且会发现事务的持续时间很短。正常状况下注册一个用户到刷新首页通常都要2秒,如今只须要0.3秒。这是由于当服务器判断到该用户已存在后,就没有了数据插入和等待1秒刷新首页的操做,而是直接返回错误提示页面。这个0.3秒是系统处理错误的时间而不是注册用户所须要的时间。 LR_AUTO也是根据服务器的返回状态信息来决定事务是以LR_PASS状态经过仍是以LR_FAIL状态结束,只要服务器返回页面,那么事务就会认为请求成功发出去了,服务器看懂了请求也返回了内容,天然事务是PASS状态了。   这样因为事务自动判断的错误,致使虽然操做是失败的,但获得了一个响应时间,而且这个响应时间又没有正确反映出作这件事情的真正时间,最终就会影响到性能测试获得的数据。 记得在论坛上就有朋友问过这样的问题,为何系统在用户愈来愈多的状况下,响应时间不增反减?这种现象颇有可能就是没有使用手工事务致使的结果。 对于这种状况就须要手工来判断操做是否成功,经过web_reg_find()检查点函数来检查页面是否返回正确,而后经过rowcount的参数值来进行事务状态判断,作到智能判断事务结果。 例如:检查点函数的rowcount保存在参数loginst中,那么事务的状态就应该这样判断: 1. lr_start_transaction("login");   2. web_reg_find("Search=Body",   3.         "SaveCount=loginst",   4.         "Text=登陆失败",   5.         LAST);   6. //登陆请求   7. If(atoi(lr_eval_string("{loginst}"))>=1))   8.         lr_end_transaction("login", LR_FAIL);   9. else   10.         lr_end_transaction("login",LR_PASS);  经过检查点来检查登陆后页面是否是存在"登陆失败"这样的内容,若是存在那么loginst的值就大于等于1,而后把loginst的值取出来和1作比较,若是大于1那么就是登陆失败,不然就是登陆成功。 参数不能和值作比较,因此要先经过lr_eval_string()函数将其转化成字符串,而后再经过atoi()函数转化成整数,这样才能和1做比较。 在绝大多数状况下对于事务都须要采用手工事务的方式来确保事务的正确性和事务时间的有效性。 思考题: 对于Discuz论坛来讲如何作一个有效的用户注册脚本经过手工事务而且得到准确注册操做的响应时间。 业务分析: 注册用户后,在系统的页面上会出现【欢迎:注册用户名】的信息,能够在注册后返回的页面中检查是否出现了这样的内容来判断注册事务是否成功。 经过检查页面能够获得须要判断的代码为: 1.   欢迎:<a class="dropmenu" id="viewpro" οnmοuseοver="showMenu(this.id)">  因此在检查点函数中须要添加这个内容,为了更好地判断,还须要把注册用户的名字也加进去,最后能够获得下面的代码: 1. Action()   2. {   3.         web_url("注册",   4.             "URL=http://192.168.0.200/register.aspx",   5.             "TargetFrame=",   6.             "Resource=0",   7.             "RecContentType=text/html",   8.             "Referer=http://192.168.0.200/",   9.             "Snapshot=t2.inf",   10.             "Mode=HTML",   11.             EXTRARES,   12.             "URL=/templates/default/images/check_error.gif", ENDITEM ,   13.             "URL=/templates/default/images/check_right.gif", ENDITEM ,   14.             "URL=/images/level/3.gif", ENDITEM,   15.             LAST);   16.        17.         lr_start_transaction("reg");   18.        19.         web_reg_find("Search=Body",   20.             "SaveCount=regst",   21.             "Text=欢迎:<a class=\"dropmenu\" id=\"viewpro\" οnmοuseοver= \"showMenu(this.id)\">{username}",   22.             LAST);   23.        24.         web_submit_data("register.aspx",   25.             "Action=http://192.168.0.200/register.aspx?createuser=1" ,   26.             "Method=POST",   27.             "TargetFrame=",   28.             "RecContentType=text/html",     29.             "Referer=http://192.168.0.200/register.aspx",   30.             "Snapshot=t11.inf",   31.             "Mode=HTML",   32.             ITEMDATA,   33.             "Name=username", "Value={username}", ENDITEM,   34.             "Name=password", "Value=112212", ENDITEM,   35.             "Name=password2", "Value=112212", ENDITEM,   36.             "Name=email", "Value={username}@cloud.chen", ENDITEM,   37.             "Name=submit", "Value=建立用户", ENDITEM,   38.             "Name=question", "Value=0", ENDITEM,   39.             "Name=answer", "Value=", ENDITEM,   40.             "Name=realname", "Value=", ENDITEM,   41.             "Name=idcard", "Value=", ENDITEM,   42.             "Name=mobile", "Value=", ENDITEM,   43.             "Name=phone", "Value=", ENDITEM,   44.             "Name=gender", "Value=0", ENDITEM,   45.             "Name=nickname", "Value=", ENDITEM,   46.             "Name=bday_y", "Value=", ENDITEM,   47.             "Name=bday_m", "Value=", ENDITEM,   48.             "Name=bday_d", "Value=", ENDITEM,   49.             "Name=location", "Value=", ENDITEM,   50.             "Name=msn", "Value=", ENDITEM,   51.             "Name=yahoo", "Value=", ENDITEM,   52.             "Name=skype", "Value=", ENDITEM,   53.             "Name=icq", "Value=", ENDITEM,   54.             "Name=qq", "Value=", ENDITEM,   55.             "Name=homepage", "Value=", ENDITEM,   56.             "Name=bio", "Value=", ENDITEM,   57.             "Name=templateid", "Value=0", ENDITEM,   58.             "Name=tpp", "Value=0", ENDITEM,   59.             "Name=ppp", "Value=0", ENDITEM,   60.             "Name=newpm", "Value=radiobutton", ENDITEM,   61.             "Name=pmsound", "Value=1", ENDITEM,   62.             "Name=showemail", "Value=1", ENDITEM,   63.             "Name=receivesetting", "Value=2", ENDITEM,   64.             "Name=receivesetting", "Value=4", ENDITEM,   65.             "Name=invisible", "Value=0", ENDITEM,   66.             "Name=signature", "Value=", ENDITEM,     67.             "Name=sigstatus", "Value=1", ENDITEM,   68.             LAST);   69.        70.         if(atoi(lr_eval_string("{regst}"))>=1)   71.             lr_end_transaction("reg", LR_PASS);   72.         else   73.             lr_end_transaction("reg",LR_FAIL);   74.         return 0;   75.     }  这里的{username}是一个参数,用来存放注册的用户名,在参数列表中设置了该参数的取值方式和信息。