结合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)).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切分的细节都在这里了
结合jenkins以及PTP平台的性能回归测试的更多相关文章
- 【持续集成】使用Jenkins实现多平台并行集成
使用Jenkins实现多平台并行集成 二月 15, 2012 暂无评论 我们的后端C应用都是支持跨平台的,至少目前在Linux和Solaris上运行是没有问题的,这样一来我们在配置持续集成环境时就要考 ...
- Jenkins结合.net平台综合之监听git仓库并自动摘取最新代码编译
前面章节我们讲解了Jenkins结合.net平台工具以及一些第三方工具实现项目自动还原,自动编译,自动测试和自动发布.然而实现自动化还有一个关键的步骤就是监听源码仓库变化然后从仓库拉取最新代码,然后再 ...
- 基于Kubernetes/K8S构建Jenkins持续集成平台(上)-1
基于Kubernetes/K8S构建Jenkins持续集成平台(上)-1 Jenkins的Master-Slave分布式构建 什么是Master-Slave分布式构建 Jenkins的Master-S ...
- 基于Kubernetes/K8S构建Jenkins持续集成平台(上)-2
基于Kubernetes/K8S构建Jenkins持续集成平台(上)-2 Kubernetes实现Master-Slave分布式构建方案 传统Jenkins的Master-Slave方案的缺陷 Mas ...
- 基于Kubernetes/K8S构建Jenkins持续集成平台(下)
基于Kubernetes/K8S构建Jenkins持续集成平台(下) Jenkins-Master-Slave架构图回顾: 安装和配置NFS NFS简介 NFS(Network File System ...
- 一.Jmeter+Ant+Jenkins搭建持续集成接口性能自动化测试
微创新作品信息 1)微创新作品描述 A.为什么诞生: 1. 接口测试是测试系统组件间接口的一种测试.接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点.测试的重点是要检查数据的交换, ...
- Jmeter+jenkins接口性能测试平台实践整理(一)
最近两周在研究jmeter+Jenkin的性能测试平台测试dubbo接口,分别尝试使用maven,ant和Shell进行构建,jmeter相关设置略. 一.Jmeter+jenkins+Shell+t ...
- 一个基于集成jenkins的测试平台
(一)先看测试业务的情况: 有各种各样的任务包括代码构建.部署搭建.单元测试.功能自动化测试(包括许多模块的功能自动化测试,有十几个居多),性能测试.正确性验证:复杂一点的是这些任务在不同的测试阶段中 ...
- Jenkins结合.net平台之ftp客户端
上一节我们讲解了如何配置ftp服务端,本节我们讲解如何使用winscp搭建ftp客户端,为什么使用winscp而不是filezilla客户端版,前面我们简单说过,这里不再赘述. 下载winscp以后我 ...
随机推荐
- something important
docker run ubuntu /bin/echo 'Hello world' 运行这条命令,docker做了什么 Well, Docker containers only run as long ...
- 深入理解JVM - 早期(编译期)优化
Java“编译期”是一段“不确定”的操作过程:可能是指一个前端编译器(编译器的前端)把*.java文件转变为*.class文件的过程:可能是指虚拟机的后端运行期编译器(JIT编译器,Just In T ...
- php gizp压缩传输js和css文件
1. [代码][PHP]代码 <?php /** * 完整调用示例: * 1.combine.php?t=j&b=public&fs=jslib ...
- eclipse自动提示功能没了的解决办法
由于重新配置了环境,并且eclipse也是装的4.2的,今天用的时候发现了,居然没有自动提示功能,也就是当一个对象居然点不出他的相关方法.后来网上搜索了下,成功的 办法是. 1.我window-> ...
- 关于float与double区别
Problem A: 啤酒和饮料 Time Limit: 1 Sec Memory Limit: 128 MB Submit: 175 Solved: 29 [Submit][Status][We ...
- AngularJS学习笔记(一) 关于MVVM和双向绑定
写在前面: 因为需要开始学习ng,之前在知乎上听大神们介绍ng的时候说这个坑如何的大,学了一阵(其实也就三天),感觉ng做的很大很全,在合适的情境你可以完全使用ng搞定一切.这一点从诸如jqLite之 ...
- PL/SQL学习笔记_03_存储函数与存储过程
ORACLE 提供可以把 PL/SQL 程序存储在数据库中,并可以在任何地方来运行它.这样就叫存储过程或函数. 存储函数:有返回值,创建完成后,通过select function() from dua ...
- php常用函数htmlspecialchars、strip_tags、addslashes解析
本文章向大家介绍php开发中经常使用到的字符串函数htmlspecialchars.strip_tags.addslashes的使用方法及他们之间的区别,需要的朋友可以参考一下. 1.函数strip_ ...
- Javascript-- jQuery事件篇(2)
jQuery表单事件之blur与focus事件 单处理事件focusin事件与focusout事件,同样用于处理表单焦点的事件还有blur与focus事件 它们之间的本质区别: 是否支持冒泡处理 举个 ...
- 了解fiddler:实现简单的抓包测试
fiddler是一款轻型的抓包软件 本文介绍几个常用的功能:(相信图片更直观点,上图片,右键在新标签页中打开,查看高清大图) 通过composer,我们可以修改http头部信息,修改post(),ge ...