1. 程式人生 > >結合jenkins以及PTP平臺的效能迴歸測試

結合jenkins以及PTP平臺的效能迴歸測試

此文已由作者餘笑天授權網易雲社群釋出。

歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。


1背景簡介

1.1 jenkins

Jenkins是一個用Java編寫的開源持續整合工具。在與Oracle發生爭執後,專案從Hudson專案復刻。Jenkins提供了軟體開發的持續整合服務。它執行在Servlet容器中(例如Apache Tomcat)。它支援軟體配置管理(SCM)工具(包括AccuRev SCMCVSSubversionGitPerforceClearcaseRTC),可以執行基於Apache AntApache 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)).json
        return 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).json
        print '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).json
        return r['deployStatus'],r['status']
 
def check_deploy_result(appId, appSecret,envId,instanceId):
        status = get_status(appId, appSecret,envId,instanceId)
        print 'building .......'
        times = 0
        while 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 = kick
 
grinder.script = Serial.py
grinder.processes = 1
grinder.threads = 1
grinder.runs = 1

script.*代表是待除錯指令碼的名稱,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:
        continue
    else:
        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=False
                break
            else:
                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
then
    echo "result is right"
    exit 0
else
    echo "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+="/"+tmp
    info.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=False
       os.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;
       }
   }

然後需要進行進行資料持久化的操作,這部分的程式碼實現的方式有多重,就不在此贅述,至此完成了自動化迴歸的部分過程,後續的結合哨兵監控以及對資源、效能資料進行進一步分析可以做更多的工作,歡迎有興趣的同學一起來討論。



免費體驗雲安全(易盾)內容安全、驗證碼等服務

更多網易技術、產品、運營經驗分享請點選




相關文章:
【推薦】 HBase原理–所有Region切分的細節都在這裡了