Java程序性能测试过程002-软件使用


前置说明

该章节只要讲述测试过程中使用的软件和方法。非完全零基础,最好对软件有一定了解。

第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、内存

  1. 下载 https://github.com/undera/perfmon-agent 放到服务器,并执行 ./startAgent.sh 启动监控
  2. JMeter添加监控
    监控1
  3. 通过聚合图表查看TPS和CPU、内存的情况
    监控1

1.1.4 两种压力测试

1.1.4.1 第一种是并发用户模式(虚拟用户模式,即线程数 )

1.1.4.2 第二种是RPS 模式(吞吐量模式)

压力测试1
压力测试2
压力测试3

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 观察性能拐点

  1. 在基准测试中,我们会测试出系统某个接口的大概TPS和RT。(假设 平均响应时间: 798ms 平均TPS: 229.7/s)
  2. 根据第1点的TPS设置RPS,逐步增大到基准值,观察性能拐点。
    观察性能拐点1
    观察性能拐点2
    观察性能拐点3
    观察性能拐点4
  3. 拐点的原因需要考虑带宽,内存,cpu 等等资源的限制

1.2.3 错误率分析

错误率分析1

错误率分析2

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情况

vmstat1

Bi/Bo参考值是1000,如果超过了,且wa比较大,说明有io性能瓶颈
In/cs参考值10000以下

sysstat1

2.2 Pidstat

查看进程内部的线程,主动切换和被动切换次数

pidstat -p [pid] -w 1 10

pidstat -t -p [pid] -w 1 10 可以看进程内部的线程

pidstat1

分析:
主动切换多 -> 内存不足/io资源不足
被动切换多 -> 进程多导致CPU时间片不足

2.3 Sar

查看服务器 等待运行的进程数、正在运行的进程和线程数量

sar -q 1
sar -o 28 // 生成sa28文件

sar1
sar2
sar3
sar4

2.4 dstat

显示系统中断和上下文切换情况

dstat -y

2.5 watch

分析软件中断的类别

watch -d cat /proc/interrupts

2.6 iostat、swap

sysstat2

sysstat3

sysstat4

第3讲 Linux监控命令

3.1 top

查看服务器 CPU、内存等情况

top             后按1 查看个cpu情况。
top -Hp pid     查看某进程内部占用cpu情况

top1

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

第5讲 jemalloc

  1. 下载解压: https://github.com/jemalloc/jemalloc/releases
  2. 系统没有安装autoconf的话要先apt install autoconf
  3. 执行下面命令安装
    ./configure --enable-prof --enable-stats --enable-debug --enable-fill
    make
    sudo make install   // 必须加sudo
  4. 设置环境变量
    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/
  5. 重启服务Java -jar xxx.jar
  6. 开始压测,定期到test2查看生成文件
  7. 输出文件
    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,装一下
  8. 使用教程
    https://zhuanlan.zhihu.com/p/614227550?utm_id=0
    https://mp.weixin.qq.com/s/55slokngVRgqEav6c3TxOA
    GitHub - jeffgriffith/native-jvm-leaks
  9. 其它文章
    分析堆外内存
    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、内存分析

  1. 当不可中断的进程太多时, 系统CPU会负载高,但实际利用率低
  2. 当运行的进程(线程)过多时,频繁的上下文切换耗费了大量的 CPU 时间,导致真正用在运算的 CPU 时间片比较少(低 CPU 使用率),却有很多进程在等待运行(高 Load)。

我们做压测的时候一般认为 CPU 利用率和 Load 值是正比的关系,Load 值越高,CPU 利用率就越高。但是事实上有时候 Load 很高,CPU 利用率却比较低(多核更可能出现分配不均的情况)。

6.2 TPS波动时候,分析CPU和内存

如果 “TPS波动大” 或 “周期波动” 或 “暴升高暴跌” 即表示有问题

相关链接

  1. 错误率分析: https://testerhome.com/articles/21888
  2. 命令行方式执行: https://testerhome.com/topics/12114
  3. Jmeter监控CPU、内存: https://testerhome.com/articles/21314
  4. 监控CPU上下文切换: https://testerhome.com/articles/21339
  5. 服务器安装pidstat命令: https://github.com/sysstat/sysstat
  6. 处理服务器不能访问域名问题: https://www.cnblogs.com/cnyws/p/14029095.html
  7. 没有sa10文件: https://www.cnblogs.com/guipeng/p/12104918.html

文章作者: Alex
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Alex !
  目录