结合jenkins以及PTP平台的性能回归测试
此文已由作者余笑天授權網易云社區發布。
歡迎訪問網易云社區,了解更多網易技術產品運營經驗。
1背景簡介
1.1 jenkins
Jenkins是一個用Java編寫的開源的持續集成工具。在與Oracle發生爭執后,項目從Hudson項目復刻。Jenkins提供了軟件開發的持續集成服務。它運行在Servlet容器中(例如Apache Tomcat)。它支持軟件配置管理(SCM)工具(包括AccuRev SCM、CVS、Subversion、Git、Perforce、Clearcase和RTC),可以執行基于Apache Ant和Apache Maven的項目,以及任意的Shell腳本和Windows批處理命令。Jenkins的主要開發者是川口耕介。Jenkins是在MIT許可證下發布的自由軟件。可以通過各種手段觸發構建。例如提交給版本控制系統時被觸發,也可以通過類似Cron的機制調度,也可以在其他的構建已經完成時,還可以通過一個特定的URL進行請求。
1.2 PTP平臺
?????? 性能測試一直是業界重點關注的部分,但是復雜的性能測試過程卻讓很多人望而生畏:管理測試用例、收集測試數據、進行數據分析、編寫測試報告,每一項都需要耗費很多心血。
于是,PTP平臺就這樣應運而生了,它是網易自主開發的自動化性能測試平臺,致力于將性能測試過程自動化、標準化、一體化,并且將性能測試過程持續起來,進行更多數據分析。
?
2自動化流程
2.1創建任務
?????? QA管理員擁有新建節點權限,如需增加新節點,請找各自的QA管理員。QA管理員在Jenkins上添加一個新節點步驟如下:
(1)點擊鏈接進入
(2)輸入節點名稱,節點名稱通常以服務器hostname或者機器描述命名,比如qa10.server,ddb-23.photo,QA_AutoTest_1等。
(3)選擇Dumb Slave選項,點擊OK按鈕
(4)輸入以下設置:
a.# of executors:輸入執行器的個數(一個或者多個):這個值控制著Jenkins并發構建的數量, 因此這個值會影響Jenkins系統的負載壓力。使用處理器個數作為其值會是比較好的選擇。
b.Remote FS root:輸入slave機器作為持續集成Home的路徑
c.Labels:用來對多節點分組,在目前杭研的應用中,我們一般設置其跟節點名稱一樣
d.用法:一般選只運行綁定到這臺機器的job
e.Launch Method選擇Launch slave agents via Java Web Start
(5)保存
Node Properties可設置環境變量,如果不設置就會使用jenkins主機上全局定義的環境變量,如下圖所示:
更詳細的創建教程可參見wiki:http://doc.hz.netease.com/pages/viewpage.action?pageId=36463105
2.2 自動化環境部署
?????? Jenkins上添加配置好的節點,如下所示:
編寫自動化部署腳本:
import?requests import?time import?os import?sys#?web?is?deployed?on?two?servers,the?arguments?in?url:moduleId,envId,instanceId test_web_arg_1?=?('***','***','***') basi_url?=?'http://omad.hz.netease.com/api'productId?=?'***' envName='urs-regzj-perftest' branch='perftest_jenkins'def?get_token(appId,?appSecret):r?=?requests.get(basi_url?+?'/cli/login?appId=%s&appSecret=%s'?%?(appId,?appSecret)).jsonreturn?r['params']['token']def?deploy_web(appId,?appSecret,moduleId,envId):test_web_url?=?'/cli/deploy?token=%s&moduleId=%s&envId=%s'%(get_token(appId,?appSecret),moduleId,?envId)r?=?requests.get(basi_url?+?test_web_url).jsonprint?'Deploy?result:'def?get_status(appId,?appSecret,envId,instanceId):status_url?=?'/cli/istatus?token=%s&envId=%s&instanceId=%s'%(get_token(appId,?appSecret),?envId,?instanceId)r?=?requests.get(basi_url?+?status_url).jsonreturn?r['deployStatus'],r['status']def?check_deploy_result(appId,?appSecret,envId,instanceId):status?=?get_status(appId,?appSecret,envId,instanceId)print?'building?.......'times?=?0while?status[0]?==?'success':status?=?get_status(appId,?appSecret,envId,instanceId)times?+=?1該過程主要是調用OMAD接口實現了自動化部署,分為以下幾個步驟:
(1)調用/api/cli/login接口獲取個人token信息;
(2)調用/api/cli/vcchange接口對指定產品的指定環境切換成指定分支;
(3)調用/api/cli/ls接口獲取當前用戶有權限的所有產品的所有工程的信息;
(4)調用/api/cli/deploy接口對指定環境的指定分支進行構建部署。
執行方式為python omad.py AccessKeyAccessSecret,其中$AccessKey和$AccessSecret為登錄OMAD后的個人認證信息。
?
2.3 自動化腳本調試
????? 在腳本執行前,我們需要腳本調試這個過程,該過程用來驗證腳本是否能被正確執行,若腳本本來就存在問題等到執行時再去發現問題就可能浪費大量執行時間,因此在這個階段,我們需要執行一次腳本,并驗證腳本是否正確。
????? 首先我們需要將所有的腳本上傳到節點上,并保證該節點機安裝有一些壓測工具,這里以grinder為例,首先需要配置grinder.properties文件,以我的例子來說明:
script1?=?createUser script2?=?updateUinfo script3?=?updateToken script4?=?getUserInfo script5?=?setSpecialRelation script6?=?updateUserID script7?=?getToken script8?=?addFriend script9?=?getFriendRelation script10?=?updateRelationship script11?=?addGroup script12?=?queryTeam script13?=?queryTeamNoUser script14?=?joinTeams script15?=?sendTeamMsg script16?=?SendCustomMessage script17?=?sendGroupMessage script18?=?sendBatchAttachMsg script19?=?sendBatchMsg script20?=?kickgrinder.script?=?Serial.py grinder.processes?=?1 grinder.threads?=?1 grinder.runs?=?1script.*代表是待調試腳本的名稱,Serial.py是主腳本名,grinder.processes ,grinder.threads,grinder.runs 分別是grinder的進程,線程,以及運行次數,因為這部分主要是調試腳本,這里的參數全部設置為1。Serial.py實際是一個串行腳本,它負責順序執行各腳本,代碼如下所示:
from?net.grinder.script.Grinder?import?grinder from?java.util?import?TreeMap #?TreeMap?is?the?simplest?way?to?sort?a?Java?map. scripts?=?TreeMap(grinder.properties.getPropertySubset("script")) #?Ensure?modules?are?initialised?in?the?process?thread. for?module?in?scripts.values():exec("import?%s"?%?module) def?create_test_runner(module):x=''exec("x?=?%s.TestRunner()"?%?module)return?x class?TestRunner:def?__init__(self):self.testRunners?=?[create_test_runner(m)?for?m?in?scripts.values()]#?This?method?is?called?for?every?run.def?__call__(self):#create_test_runner()for?testRunner?in?self.testRunners:?testRunner()?????? 執行完該腳本后需要驗證該腳本的正確性,我的做法是驗證classb-im14-0-data.log下的日志信息,讀取error列的值,具體代碼如下:
info?=?[] f?=?open('result.txt',?'w') path?=?os.getcwd() #print?path path+='/logs' os.chdir(path) path?=?os.getcwd() #print?path file=open('classb-im14-0-data.log','r') count=len(file.readlines()) while(count!=interfaceNum):count=len(file.readlines()) file=open('classb-im14-0-data.log','r') for?line?in?file:info.append(line.strip())if?line.find("Thread")>=0:continueelse:vec=line.split(',')if?vec[5].strip()!='0':#print?vec[5]str=testIdToScene(vec[2].strip())if?str==None:f.write('testId?does?not?exit')excuteflag=Falsebreakelse:str+=('?Error\n')f.write(str)flag=False if?flag==True?and?excuteflag==True:f.write('All?interfaces?have?been?successfully?executed') f.close() file.close()?????? 以上腳本實現了讀取error值的功能,但是在jenkins上即使執行過程中產生錯誤,只要構建過程中每個程序的退出狀態是正常的,仍然會顯示構建成功,為此需要編寫以下腳本,使腳本執行失敗時保證該構建過程同時失敗:
#!/bin/bash if?grep?"All?interfaces?have?been?successfully?executed"?result.txt thenecho?"result?is?right"exit?0 elseecho?"result?is?wrong"exit?1 fi?????? 該腳本在有腳本執行失敗的情況下會強制退出狀態為1,從而使得構建失敗。
?
2.4 自動化腳本執行以及結果收集
?????? 腳本執行需要借助ptp平臺的插件,具體如圖所示:
?????? 執行完成后,需要獲取PTP平臺的執行結果,判斷執行過程中是否有錯誤產生,具體腳本如下所示:
import?os flagSucess=True path?=?os.getcwd() path_pertest=path path+='/projects' path_curr=path f=open("/home/qatest/monitorTools/conf/topnFilesRes.txt") file?=?open('result.txt',?'w') info=[] for?line?in?f:tmp=line.strip()path+="/"+tmpinfo.append(path)path=path_curr for?i?in?info:i+="/logs"os.chdir(i)fileSize?=?os.path.getsize("error_grinder.log")if?fileSize!=0:flagSucess=Falseos.chdir(path_pertest)i?+=?"?make?an?error"file.write(i) if?flagSucess: file.write("All?rounds?have?been?successfully?executed")完成該部分后需要將測試結果持久化到數據庫,這部分的思路是調用平臺的/api/v1.0/round/${roundId}/summary接口,解析json數據,然后插入到數據庫,具體代碼如下。
首先需要利用httpclient獲取該接口的結果然后進行解析:
public?class?GetRoundsAndJasonParse {@SuppressWarnings("finally")public??String??getJasonRes(String?roundID)?throws?HttpException{String?res=null;String?prefix="http://perf.hz.netease.com/api/v1.0/round/";prefix+=roundID;?prefix+="/summary";HttpClient?client?=?new?HttpClient();GetMethod?getMethod?=?new?GetMethod(prefix);try{??client.executeMethod(getMethod);//res?=?new?String(getMethod.getResponseBodyAsString());BufferedReader?reader?=?new?BufferedReader(new?InputStreamReader(getMethod.getResponseBodyAsStream()));StringBuffer?stringBuffer?=?new?StringBuffer();?String?str?=?"";?while((str?=?reader.readLine())!=null){?stringBuffer.append(str);?}?res?=?stringBuffer.toString();?}?catch?(HttpException?e){e.printStackTrace();}finally{getMethod.releaseConnection();return?res;}}public?ArrayList<Perf>?getValue(JsonObject?json,String[]?key){FormattingPerf?fp?=?new?FormattingPerf();ArrayList<Perf>?res=new?ArrayList<Perf>();ArrayList<String>?values=new?ArrayList<String>();??????String?machine_name=null;String?test_id=null;String?tmp=null;try{//if(json.containsKey(key))String?resStr?=?json.get("success").getAsString();if(resStr.equals("false"))System.out.println("Check?your?roundID");else{JsonArray?array=json.get("data").getAsJsonArray();??for(int?i=0;i<array.size();i++){JsonObject?subObject=array.get(i).getAsJsonObject();machine_name=subObject.get("machine_name").getAsString();test_id=subObject.get("test_id").getAsString();if(machine_name.equals("all")&&!test_id.equals("0")){for(int?j=0;j<key.length;j++){tmp=subObject.get(key[j]).getAsString();values.add(tmp);}Perf?perf=new?Perf(values);fp.formatPerf(perf);res.add(perf);values.clear();}}??}}catch?(Exception?e){e.printStackTrace();}??return?res;}@SuppressWarnings("finally")public??ArrayList<Perf>?parseJason(String?jasonbody)?throws?JsonIOException,?JsonSyntaxException{//ArrayList<String>?res=new?ArrayList<String>();ArrayList<Perf>?res=new?ArrayList<Perf>();JsonParser?parse?=new?JsonParser();try{JsonObject?json=(JsonObject)?parse.parse(jasonbody);String[]?key={"test_id","perf_round_id","tps","response_ave","response90","err_rate","mean_response_length"};res=getValue(json,key);}?catch?(JsonIOException?e){e.printStackTrace();}catch?(JsonSyntaxException?e){e.printStackTrace();}finally{return?res;}}然后需要進行進行數據持久化的操作,這部分的代碼實現的方式有多重,就不在此贅述,至此完成了自動化回歸的部分過程,后續的結合哨兵監控以及對資源、性能數據進行進一步分析可以做更多的工作,歡迎有興趣的同學一起來討論。
免費體驗云安全(易盾)內容安全、驗證碼等服務
更多網易技術、產品、運營經驗分享請點擊。
相關文章:
【推薦】?MySQL InnoDB 索引原理
【推薦】?如何用GO實現一個tail -f功能以及相應的思維發散
轉載于:https://www.cnblogs.com/zyfd/p/9887562.html
總結
以上是生活随笔為你收集整理的结合jenkins以及PTP平台的性能回归测试的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Elasticsearch中如何进行排序
- 下一篇: iptables加载顺序问题及优化方法