LoadRunner中的時間處理——事物時間、消耗時間等

1. 響應時間

事務是指用戶在客戶端做一種或多種業務所需要的操作集,通過事務函數可以標記完成該業務所需要的操作內容;另一方面事務可以用來統計用戶操作的響應時間,事務響應時間是通過記錄用戶請求的開始時間和服務器返回內容到客戶端時間的差值來計算用戶操作響應時間的,如圖1所示。

這裏的響應時間不包含客戶端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所示。

那麼這些消耗的時間都花在什麼事情上了呢?影響網絡的因素一般包括以下內容: 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中。
運行一下下面的代碼:
Action()
{   
    int i;  
    int baseIter = 100;
    char dude[1000];   
    merc_timer_handle_t timer;   // Examine the total elapsed time of the action,相當於是聲明瞭一個計時器 
    
    //Start transaction  事務開始 
	  lr_start_transaction("Demo"); 
	    
    timer=lr_start_timer();  //計時器開始
     for (i=0;i<=baseIter*1000;i++) {  
           sprintf(dude,"This is the way we waste time in a script =
           %d", i);   13.            }  
    wasteTime=lr_end_timer(timer); //消耗時間,通過調用LoadRunner提供的函數lr_end_timer來停止計時(lr_end_timer這個函數的入參是一個計時器),時間單位爲毫秒

    lr_wasted_time(wasteTime*1000);//將wasteTime轉換爲秒並計入lr的
     
    //Stop transaction  事務結束
	  lr_end_transaction("Demo", LR_PASS);
	  
	  return 0;
}


Wasted Time,當在場景中運行的時候,事務的響應時間會自動扣除Wasted Time 16.     lr_end_transaction("Demo", LR_AUTO);    17.     return 0;    18. } 
其中,lr_start_timer()是一個LoadRunner自帶的時間計數器,它和lr_end_timer()相對應,能夠返回這兩個函數間的時間差。
運行腳本後,等待一段時間腳本運行結束,可以看到以下日誌。
Action.c(18): Notify: Transaction "Demo" started. 

Action.c(27): wasted time is 85.860000  
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()函數將多餘的時間開銷扣除。

例如這樣的一段腳本:

 merc_timer_handle_t timer; //變量聲明  
 lr_start_transaction("Demo");   
 timer=lr_start_timer();  
 lr_load_dll("getkey.dll");  
 lr_save_string(getrandkey(),"key");  
 //通過調用dll獲得密鑰  
 wasteTime=lr_end_timer(timer); 
 lr_wasted_time(wasteTime*1000);  
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中,那麼事務的狀態就應該這樣判斷:
lr_start_transaction("login");
web_reg_find("Search=Body",   
        "SaveCount=loginst",   
        "Text=登錄失敗",   
        LAST);   
//登錄請求  
If(atoi(lr_eval_string("{loginst}"))>=1))   
        lr_end_transaction("login", LR_FAIL);   
else  
         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)"> 
所以在檢查點函數中需要添加這個內容,爲了更好地判斷,還需要把註冊用戶的名字也加進去,最後可以得到下面的代碼:
Action()   
{  
        web_url("註冊",  
            "URL=http://192.168.0.200/register.aspx",   
            "TargetFrame=",   
            "Resource=0",  
            "RecContentType=text/html",   
            "Referer=http://192.168.0.200/",   

            "Snapshot=t2.inf",  
             "Mode=HTML",   
             EXTRARES,  
             "URL=/templates/default/images/check_error.gif", ENDITEM,  
             "URL=/templates/default/images/check_right.gif", ENDITEM,  
             "URL=/images/level/3.gif", ENDITEM,   
             LAST);   
       
         lr_start_transaction("reg");   
       
         web_reg_find("Search=Body",   
             "SaveCount=regst",  
             "Text=歡迎:<a class=\"dropmenu\" id=\"viewpro\" οnmοuseοver= \"showMenu(this.id)\">{username}",   
             LAST);   
       
         web_submit_data("register.aspx",  
             "Action=http://192.168.0.200/register.aspx?createuser=1",  
             "Method=POST",   
             "TargetFrame=",  
             "RecContentType=text/html", 
                          "Referer=http://192.168.0.200/register.aspx",   
             "Snapshot=t11.inf",   
             "Mode=HTML",   
             ITEMDATA,  
             "Name=username", "Value={username}", ENDITEM,   
             "Name=password", "Value=112212", ENDITEM,   
             "Name=password2", "Value=112212", ENDITEM,  
             "Name=email", "Value={username}@cloud.chen", ENDITEM,   
             "Name=submit", "Value=創建用戶", ENDITEM,   
             "Name=question", "Value=0", ENDITEM,   
             "Name=answer", "Value=", ENDITEM,   
             "Name=realname", "Value=", ENDITEM,   
             "Name=idcard", "Value=", ENDITEM,   
             "Name=mobile", "Value=", ENDITEM,   
             "Name=phone", "Value=", ENDITEM,   
             "Name=gender", "Value=0", ENDITEM,   
             "Name=nickname", "Value=", ENDITEM,   
             "Name=bday_y", "Value=", ENDITEM,   
             "Name=bday_m", "Value=", ENDITEM,   
             "Name=bday_d", "Value=", ENDITEM,   
             "Name=location", "Value=", ENDITEM,   
             "Name=msn", "Value=", ENDITEM,   
             "Name=yahoo", "Value=", ENDITEM,   
             "Name=skype", "Value=", ENDITEM,   
             "Name=icq", "Value=", ENDITEM,   
             "Name=qq", "Value=", ENDITEM,   
             "Name=homepage", "Value=", ENDITEM,   
             "Name=bio", "Value=", ENDITEM,  
             "Name=templateid", "Value=0", ENDITEM,   
             "Name=tpp", "Value=0", ENDITEM,   
             "Name=ppp", "Value=0", ENDITEM,  
             "Name=newpm", "Value=radiobutton", ENDITEM,   
             "Name=pmsound", "Value=1", ENDITEM,   
             "Name=showemail", "Value=1", ENDITEM,   
             "Name=receivesetting", "Value=2", ENDITEM,   
             "Name=receivesetting", "Value=4", ENDITEM,   
             "Name=invisible", "Value=0", ENDITEM,   
             "Name=signature", "Value=", ENDITEM,  
             "Name=sigstatus", "Value=1", ENDITEM,   
             LAST);   
       
         if(atoi(lr_eval_string("{regst}"))>=1)   
             lr_end_transaction("reg", LR_PASS);   
         else  
             lr_end_transaction("reg",LR_FAIL);   
         return 0;   
     }    


這裏的{username}是一個參數,用來存放註冊的用戶名,在參數列表中設置了該參數的取值方式和信息。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章