前置说明
该章节只要讲述测试过程中使用的软件和方法。非完全零基础,最好对软件有一定了解。
第1讲 JMeter
用于进行压测的软件。
1.1 JMeter使用
1.1.1 常用参数
参数 | 说明 | 举例 |
---|---|---|
Ramp-up-period | 启动线程所需的时间 | 如设置100个线程,Ramp-up-period=25,即每秒启动4个线程进行请求。 即第一秒4个请求,第二秒8个请求,第三秒12个请求… |
循环次数 | 每个线程执行次数 | 设置了 100 个线程,迭代次数=10,Ramp-up-period=25,那么它表示我将在 25 秒内启动 100 个线程,每个线程迭代 10 次。 即第一秒40个请求,第二秒80个请求,第三秒120个请求… 但实际运行会受到响应时间影响,响应时间慢,1秒执行不到10次就会顺延运行 |
Throughput Shaping Timer | rps 定时器 | start=1 end=200,持续时间是 60。表示我们需要在 60s 内将 RPS(每秒请求数)均匀的从 1 提升到 200 |
固定定时器 | 请求间隔时间 | 两个请求间的间隔时间,如果有两个请求hppt1和http2,一般设置在http2中 |
同步定时器 | 等待足够线程,同一时间发起请求 | 等足够xxx人,同步发送请求给服务器,考量系统瞬时处理这么多请求的能力 https://www.cnblogs.com/yayazhang221/p/13495279.html |
断言持续时间 | 等效于代码内的条件断点 | 在限定的时间内得到响应数据,超时同样为失败 |
1.1.2 性能指标监听
1.1.3 监控服务器CPU、内存
- 下载 https://github.com/undera/perfmon-agent 放到服务器,并执行
./startAgent.sh
启动监控 - JMeter添加监控
- 通过聚合图表查看TPS和CPU、内存的情况
1.1.4 两种压力测试
1.1.4.1 第一种是并发用户模式(虚拟用户模式,即线程数 )
1.1.4.2 第二种是RPS 模式(吞吐量模式)
1.1.5 命令行方式执行(并输出报告)
jmeterGUI 模式下,性能测试的结果往往误差很大,因为 GUI 本身就会消耗一部分资源。所以我们常常用命令行去跑性能脚本,得出结果。
环境:
1:jmeter3.0 版本之后开始支持动态生成测试报表
2:jdk 版本 1.7 以上
3:需要 jmx 脚本文件
操作:
1. 在你的脚本文件路径下
1.1 删除上次导出的 result.jtl 和 /tmp/ResultReport
1.2 执行 cmd 命令:jmeter -n -t test.jmx -l result.jtl -e -o /tmp/ResultReport
2. 参数说明:
-n: 非 GUI 模式执行 JMeter
-t: 执行测试文件所在的位置
-l: 指定生成测试结果的保存文件,jtl 文件格式
-e: 测试结束后,生成测试报告
-o: 指定测试报告的存放位置
注意:结尾的 ResultReport 是自己手动创建的报告文件夹。
示例:jmeter -n -t test.jmx -l result.jtl -e -o ./report
1.1.6 命令行方式执行(动态线程数和循环次数)
jmeter -JthreadNum=100 -Jtime=180 -n -t 命令行动态设置线程数/时间(秒)
1.1.7 jmeter控制请求执行顺序
1.2 JMeter分析
1.2.1 获取系统最大支持用户并发
通过 RPS 定时器或者阶梯加压线程组测试每秒最大的请求数
1.2.2 观察性能拐点
- 在基准测试中,我们会测试出系统某个接口的大概TPS和RT。(假设 平均响应时间: 798ms 平均TPS: 229.7/s)
- 根据第1点的TPS设置RPS,逐步增大到基准值,观察性能拐点。
- 拐点的原因需要考虑带宽,内存,cpu 等等资源的限制
1.2.3 错误率分析
1.2.4 分析性能报告
性能报告的误区:
1.分析响应时间
应该使用中位数或百分比分布统计, 不应该使用平均值(类似平均工资一平均就很高),一旦有异常请求就会拉大平均值。
所以确定指标时应该是:99%请求必须小于xx毫秒。
2、响应时间和吞吐量和成功率应该挂钩
例如:TP99 小于 100ms 的时候,系统可以承载的最大并发数是 1000。
反面例子:
我的系统 tps 可以达到 10000,但是响应时间已经到了 20 秒钟,这样的系统已经不可用了,吞吐量也是没有意义的。
当负载上升的时候,系统会逐渐变的不稳定,响应时间也会变得越来越慢,波动越来越大,而吞吐率却开始下降,包括 CPU 的使用率情况也会如此。
所以,当系统变得不稳定的时候,吞吐量已经没有意义了。
第2讲 sysstat
开源的linux系统监控工具
安装: https://github.com/sysstat/sysstat
2.1 vmstat
查看服务器 CPU的进程数情况(队列长度/不可中断的进程)、CPU的进程中断次数、CPU的上下文切换次数、io情况
Bi/Bo参考值是1000,如果超过了,且wa比较大,说明有io性能瓶颈
In/cs参考值10000以下
2.2 Pidstat
查看进程内部的线程,主动切换和被动切换次数
pidstat -p [pid] -w 1 10
pidstat -t -p [pid] -w 1 10 可以看进程内部的线程
分析:
主动切换多 -> 内存不足/io资源不足
被动切换多 -> 进程多导致CPU时间片不足
2.3 Sar
查看服务器 等待运行的进程数、正在运行的进程和线程数量
sar -q 1
sar -o 28 // 生成sa28文件
2.4 dstat
显示系统中断和上下文切换情况
dstat -y
2.5 watch
分析软件中断的类别
watch -d cat /proc/interrupts
2.6 iostat、swap
第3讲 Linux监控命令
3.1 top
查看服务器 CPU、内存等情况
top 后按1 查看个cpu情况。
top -Hp pid 查看某进程内部占用cpu情况
3.2 show_system_info.sh
查看服务器 CPU、内存、磁盘等情况
#!/bin/bash
#
os_check() {
if [ -e /etc/redhat-release ]; then2
REDHAT=`cat /etc/redhat-release |cut -d' ' -f1`
else
DEBIAN=`cat /etc/issue |cut -d' ' -f1`
fi
if [ "$REDHAT" == "CentOS" -o "$REDHAT" == "Red" ]; then
P_M=yum
elif [ "$DEBIAN" == "Ubuntu" -o "$DEBIAN" == "ubutnu" ]; then
P_M=apt-get
else
Operating system does not support.
exit 1
fi
}
if [ $LOGNAME != root ]; then
echo "Please use the root account operation."
exit 1
fi
if ! which vmstat &>/dev/null; then
echo "vmstat command not found, now the install."
sleep 1
os_check
$P_M install procps -y
echo "-----------------------------------------------------------------------"
fi
if ! which iostat &>/dev/null; then
echo "iostat command not found, now the install."
sleep 1
os_check
$P_M install sysstat -y
echo "-----------------------------------------------------------------------"
fi
while true; do
select input in cpu_load disk_load disk_use disk_inode mem_use tcp_status cpu_top10 mem_top10 traffic quit; do
case $input in
cpu_load)
#CPU利用率与负载
echo "---------------------------------------"
i=1
while [[ $i -le 3 ]]; do
echo -e "\033[32m 参考值${i}\033[0m"
UTIL=`vmstat |awk '{if(NR==3)print 100-$15"%"}'`
USER=`vmstat |awk '{if(NR==3)print $13"%"}'`
SYS=`vmstat |awk '{if(NR==3)print $14"%"}'`
IOWAIT=`vmstat |awk '{if(NR==3)print $16"%"}'`
echo "Util: $UTIL"
echo "User use: $USER"
echo "System use: $SYS"
echo "I/O wait: $IOWAIT"
i=$(($i+1))
sleep 1
done
echo "---------------------------------------"
break
;;
disk_load)
#硬盘I/O负载
echo "---------------------------------------"
i=1
while [[ $i -le 3 ]]; do
echo -e "\033[32m 参考值${i}\033[0m"
UTIL=`iostat -x -k |awk '/^[v|s]/{OFS=": ";print $1,$NF"%"}'`
READ=`iostat -x -k |awk '/^[v|s]/{OFS=": ";print $1,$6"KB"}'`
WRITE=`iostat -x -k |awk '/^[v|s]/{OFS=": ";print $1,$7"KB"}'`
IOWAIT=`vmstat |awk '{if(NR==3)print $16"%"}'`
echo -e "Util:"
echo -e "${UTIL}"
echo -e "I/O Wait: $IOWAIT"
echo -e "Read/s:\n$READ"
echo -e "Write/s:\n$WRITE"
i=$(($i+1))
sleep 1
done
echo "---------------------------------------"
break
;;
disk_use)
#硬盘利用率
DISK_LOG=/tmp/disk_use.tmp
DISK_TOTAL=`fdisk -l |awk '/^Disk.*bytes/&&/\/dev/{printf $2" ";printf "%d",$3;print "GB"}'`
USE_RATE=`df -h |awk '/^\/dev/{print int($5)}'`
for i in $USE_RATE; do
if [ $i -gt 90 ];then
PART=`df -h |awk '{if(int($5)=='''$i''') print $6}'`
echo "$PART = ${i}%" >> $DISK_LOG
fi
done
echo "---------------------------------------"
echo -e "Disk total:\n${DISK_TOTAL}"
if [ -f $DISK_LOG ]; then
echo "---------------------------------------"
cat $DISK_LOG
echo "---------------------------------------"
rm -f $DISK_LOG
else
echo "---------------------------------------"
echo "Disk use rate no than 90% of the partition."
echo "---------------------------------------"
fi
break
;;
disk_inode)
#硬盘inode利用率
INODE_LOG=/tmp/inode_use.tmp
INODE_USE=`df -i |awk '/^\/dev/{print int($5)}'`
for i in $INODE_USE; do
if [ $i -gt 90 ]; then
PART=`df -h |awk '{if(int($5)=='''$i''') print $6}'`
echo "$PART = ${i}%" >> $INODE_LOG
fi
done
if [ -f $INODE_LOG ]; then
echo "---------------------------------------"
rm -f $INODE_LOG
else
echo "---------------------------------------"
echo "Inode use rate no than 90% of the partition."
echo "---------------------------------------"
fi
break
;;
mem_use)
#内存利用率
echo "---------------------------------------"
MEM_TOTAL=`free -m |awk '{if(NR==2)printf "%.1f",$2/1024}END{print "G"}'`
USE=`free -m |awk '{if(NR==3) printf "%.1f",$3/1024}END{print "G"}'`
FREE=`free -m |awk '{if(NR==3) printf "%.1f",$4/1024}END{print "G"}'`
CACHE=`free -m |awk '{if(NR==2) printf "%.1f",($6+$7)/1024}END{print "G"}'`
echo -e "Total: $MEM_TOTAL"
echo -e "Use: $USE"
echo -e "Free: $FREE"
echo -e "Cache: $CACHE"
echo "---------------------------------------"
break
;;
tcp_status)
#网络连接状态
echo "---------------------------------------"
COUNT=`netstat -antp |awk '{status[$6]++}END{for(i in status) print i,status[i]}'`
echo -e "TCP connection status:\n$COUNT"
echo "---------------------------------------"
;;
cpu_top10)
#占用CPU高的前10个进程
echo "---------------------------------------"
CPU_LOG=/tmp/cpu_top.tmp
i=1
while [[ $i -le 3 ]]; do
#ps aux |awk '{if($3>0.1)print "CPU: "$3"% -->",$11,$12,$13,$14,$15,$16,"(PID:"$2")" |"sort -k2 -nr |head -n 10"}' > $CPU_LOG
ps aux |awk '{if($3>0.1){{printf "PID: "$2" CPU: "$3"% --> "}for(i=11;i<=NF;i++)if(i==NF)printf $i"\n";else printf $i}}' |sort -k4 -nr |head -10 > $CPU_LOG
#循环从11列(进程名)开始打印,如果i等于最后一行,就打印i的列并换行,否则就打印i的列
if [[ -n `cat $CPU_LOG` ]]; then
echo -e "\033[32m 参考值${i}\033[0m"
cat $CPU_LOG
> $CPU_LOG
else
echo "No process using the CPU."
break
fi
i=$(($i+1))
sleep 1
done
echo "---------------------------------------"
break
;;
mem_top10)
#占用内存高的前10个进程
echo "---------------------------------------"
MEM_LOG=/tmp/mem_top.tmp
i=1
while [[ $i -le 3 ]]; do
#ps aux |awk '{if($4>0.1)print "Memory: "$4"% -->",$11,$12,$13,$14,$15,$16,"(PID:"$2")" |"sort -k2 -nr |head -n 10"}' > $MEM_LOG
ps aux |awk '{if($4>0.1){{printf "PID: "$2" Memory: "$3"% --> "}for(i=11;i<=NF;i++)if(i==NF)printf $i"\n";else printf $i}}' |sort -k4 -nr |head -10 > $MEM_LOG
if [[ -n `cat $MEM_LOG` ]]; then
echo -e "\033[32m 参考值${i}\033[0m"
cat $MEM_LOG
> $MEM_LOG
else
echo "No process using the Memory."
break
fi
i=$(($i+1))
sleep 1
done
echo "---------------------------------------"
break
;;
traffic)
#查看网络流量
while true; do
read -p "Please enter the network card name(eth[0-9] or em[0-9]): " eth
#if [[ $eth =~ ^eth[0-9]$ ]] || [[ $eth =~ ^em[0-9]$ ]] && [[ `ifconfig |grep -c "\<$eth\>"` -eq 1 ]]; then
if [ `ifconfig |grep -c "\<$eth\>"` -eq 1 ]; then
break
else
echo "Input format error or Don't have the card name, please input again."
fi
done
echo "---------------------------------------"
echo -e " In ------ Out"
i=1
while [[ $i -le 3 ]]; do
#OLD_IN=`ifconfig $eth |awk '/RX bytes/{print $2}' |cut -d: -f2`
#OLD_OUT=`ifconfig $eth |awk '/RX bytes/{print $6}' |cut -d: -f2`
OLD_IN=`ifconfig $eth |awk -F'[: ]+' '/bytes/{if(NR==8)print $4;else if(NR==5)print $6}'`
#CentOS6和CentOS7 ifconfig输出进出流量信息位置不同,CentOS6中RX与TX行号等于8,CentOS7中RX行号是5,TX行号是5,所以就做了个判断.
OLD_OUT=`ifconfig $eth |awk -F'[: ]+' '/bytes/{if(NR==8)print $9;else if(NR==7)print $6}'`
sleep 1
NEW_IN=`ifconfig $eth |awk -F'[: ]+' '/bytes/{if(NR==8)print $4;else if(NR==5)print $6}'`
NEW_OUT=`ifconfig $eth |awk -F'[: ]+' '/bytes/{if(NR==8)print $9;else if(NR==7)print $6}'`
IN=`awk 'BEGIN{printf "%.1f\n",'$((${NEW_IN}-${OLD_IN}))'/1024/128}'`
OUT=`awk 'BEGIN{printf "%.1f\n",'$((${NEW_OUT}-${OLD_OUT}))'/1024/128}'`
echo "${IN}MB/s ${OUT}MB/s"
i=$(($i+1))
sleep 1
done
echo "---------------------------------------"
break
;;
quit)
exit 0
;;
*)
echo "---------------------------------------"
echo "Please enter the number."
echo "---------------------------------------"
break
;;
esac
done
done
第4讲 阿里arthas
- 安装: https://www.cnblogs.com/chuanzhang053/p/16598081.html
- 基础使用: https://developer.aliyun.com/article/764933
https://juejin.cn/post/6938222067955286052
https://testerhome.com/articles/25029
第5讲 jemalloc
- 下载解压: https://github.com/jemalloc/jemalloc/releases
- 系统没有安装autoconf的话要先apt install autoconf
- 执行下面命令安装
./configure --enable-prof --enable-stats --enable-debug --enable-fill make sudo make install // 必须加sudo
- 设置环境变量
export LD_PRELOAD=/usr/local/lib/libjemalloc.so export MALLOC_CONF=prof:true,lg_prof_interval:30,lg_prof_sample:17,prof_prefix:/home/test2/jeprof 将test2文件夹设置可任意读写: chmod 777 test2/
- 重启服务Java -jar xxx.jar
- 开始压测,定期到test2查看生成文件
- 输出文件
jeprof --show_bytes --gif /home/y/share/yjava_jdk/java/bin/java /my/output/directory/jeprof-blah-blah.heap > output.gif 或者jeprof --show_bytes --pdf jdk/bin/java *.heap > leak.pdf。 期间会需要一些依赖库graphviz,装一下
- 使用教程
https://zhuanlan.zhihu.com/p/614227550?utm_id=0 https://mp.weixin.qq.com/s/55slokngVRgqEav6c3TxOA GitHub - jeffgriffith/native-jvm-leaks
- 其它文章
分析堆外内存 https://baijiahao.baidu.com/s?id=1763477601081729978&wfr=spider&for=pc https://baijiahao.baidu.com/s?id=1774522835590104562&wfr=spider&for=pc 性能分析好文: https://zhuanlan.zhihu.com/p/614227550?utm_id=0 jemalloc - 分析C++插件,gperftools看不到c++内存 https://my.oschina.net/openeuler/blog/5949764 设置堆外内存 XX:MaxDirectMemorySize=1g
第6讲 一些分析知识点
6.1 CPU、内存分析
- 当不可中断的进程太多时, 系统CPU会负载高,但实际利用率低
- 当运行的进程(线程)过多时,频繁的上下文切换耗费了大量的 CPU 时间,导致真正用在运算的 CPU 时间片比较少(低 CPU 使用率),却有很多进程在等待运行(高 Load)。
我们做压测的时候一般认为 CPU 利用率和 Load 值是正比的关系,Load 值越高,CPU 利用率就越高。但是事实上有时候 Load 很高,CPU 利用率却比较低(多核更可能出现分配不均的情况)。
6.2 TPS波动时候,分析CPU和内存
如果 “TPS波动大” 或 “周期波动” 或 “暴升高暴跌” 即表示有问题
- TPS波动大: https://testerhome.com/articles/21780
- TPS先高后暴跌后稳定: https://testerhome.com/articles/24707
- cpu长期利用率高: https://testerhome.com/articles/24744
- TPS先高后暴跌后稳定, 并通过线程池优化TPS: https://testerhome.com/articles/24997
- Cpu瓶颈分析: https://testerhome.com/articles/25577
- 内存瓶颈分析:https://testerhome.com/articles/25583
- 其它: https://testerhome.com/articles/22721
相关链接
- 错误率分析: https://testerhome.com/articles/21888
- 命令行方式执行: https://testerhome.com/topics/12114
- Jmeter监控CPU、内存: https://testerhome.com/articles/21314
- 监控CPU上下文切换: https://testerhome.com/articles/21339
- 服务器安装pidstat命令: https://github.com/sysstat/sysstat
- 处理服务器不能访问域名问题: https://www.cnblogs.com/cnyws/p/14029095.html
- 没有sa10文件: https://www.cnblogs.com/guipeng/p/12104918.html