移动性能测试 shell 管理 monkey 压力测试

浮云 · 2015年10月29日 · 最后由 浮云 回复于 2016年08月11日 · 162 次阅读
本帖已被设为精华帖!
终于抽出时间写了早计划要写的 monkey 压力测试方案;自己设计完成建立的测试模式,集成到了压力测试任务;已经建立了还算完善的方案。

方案流程:设计 monkey 语句-->套用脚本方案执行-->收集结果脚本统计分析-->分析监控数据-->提交 bug。

  • 修改 moneky.sh(主控脚本)中的 monkey 语句后批处理一键执行。
  • 方案不仅包含了执行,还包括了 ANR/Force Closed/tombstone 的归类筛查,快速去重。
  • 执行方式易于交付给懂 monkey 语法可设计 monkey 语句压测逻辑的执行人员,或固定脚本执行。

一、monkey 压力测试执行需求设计

设计调整方案的持续过程中,根据测试最终目的和提高执行及结果分析效率的需求,不断给自己提出目标需求,不断改进如下:

1、多条 monkey 语句依次执行

逻辑:执行 monkey 语句-->判定 monkey 进程产生-->等待 monkey 进程结束-->循环。
控制脚本:shell 脚本控制流程逻辑,设备端后台运行(&符后台执行)
优势

  • 组合不同 monkey 脚本进行整体测试;
  • 设备端独立完成;
  • 可连接线充避免手机电量不足;
  • 可以对手机状态和 app 状态进行控制;
  • 可同步进行 log 分析处理;
  • 等多进程操作。

2、monkey 的 log 除记录操作同时记录监控到的 ANR 和 crash

方法

monkey -p 包名 --throttle 500 --ignore-crashes --monitor-native-crashes --ignore-timeouts --ignore-native-crashes 事件比例 -v -v -v 事件数 1>>monkey.log 2>&1 &
注:其中包名,事件比例,事件数需定制调整;正常 monkey 的 log 为 1 输出流,异常的为 2,追加写在同一个文件内。

筛查:monkey.log 查找 anr 及 crash 了解异常前的 monkey 操作及异常 log。

3、轮询筛查 logcat 减少长时间抓取的 log 量

方式:控制间隔轮询筛查 logcat 有 ANR/FC/Tombstone 则保留,否则删除。
添加位置:“等待 moneky 进程结束” 阶段
统计字段:ANR——"ANR in";Force Closed——"FATAL EXCEPTION";tombstone——"Build fingerprint";
额外获取:bugreport;判定时刻截图

4、当前状态检查处理异常情况

按需设计:锁屏则自动解锁;存储空间不够则删除下载目录的文件;等。

5、初始化及控制执行页面

按需设计:执行前初始化 app 状态,存档;已运行状态执行/未运行状态执行 monkey;显示的 Activity 非目标页面,操作页面返回;等。

注:当前项目未有此需求,每次测试手工初始化,暗码开启离线 log 等。所以一直未设计这部分,实现逻辑上是可行的。

逻辑:shell 脚本写具体功能的 case 函数在 monkey 语句前执行;通过 SurfaceFlinger,window 等服务获取状态判定处理;通过 case 语句涵盖所有场景的处理方式,依据判定信息进行选择处理;放在等待 monkey 进程结束的判定循环内。

6、配合监控脚本抓取数据

方式:监控进程和 monkey 脚本进程以多进程形式运行;依次运行后台脚本即可
同步:监控进程触发条件停止,触发条件为指定文件名存在,moneky 脚本结束前创建此文件通知监控停止。
监控内容:CPU/内存——mcm 方案 (https://testerhome.com/topics/2976) ;电量监控——获取对应信息格式化为 CSV 输出走势图,判定关机/死机/重启是否是充电异常导致电量不足;等

7、对收集到的 log 文件进行脚本分析格式化归类

方式:由于 ANR/Force Close/Tombstone 是固定格式打印 log;根据格式输出的形式简单的归类统计。
**shell 逻辑:使用 grep+sed+awk,组织逻辑完成了格式化归类为 CSV。

二、monkey 压力测试执行代码设计

1、monkey 执行控制脚本

#!/system/bin/sh

#添加备注到comments.csv
comment(){
if [ ! -f $testresult/comments.csv ];then
    echo "Date,Error Type,Log Name" >$testresult/comments.csv
fi
echo "`date +%m-%d" "%H":"%M":"%S`,$1,$2" >>$testresult/comments.csv
}

#锁屏函数$1:0--lock;1--unlock;按显示内容判定处理,显示壁纸并且无其他activity则滑屏;黑屏则电源键唤醒加滑屏。
lock(){
local tmp=`wm size|busybox awk '{print $NF}'`
local x=`echo $tmp|busybox awk -F "x" '{print $1}'`
local y=`echo $tmp|busybox awk -F "x" '{print $2}'`
while true;do
    case `dumpsys SurfaceFlinger|grep "|....|"|busybox awk 'BEGIN{r="o"}{if($NF=="com.android.systemui.ImageWallpaper"||$NF=="com.android.systemui.keyguard.leui.KeyguardImageWallpaper")r="l"}END{print NR-1 r}'` in
        "1o")
            local state=0
        ;;
        "3l")
            local state=1
        ;;
        *)
            local state=2
        ;;
    esac
    case $1 in
        0)
            if [ $state -ne 0 ];then
                input keyevent 26
            else
                break
            fi
            sleep 1
        ;;
        1)
            if [ $state -eq 0 ];then
                input keyevent 26&&sleep 1&&input swipe $((x/2)) $((4*y/5)) $((x/2)) $((y/5))
            elif [ $state -eq 1 ];then
                input swipe $((x/2)) $((4*y/5)) $((x/2)) $((y/5))
            else
                break
            fi
            sleep 1
        ;;
    esac
done
}

#筛查指定log文件的函数
output_error(){
if [ -f $1 ];then
    local times1=`grep -c "ANR in" $1`
    anr=$((anr+times1))
    local times2=`grep -c "FATAL EXCEPTION" $1`
    fatal=$((fatal+times2))
    local times3=`grep -c "Build fingerprint" $1`
    fingerprint=$((fingerprint+times3))
    if [ $times1 -ne 0 -o $times2 -ne 0 -o $times3 -ne 0 ];then
        if [ ! -d $testresult/log ];then
            mkdir $testresult/log
        fi
        time_name="`date +%Y%m%d%H%M%S`"
        busybox mv $1 $testresult/log/$time_name.log
        bugreport >$testresult/log/$time_name.bugreport
        screencap $testresult/log/$time_name.png
        local result="Error:"
        if [ $times1 -ne 0 -a ! -z "`ls /data/anr 2>/dev/null`" ];then
            mkdir $testresult/log/$time_name
            busybox mv /data/anr/* $testresult/log/$time_name/
            local result=$result"ANR;"
        fi
        if [ $times2 -ne 0 ];then
            local result=$result"FATAL;"
        fi
        if [ $times3 -ne 0 -a ! -z "`ls /data/tombstones 2>/dev/null`" ];then
            if [ ! -d $testresult/log/$time_name ];then
                mkdir $testresult/log/$time_name
            fi
            busybox mv /data/tombstones/* $testresult/log/$time_name
            local result=$result"tombstones;"
        fi
        comment "$result" "$time_name.log"
    else
        rm $1
    fi
fi
}

#创建$log_f函数,
c_log(){
if [ -f $log_f ];then
    while true;do
        if [ -f $testresult/check_error.log ];then
            sleep 1
        else
            break
        fi
    done
    local p=`busybox ps`
    kill -9 `echo "$p"|grep "logcat -f $log_f -b all -v time"|busybox awk '{print $1}'`
    busybox mv $log_f $testresult/check_error.log
fi
if [ $1 -eq 0 ];then
    logcat -c
    logcat -f $log_f -b all -v time &
fi
if [ -f $testresult/check_error.log ];then
    output_error $testresult/check_error.log
    echo -e "local mac=$mac
build id=$build
ANR=$anr
Fatal=$fatal
tombstone=$fingerprint" >$testresult/statistics.txt
fi
}

#可用空间少于80%,删除ledown下载的视频文件
clear_files(){
if [ `busybox df /sdcard|busybox awk '{r=substr($(NF-1),1,length($(NF-1))-1)}END{print r+0}'` -ge 80 ];then
    rm /sdcard/ledown/*
fi
}


#Global variables
anr=0
fatal=0
fingerprint=0
log_f="/data/check_error.log"
if [ -f $log_f ];then
    rm $log_f
fi
testresult="/data/monkey"
if [ -d $testresult ];then
    rm -r $testresult
fi
mkdir $testresult
mac=`cat /sys/class/net/*/address|busybox sed -n '1p'|busybox tr -d ':'`
build=`getprop ro.build.fingerprint`
if [ -z $build ];then
    build=`getprop ro.build.description`
fi

#等待monkey进程执行完毕函数,如果锁屏则自动解锁
waitmonkey(){
local a=0
while [ $a != 1 ];do
    local a=`/system/bin/ps |grep -c "com.android.commands.monkey"`
done
while [ $a != 0 ];do
    c_log 0
    lock 1
    clear_files
    sleep 10
    local a=`/system/bin/ps |grep -c "com.android.commands.monkey"`
done
c_log 1
}

#等待指定时间,间隔10S筛查log
wait_time(){
local chek_begin=`busybox awk -F. 'NR==1{print $1}' /proc/uptime`
local check=$chek_begin
c_log 0
while true;do
    local chek_end=`busybox awk -F. 'NR==1{print $1}' /proc/uptime`
    if [ $((chek_end-chek_begin)) -ge $1 ];then
        clear_files
        c_log 1
        break
    elif [ $((chek_end-check)) -gt 10 ];then
        local check=$chek_end
        clear_files
        c_log 0
    else
        sleep 1
    fi
done
}

busybox pkill monkey
#以下为monkey脚本逻辑

#如锁屏先解锁
lock 1
#记录开始时间
comment "package" "start test"
#-p package限定执行范围自行调整;事件间隔一般手机500ms,TV遥控器操作频率不高500到1500ms之间按需设定;事件比例按测试目的配置;事件数按时常预估情况设定,此情况25000在1小时左右;
monkey -p package --throttle 500 --ignore-crashes --monitor-native-crashes --ignore-timeouts --ignore-native-crashes --pct-touch 10 --pct-trackball 20 --pct-motion 15 --pct-flip 20 --pct-appswitch 30 --pct-anyevent 5 -v -v -v 25000 1>>$testresult/monkey.log 2>&1 &
#等待结束
waitmonkey
#记录结束时间
comment "com.letv.app.appstore"
#锁屏静置十分钟,自己设定的,下一条前让手机休息下,并且可通过cpu内存监控关注停止monkey后内存是否持续上涨。
lock 0
comment "lock" "lock screen"
wait_time 600
comment "wait_time" "finish wait 600"

#下一条monkey语句

#记录测试结束
comment "finish" "END"
#通知停止监控
echo "`date +%m-%d" "%H":"%M":"%S` stop MCM" > /data/local/tmp/stop

2、电量监控——btm

#!/system/bin/sh
echo "loop,uptime,battery_capacity,cpu_temperature,battery_voltage,battery_status,battery_health,cpufreq,Date_Time" >/data/btm.csv
loop=0
#判定高通和MTK兼容获取电池温度不同
if [ -f /sys/class/power_supply/battery/temp ];then
    temp="/sys/class/power_supply/battery/temp"
elif [ -f /sys/class/power_supply/battery/batt_temp ];then
    temp="/sys/class/power_supply/battery/batt_temp"
fi
while true;do
    tmp=`cat /proc/uptime /sys/class/power_supply/battery/capacity $temp`
    #判定高通和MTK兼容获取电池温度不同
    if [ -f /sys/class/power_supply/battery/voltage_now ];then
        voltage=$((`cat /sys/class/power_supply/battery/voltage_now`/1000))
    elif [ -f /sys/class/power_supply/battery/batt_vol ];then
        voltage=`cat /sys/class/power_supply/battery/batt_vol`
    fi
    tmp2=`cat /sys/class/power_supply/battery/status /sys/class/power_supply/battery/health`
    part1=`echo $tmp $voltage $tmp2|busybox awk '{a=sprintf("%.0f",$1);b=$3;c=$4/10;d=sprintf("%.3f",$5/1000);e=$6;f=$7}END{printf a","b","c","d","e","f}'`
    part2=`cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq|busybox awk '{if(NR>1)printf "|";printf $1/1000}'`
    data_t=`date +%Y/%m/%d" "%H:%M:%S`
    echo $loop","$part1","$part2","$data_t>>/data/btm.csv
    if [ -f /data/local/tmp/stop ];then
        break
    elif [ `busybox df /data|busybox awk '{r=substr($(NF-1),1,length($(NF-1))-1)}END{print r+0}'` -ge 90 ];then
        echo "The free space of data less 10%,stop" >/data/local/tmp/stop
        break
    fi
    sleep $1
    loop=$((loop+1))
done
电量监控图



3、log 筛查归类为 CSV

#!/bin/bash

fatal(){
echo check_Fatal: $1
grep -E "AndroidRuntime|Force finishing activity|crashApplication" $1|awk -F ":" -v file="$1" -v csv="$2/FATAL_EXCEPTION.csv" -v t1="\"" 'BEGIN{p=0}{if(p==1||p==2){if($4!=" Process"){p=3;c=substr($4,2,length($4)-1)","t1 substr($5,2,length($5)-1) t1}};if($4==" Process"){p=2;b=substr($5,2,length($5)-6)};if($4==" FATAL EXCEPTION"||$4==" *** FATAL EXCEPTION IN SYSTEM PROCESS"){p=1;if(a!="")print file","b","a","c","t1 d t1","t1 r t1 >>csv;a=substr($5,2,length($5)-1);b="SYSTEM PROCESS";r=$0;d=""};if(substr($4,4,15)=="Force finishing")b=substr($4,29,length($4)-28);if($4==" Caused by"){if(d!="")d=d"\n"substr($5,2,length($5)-1);else d=substr($5,2,length($5)-1)};if(p>1){r=r"\n"$0}}END{if(a!="")print file","b","a","c","t1 d t1","t1 r t1 >>csv}'
}

anr(){
echo check_ANR: $1
grep -A 9 "ANR in" $1|awk -F ":" -v file="$1" -v csv="$2/ANR.csv" -v t1="\"" 'BEGIN{p=0}{if(substr($4,2,3)=="ANR"){p=1;if(a!="")print file","a","b","t1 c t1 >>csv;a=t1 substr($4,9,length($4)-8) t1;c=$0};if($4==" PID")p=2;if($4==" Reason")b=t1 $5 t1;if(p>1)c=c"\n"$0}END{if(a!="")print file","a","b","t1 c t1 >>csv}'
}

tombstone(){
echo check_tombstone: $1
grep "DEBUG" $1|awk -F ":" -v file="$1" -v csv="$2/tombstone.csv" -v t1="\"" 'BEGIN{p=0}{if($4==" Build fingerprint"){p=1;if(a!="")print file","a","t1 c t1 >>csv;c=$0};if($4==" Revision")p=2;if($4==" pid")a=substr($NF,1,length($NF)-2);if(p>1)c=c"\n"$0}END{if(a!="")print file","a","t1 c t1 >>csv}'
sed -i 's/log,.*>>> /log,/g;s/ <<*,/,/g' $2/tombstone.csv
}


echo File,Process,Thread,Exception,Exception info,Caused by,Log >$1/FATAL_EXCEPTION.csv
echo File,ANR in,Reason,Log >$1/ANR.csv
echo File,tombstone,Log >$1/tombstone.csv
find $1 -name *.log|xargs grep -l -E ": ANR in|Build fingerprint|FATAL EXCEPTION"|while read log ;do
if [ `grep -c "FATAL EXCEPTION" $log` -ne 0 ];then
fatal $log $1
fi
if [ `grep -c ": ANR in" $log` -ne 0 ];then
anr $log $1
fi
if [ `grep -c "Build fingerprint" $log` -ne 0 ];then
tombstone $log $1
fi
done
if [ `awk 'END{print NR}' $1/FATAL_EXCEPTION.csv` -eq 1 ];then
    rm $1/FATAL_EXCEPTION.csv
fi
if [ `awk 'END{print NR}' $1/ANR.csv` -eq 1 ];then
    rm $1/ANR.csv
fi
if [ `awk 'END{print NR}' $1/tombstone.csv` -eq 1 ];then
    rm $1/tombstone.csv
fi

三、脚本实例(我做系统测试所设计的脚本实例)

重构 moneky 压力测试 (https://testerhome.com/topics/3685)

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 34 条回复 时间 点赞

大神们都是半夜还在努力!赞

电量监控图 那是什么软件吗

#2 楼 @jennyhui Highcharts 做的 HTML 模板,python 使用 pandas 完成 csv 到 js 数据文件的生成,生成的 Html 交互图。

回复收藏,后续学习

够专业,学习了!

—— 来自 TesterHome 官方 安卓客户端

牛,我还一直在考虑要怎么处理锁屏的问题

楼主脚本不是直接运行吧

—— 来自 TesterHome 官方 安卓客户端

浮云 #26 · 2015年11月04日 Author

#9 楼 @babyshine 注释了,需要修改 monkey 语句,设备端的脚本用到了 busybox,本身脚本是去除了,moneky 测试逻辑,毕竟这个是和测试目的和设备相关的,需要按需定制。

浮云 #11 · 2015年11月04日 Author

再有,如果是在 windows 上编辑 shell 脚本,需要注意文档格式为 unix,字符编码为 utf8,使用 Notpad++ 转一下文档格式就好了。

浮云 #24 · 2015年11月04日 Author

log 筛查处理的是用-v time 格式获取的 log。

#11 楼 @sandman 牛牛牛🐮

—— 来自 TesterHome 官方 安卓客户端

非常赞啊

这个必须在 root 的手机上使用吧?

浮云 #20 · 2015年11月05日 Author

#15 楼 @andyguo 是的,shell 脚本在设备端后台运行,不需要长时间连 PC。需要用到 busybox,而且有些设备自身没 root 权限连 monkey 都跑不起来。单就逻辑来说对于无 root 但可以正常执行 monkey 的安卓设备,可以写 PC 端的脚本进行控制,不过没有设备端后台脚本方便。

把当前我自己在项目中使用的实例发上来了,可以参考,定制为所需的执行逻辑。

monkey 压力执行脚本:(http://pan.baidu.com/s/1jG2dtHG)
筛查异常:(logcat -v time)(http://pan.baidu.com/s/1gdg4K99)
BTM(电量 - 电压 - 温度监控):(http://pan.baidu.com/s/1kT92OJX)

其中把结果保存在/data/目录下是由于测试了文件管理和图库,结果放 sdcard 下可能被 monkey 操作。就换了文件管理类 app 控制不了的 data 目录保存结果。

赞,支持一下

我研究过类似的,但是我做的比较简单了,我把最后的结果收集后,用 mat 生成的报告。。。

不错 学习学习

#16 楼 @sandman 非常感谢,最近正在研究这块,打算把你的脚本看看能不能移到 pc 端实时,因为很多手机 root 不了,有些手机 root 了也办法写文件

浮云 #15 · 2015年11月09日 Author

#21 楼 @andyguo 可以移植到 PC,如果 PC 是 linx 的话,很容易直接把 busybox 的换成 pc 本地的命令,把权限受限无法使用的去除,windows 改成 bat 写起来会麻烦点,不过也是很容易可以实现的。就是连 PC 长时间跑的话,需要解决供电,需要购买 2A 输出的带外接电源的 hub。

root@guozhenhua-Latitude-E5430-non-vPro:/home/guozhenhua# adb shell cat /sys/class/power_supply/battery/voltage_now
4317478

我这温度怎么这么高啊,是不是我取的有问题?

对了你那有电量统计的文件样本吗?

浮云 #12 · 2015年11月11日 Author

#23 楼 @andyguo 。。。。。。你取的是电压。voltage_now,你这精度是μV,看清英文 battery/voltage_now,电池当前电压

浮云 #11 · 2015年11月11日 Author

#23 楼 @andyguo 开始写的时候测试的是高通取的 cpu 温度,实际测试 cpu 温度和电池温度一直一样,后来发现 MTK 不兼容就做了兼容处理,全部取了电池问题,模板没改还是 cpu 温度。
下面是取温度的兼容,看注释。存在哪个取哪个,毕竟不同平台情况互斥,不会共存。
# 判定高通和 MTK 兼容获取电池温度不同
if [ -f /sys/class/power_supply/battery/temp ];then
temp="/sys/class/power_supply/battery/temp"
elif [ -f /sys/class/power_supply/battery/batt_temp ];then
temp="/sys/class/power_supply/battery/batt_temp"
fi

#26 楼 @sandman 谢了我已经搞定了,发现你 uptime 好像没有用上啊?这是我改写的在 linux 下测试的脚本,但是发现个问题,就是测试的过程中会充电,这样测试就不准确了,还是你的比较靠谱:

#!/bin/bash

echo "loop,uptime,battery_capacity,cpu_temperature,battery_voltage,battery_status,battery_health,cpufreq,Date_Time" >log/btm.csv
loop=0
# 判定高通和 MTK 兼容获取电池温度不同
if [ adb shell ls /sys/class/power_supply/battery/ |grep -w temp ];then
temp="/sys/class/power_supply/battery/temp"
elif [ adb shell ls /sys/class/power_supply/battery/ |grep -w batt_temp ];then
temp="/sys/class/power_supply/battery/batt_temp"
fi

echo $temp

while true;
do
tmp3="adb shell cat /proc/uptime | xargs echo" #uptime

tmp4="adb shell cat /sys/class/power_supply/battery/capacity"

tmp5="adb shell cat $temp | xargs echo"

tmp="${tmp3%?}"" ""${tmp4%?}"" ""${tmp5%?}"

# 判定高通和 MTK 兼容获取电池温度不同
if [ adb shell ls /sys/class/power_supply/battery/ |grep -w voltage_now ];then
voltage_now=adb shell cat /sys/class/power_supply/battery/voltage_now
voltage=awk 'BEGIN{print $1,'$voltage_now'/'1000'}'
elif [ adb shell ls /sys/class/power_supply/battery/ |grep -w batt_vol ];then
voltage=adb shell cat /sys/class/power_supply/battery/batt_vol
fi

# 电池状态
tmp6=adb shell cat /sys/class/power_supply/battery/status
tmp7=adb shell cat /sys/class/power_supply/battery/health
tmp2="${tmp6%?}"" ""${tmp7%?}"
echo "tmp2:" $tmp2

part1=echo $tmp $voltage $tmp2|awk '{a=sprintf("%.0f",$1);b=$3;c=$4/10;d=sprintf("%.3f",$5/1000);e=$6;f=$7}END{printf a","b","c","d","e","f}'

echo "part1:"$part1
#cpu 频率
part2=adb shell cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq|awk '{if(NR>1)printf "|";printf $1/1000}'

echo "part2:"$part2
data_t=date +%Y/%m/%d" "%H:%M:%S
echo $loop","$part1","$part2","$data_t>>log/btm.csv
loop=$((loop+1))
if [ $loop -gt 500 ];then
break
fi
sleep 1m
done

没有对系统和工具的精准理解,再好的 Shell 功底也是写不出来的,膜拜!!

膜拜膜拜,收藏学习了

对于一个应用包含多个独立进程,且进程间无界面交互的情况,如何指定独立进程进行测试呢?

#30 楼 @alfor 限定范围是针对显示页面做的,独立子进程判断超范围则使用设定的方式唤起。

学习了,不错啊, 给你打赏了。

楼主,百度链接失效了。

浮云 #36 · 2016年08月11日 Author

#33 楼 @kevin_zsj 看维护的

恒温 感谢 fir.im —— 结果公布 中提及了此贴 06月05日 13:20
浮云 Monkey 测试中,对外放声音的处理方法? 中提及了此贴 07月10日 15:23
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册