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

此文已由做者餘笑天受權網易雲社區發佈。
php

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


1背景簡介python

1.1 jenkinsweb

Jenkins是一個用Java編寫的開源持續集成工具。在與Oracle發生爭執後,項目從Hudson項目復刻。Jenkins提供了軟件開發的持續集成服務。它運行在Servlet容器中(例如Apache Tomcat)。它支持軟件配置管理(SCM)工具(包括AccuRev SCMCVSSubversionGitPerforceClearcaseRTC),能夠執行基於Apache AntApache Maven的項目,以及任意的Shell腳本和Windows批處理命令。Jenkins的主要開發者是川口耕介。Jenkins是在MIT許可證下發布的自由軟件。能夠經過各類手段觸發構建。例如提交給版本控制系統時被觸發,也能夠經過相似Cron的機制調度,也能夠在其餘的構建已經完成時,還能夠經過一個特定的URL進行請求。數據庫

1.2 PTP平臺json

性能測試一直是業界重點關注的部分,可是複雜的性能測試過程卻讓不少人望而生畏:管理測試用例、收集測試數據、進行數據分析、編寫測試報告,每一項都須要耗費不少心血。
因而,PTP平臺就這樣應運而生了,它是網易自主開發的自動化性能測試平臺,致力於將性能測試過程自動化、標準化、一體化,而且將性能測試過程持續起來,進行更多數據分析。api

2自動化流程安全

2.1建立任務bash

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;
       }
   }複製代碼

而後須要進行進行數據持久化的操做,這部分的代碼實現的方式有多重,就不在此贅述,至此完成了自動化迴歸的部分過程,後續的結合哨兵監控以及對資源、性能數據進行進一步分析能夠作更多的工做,歡迎有興趣的同窗一塊兒來討論。



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

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




相關文章:
【推薦】 收集、分析線上日誌數據實戰——ELK

相關文章
相關標籤/搜索