性能测试工具 Jenkins+Jmeter 集成性能测试

· 2020年05月06日 · 最后由 回复于 2020年11月11日 · 5763 次阅读

环境准备

  1. 本机中 Jenkins 运行在 centOS 7.2 为了不出其他问题可以保持一致,正常情况下别的版本也不会有什么问题
  2. 安装方式我这里是下载 war 包启动,有很多方便的安装不限于这一种方式
  3. 需要 java 环境最好是 1.8
  4. 需要 Jmeter 包,版本不限,但是本地和远程最好都一样
  5. 负载机(slave)机器对 Jenkins 运行机器(master)做免密认证
  6. 所有机器内网打通,目前阿里云和腾讯云都支持的。除非不在一个区有点麻烦

免密认证

下面的 shell 脚本可直接使用,IP 列表替换一下即可。我这里都是内网 IP,写几个实例也无所谓。前提所有机器有一个相同的用户名和密码。然后根据提示输入密码即可如果提示 id_rsa.pub 文件不存在 运行下面命令即可。

ssh-keygen -t rsa
#!/bin/bash

ip="192.168.251.167
192.168.251.219
192.168.251.208
192.168.251.223
192.168.251.180
192.168.251.93
192.168.251.226
192.168.251.160"

read -p "请输入密码:" userpassword

for i in $ip
do
        sshpass -p ${userpassword} ssh-copy-id -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa.pub $i
done

Jenkins 新建几个工具

一键发送文件到所有服务器。比如把本地 Jmeter 发送到负载机上

  1. Jenkins 新建普通项目,添加构建参数 1 个文件参数,2 个字符串参数。可以见截图

  2. 添加构建步骤 Execute shell

cat > $WORKSPACE/ip.txt << EOF
192.168.251.139
192.168.251.218
192.168.251.217
192.168.251.183
EOF

while read ip
do
    scp  $WORKSPACE/$JOB_NAME root@$ip:$filePath$SeverFile_Name &

done < ip.txt

wait
echo -e "time-consuming: $SECONDS    seconds" 
  1. 保存运行即可

切割文件,并发送到远程服务器。(原来业务有券,每次压测不可重复)

  1. Jenkins 新建普通项目,添加构建参数 1 个文件参数,2 个字符串参数。可以见截图

  2. 添加构建步骤 Execute shell

ip=(192.168.251.217
192.168.251.183
192.168.251.222
192.168.251.96
192.168.251.140
192.168.251.153
192.168.251.123
192.168.251.250
192.168.251.38
192.168.251.175
192.168.251.229
192.168.251.105
192.168.251.76
192.168.251.230
192.168.251.60
192.168.251.205
192.168.251.240
192.168.251.193
#192.168.251.139
#192.168.251.218
)

fileNum=${#ip[@]}

mv file3 files.csv
cp files.csv $fileName_New.csv

# 此处需要python环境。把FileSplit.py放到当前workspace路径下。
python FileSplit.py $fileNum $fileRows fileF files.csv

# 此处0 19 因为有20台服务器。服务器数量暂时固定所以没有动态计算上面服务器个数。
for i in `seq 0 19 `
do 
    scp  $WORKSPACE/fileF$i.csv root@${ip[$i]}:/MCD/Jmeter/Product/$fileName_New.csv  &
done

wait
echo -e "time-consuming: $SECONDS    seconds"
# FileSplit.py
# coding:utf-8
from datetime import datetime
import sys
from os import path

def Main():
#    source_dir = 'C:/Users/WL/Desktop/code/file/fileTest.csv'
#    target_dir = 'C:/Users/WL/Desktop/code/file/'
#    print(path.sep, sys.path[0])

    target_dir = sys.path[0]+path.sep
    # 获取文件数,每个文件行数,文件名字,原文件名称
    files, rows, name, source_dir = int(sys.argv[1]), int(sys.argv[2]), target_dir+sys.argv[3], sys.argv[4]
    # 计数器,文件后缀,存放数据
    flag, index, dataList = 0, 0, []
    print("切割为{0}个文件,每个文件{1}行".format(files, rows))
    print("Start {}".format("="*20), datetime.now().strftime('%Y-%m-%d %H:%M:%S'))

    with open(source_dir, 'r') as f_source:
        for line in f_source:
            flag += 1
            dataList.append(line)
            if flag == rows:
                with open(name + str(index) + ".csv", 'w+') as f_target:
                    for data in dataList:
                        f_target.write(data)
                print(name + str(index) + ".csv")
                index += 1
                flag = 0
                dataList = []
            if index == files:
                break

    print("finish {}".format("="*20), datetime.now().strftime('%Y-%m-%d %H:%M:%S'))


if __name__ == "__main__":
    Main()    

批量执行 shell 项目,用于批量重启 Jmeter-server,以及批量修改服务器配置等

  1. Jenkins 新增普通项目,添加构建步骤 Execute shell
cat > $WORKSPACE/ip.txt << EOF
192.168.251.139
192.168.251.218
192.168.251.217
192.168.251.183
192.168.251.222
192.168.251.96
192.168.251.140
192.168.251.153
192.168.251.123
192.168.251.250
192.168.251.38
192.168.251.175
192.168.251.229
192.168.251.105
192.168.251.76
192.168.251.230
192.168.251.60
192.168.251.205
192.168.251.240
192.168.251.193
EOF

while read ip
do
    # 重启Jmeter,需要服务器存在AutoJmeter.sh文件,在下面提供
    ssh -n  -o StrictHostKeyChecking=no   root@$ip "sh /data/shell/AutoJmeter.sh restart" &
    # 释放linux缓存占用内存,在下面提供
#   ssh -n  -o StrictHostKeyChecking=no   root@$ip "sh /data/shell/dropCaches.sh" &
    # 更改 内核信息-不了解勿用,需重启or重载生效,此处不提供
#   ssh -n  -o StrictHostKeyChecking=no   root@$ip "echo 15 > /proc/sys/net/ipv4/tcp_fin_timeout 
    # 同上
#   echo 15 > /proc/sys/net/ipv4/tcp_tw_timeout
done < ip.txt

wait
echo -e "time-consuming: $SECONDS    seconds"
# dropCaches.sh
echo 1 > /proc/sys/vm/drop_caches
echo 2 > /proc/sys/vm/drop_caches
echo 3 > /proc/sys/vm/drop_caches

# AutoJmeter.sh
# description: jmeter agent
myip=`/sbin/ifconfig -a|grep inet|grep -v 127.0.0.1|grep -v inet6|awk '{print $2}'|tr -d "addr:"`
cmd="/MCD/Jmeter/bin/jmeter-server -Djava.rmi.server.hostname=$myip"
start(){
  echo "Start" $myip
  $cmd > /dev/null &
}

stop(){
    jmeter_pid=`ps aux | grep jmeter-server | grep -v grep | awk '{print $2}'`
    for pid in $jmeter_pid;do
    kill -9 $pid
    echo "Stop"$myip:$pid
    done
}

act=$1
case $act in
 'start')
   start;;
 'stop')
   stop;;
 'restart')
   stop
   sleep 2
   start;;
  *)
   echo '[start|stop|restart]';;
esac

上传更新 Jmeter 脚本到服务器

  1. Jenkins 新增普通项目,增加构建 1 个文件构建参数,2 个字符串参数,见下图

  2. 添加构建步骤 Execute shell

mv file $JENKINS_HOME/workspace/$filePath/$fileName

wait
echo -e "time-consuming: $SECONDS    seconds"
  1. 保存运行就可以了。

Jenkins 与 Jmeter 结合脚本配置

Jmeter 脚本要求

  1. 编写压测脚本要求:每个场景单独使用一个线程组!!!!!非常重要,1 个线程组就是 1 个场景
  2. 测试计划下新增 “用户定义的变量”,“HTTP 请求默认值”,一定按照顺序来。用户定义的变量在测试计划下面第一个。第二个是 HTTP 请求默认值,其次才是线程组等元件
  3. 用户定义的变量新增变量有多少个场景(线程组)就新增多少个变量,变量名称对应线程组名称(全英文,不可有空格),对应的值是 ${__property(线程组名称,,)},见截图
  4. filePath,host,port,Ramp_Up,RunTime 这 5 个变量不是线程组的名称,但是是必须的。格式见上图。Jenkins 里用,后面讲。
  5. HTTP 请求默认值元件里面服务器地址写 ${host},端口 ${port}。必须要这样写!!见下图
  6. 线程组里的线程数,ramp_up,持续时间。分别写 ${线程组名称},${Ramp_Up},${RunTime},见下图。不同线程组只是线程数不同。ramp_up,持续时间都是相同的。
  7. 参数化文件配置
  8. 确保脚本没有问题。保存即可

Jenkins 配置

  1. Jenkins 新增普通项目,增加字符串参数。数量由场景(线程组)个数 +4 确定
  2. 把脚本里面用户定义的变量所有名称分别对应字符串参数,一个个对应上。默认值是 0
  3. host:是服务端 IP 或者域名地址,可以给个默认值,避免每次输入。或者改成选项参数,配置多个地址
  4. port:服务器端口。开发没有给的,按照协议类型确认。http 默认 80,https 默认 443。
  5. Ramp_Up:启动线程花费时间,根据场景自定义,默认 0 也可以
  6. RunTime:压测脚本执行时间,单位是秒
  7. 新增构建步骤,Execute shell
echo 生成当前日期
date=$(date +%Y%m%d%H%M)
echo 当前时间: $date

if [ ! -d "$JENKINS_HOME/workspace/$JOB_NAME/Report" ];then
mkdir $JENKINS_HOME/workspace/$JOB_NAME/Report
else
echo "Skip"
fi

echo 配置地址
# Jenkins程序在服务器上面的路径
jmxPath=/root/.jenkins/workspace/$JOB_NAME/pickup.jmx
# 生成的报告路径,可在Jenkins对应项目工作空间内直接打开
ReportPath=$JENKINS_HOME/workspace/$JOB_NAME/Report/$date
# Jmeter运行结果文件。比较大。建议服务器重新挂个ssd磁盘专门保存。也可定期清理
jtlPath=/data2/jtl/$JOB_NAME$date.jtl

echo 创建日期文件夹
mkdir $ReportPath


# Jmeter非GUI启动命令 -nt 
# -l 指定日志路径,-R 负载机IP这里直接指定。不需要更改.propreties文件
# 重点是 -G 参数,对应上面Jenkins所有的构建参数,格式:-GGetDish=$GetDish 这是一个参数
# filePath:这里如果脚本有参数化文件。要用这个参数。-GfilePath=服务器保存参数化文件路径
echo 执行Jmeter
cd /MCD/Jmeter/bin
sh jmeter.sh -nt $jmxPath -l $jtlPath -GoffLineRedeem=$offLineRedeem -GGetCouponDetail=$GetCouponDetail -GGetStoresExtra=$GetStoresExtra -GGetDynamicBaseMenuInfo=$GetDynamicBaseMenuInfo -Ghost=$host -Gport=$port  -GfilePath=/MCD/Jmeter/Product/ -GGetdish=$Getdish -GOrderReview=$OrderReview -GOrderSubmit=$OrderSubmit -GOrderDetail=$OrderDetail -GonePopUp=$onePopUp -GonGoingOrder=$onGoingOrder -GGetPackageDish=$GetPackageDish -GGetSingleDish=$GetSingleDish -GGetDishToShopCar=$GetDishToShopCar -GLogin=$Login -GRamp_Up=$Ramp_Up -GRunTime=$RunTime -GhomePage=${homePage} -GLoggerTest=${LoggerTest} -GnewOrderSubmit=$newOrderSubmit -GnewOrderReview=$newOrderReview -R 192.168.251.218,192.168.251.139,192.168.251.217,192.168.251.183,192.168.251.222,192.168.251.96,192.168.251.140,192.168.251.193 -e -o $ReportPath

# 输出报告路径
#echo ${JENKINS_URL}view/MCD58/job/$JOB_NAME/ws/Report/$date/index.html

  1. Jenkins 项目保存之后。参数都是默认运行一次(相当于初始化)。在用上传脚本的项目上传脚本。
  2. 上传脚本。启动即可压测。下图运行日志

报告查看

  1. 进入 Jenkins 项目内,点击工作空间(workspace)看到会有个 Report 文件夹和脚本,进入 Report 文件夹就是根据压测启动时间生成的报告文件夹,如下图

扩展

记录请求日志,方便定位业务问题(稳定性压测不建议使用,beanshell 动态编译运行有内存泄漏风险)

  1. 测试计划下新增 beanshell 后置处理器,放到最后一个线程组下面,如下图
import java.util.Date;
import java.text.SimpleDateFormat;

//  获取请求Data
String requestData = prev.getSamplerData();
//  截取requestBody
requestData = requestData.replaceAll("data:","");
requestData = requestData.replaceAll("no cookies","");
requestData = requestData.replaceAll("[\n\\s?]","");
// 获取响应结果
String lableName = prev.getSampleLabel();
String responseData = prev.getResponseDataAsString().replaceAll("[\\n\\s]","");
String runTime = "/MCD/Jmeter/Product/log/Pickup"+bsh.args[0]+".csv";
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");//设置日期格式
String nowTime = df.format(new Date()); // new Date()为获取当前系统时间

FileWriter fstream = new FileWriter(runTime, true);
BufferedWriter out = new BufferedWriter(fstream);
out.write(nowTime + lableName + "requestData=="  + requestData + "responseData==" + responseData);
out.write(System.getProperty("line.separator"));
out.close();
fstream.close();
  1. Jenkins 新增普通项目,新增 1 个字符串参数。2 个选择参数,Project 和上面的 beanshell 中 runTime 参数字段的路径 Pickup 对应。这里查看日志可以看不同项目。通过文件夹区分。
  2. 构建步骤 Execute shell
cat > $WORKSPACE/ip.txt << EOF
$SlaveIP
EOF

while read ip
do
    ssh -n  -o StrictHostKeyChecking=no   root@$ip "tail -n 10000 /MCD/Jmeter/Product/log/$Project$date.csv"

done < ip.txt



wait
echo -e "time-consuming: $SECONDS    seconds"

  1. 构建时间参数写压测执行的时间即可,格式:“201902031856”,查看构建日志即可

查看所有 slaveJmeter 日志。脚本报错。异常等信息

  1. Jenkins 新建普通项目,新增构建步骤 Execute shell
cat > $WORKSPACE/ip.txt << EOF
#$SlaveIP
192.168.251.139
192.168.251.218
192.168.251.217
192.168.251.183
192.168.251.222
192.168.251.96
192.168.251.140
192.168.251.153
192.168.251.123
192.168.251.250
192.168.251.38
192.168.251.175
192.168.251.229
192.168.251.105
192.168.251.76
192.168.251.230
192.168.251.60
192.168.251.205
192.168.251.240
192.168.251.193
EOF

echo 生成当前日期
date=$(date +%Y%m%d%H%M)
echo 当前时间: $date
mkdir $WORKSPACE/log/$date

while read ip
do
    scp root@$ip:/root/jmeter-server.log $WORKSPACE/log/$date/${ip}_Jmeter_Server.log &
done < ip.txt

cp /MCD/Jmeter/bin/jmeter.log $WORKSPACE/log/$date/MasterJmeter.log

wait
echo -e "time-consuming: $SECONDS    seconds"
  1. 会在工作空间内新建一个当前时间的文件夹,把每个 slave 的运行日志放进去,以 IP 命名对应文件。

注意事项

脚本内用户定义的变量名称,和线程组名称,线程数变量,Jenkins 构件参数名称,Jmeter 启动脚本的—G 参数务必相同。不能有空格,没有任何空格。

记录请求的 beanshell 会影响 Jmeter 本身性能。

Jenkins 打开报告如果图片空白,可以在项目内加个构件步骤 Execute system Groovy script,或者在启动 Jenkins 的时候加参数指定。或者启动后在 Jenkins 执行安全命令啥的忘记了。也可自行百度,原因是 Jenkins 屏蔽外部 Js 好像。

Jenkins 执行线程数量必须设置为 1,Jenkins 管理 -- 系统设置 -- 执行者数量

RunTime(压测执行时间)最好不要低于 10 秒。原来启动的 slave 过多 10 秒可能刚刚启动完成。。。

System.setProperty("hudson.model.DirectoryBrowserSupport.CSP", "")
# 我的Jenkins启动命令。war包形式
nohup java -Xms2g -Xmx2g -Dhudson.model.DirectoryBrowserSupport.CSP="sandbox; default-src 'self';" -jar jenkins.war --httpPort=8002 >jenkins.log 2>jenkins.log &

写的有点快。可能哪里图片少了或者啥。可以评论我补充一下。IP 地址我就不隐藏了。都是内网 ip。文件路径也不隐藏了。前公司服务器都回收过了。按照我这个改改,有点动手和百度能力都能搞好。

后面有机会补充 Jenkins 账号管理。赋权压测给开发

有机会在补充线程设置为 1,压测过程中其他任务都会等待的解决方法 -- 加 Jenkins 负载机,都搞好了。改天再更新怎么操作··

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 5 条回复 时间 点赞
#1 · 2020年05月08日 Author

报告还用过 influxdb+grafana 的方式,可惜业界比较认 Jmeter 原生的报告。grafana 展示的不太认可。加上碰到了一些日志存储之类的问题。放弃。。
看到其他人用 es+FileBeats+Kibana 收集日志的方式。有机会自己也要试下

jmeter 集群容器化部署 中提及了此贴 06月08日 14:36
jmeter 负载压测 中提及了此贴 09月04日 16:17

为什么用 jenkins 去驱动 jmeter 插件执行性能测试?你的目的时什么?我能想到的,你这个后期维护非常麻烦,一堆配置想想就头疼

#6 · 2020年09月25日 Author
xuewuhe 回复

没有性能测试平台支持的情况下, 用 jenkins 驱动 jmeter 也还可以吧。起码比每次都服务器上执行那么长的命令方便很多

请问分别创建那么多 ThreadGroup 是传了哪些就跑哪些吗

#5 · 2020年11月11日 Author
kanchi240 回复

是的。方便区分场景。一个线程组就是一个场景。传线程数量进去就会启动, 默认 0Jmeter 自动不执行当前线程组

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册