1. 程式人生 > >LoadRunner中的時間處理——事物時間、消耗時間等

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" onmouseover="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\" onmouseover= \"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}是一個引數,用來存放註冊的使用者名稱,在引數列表中設定了該引數的取值方式和資訊。