当前闲鱼在精益开发模式下,整个技术团队面临了诸多的能力落地和挑战,尤为是效能方面的2-1-1的目标(2周需求交付周期,1周需求开发周期,1小时达到发布标准),具体可见 闲鱼工程师是如何构建持续集成流水线,让研发效率翻倍的 ,在这个大目标下,就必须把每一个环节都作到极致。自动化的建设是决定CI成败的关键能力,今天分享一下闲鱼Android客户端性能自动化环节的实践。java
2.1 主要是两个方面的问题shell
目前淘宝系,对于线上性能水位的监控有一套完善的体系,可是针对新功能的性能测试,每一个业务团队都有对应的性能专项小组,产出的工具都是根据本身业务特色的定制开发的,闲鱼客户端目前使用Flutter作为客户端主开发语言,对于Flutter性能数据的获取及UI自动化测试支撑工具目前是缺失的,同时业界对Flutter自动化和性能相关的实践几乎没有;windows
原先的开发模式是功能测试集成测试一块儿进行的,因此性能测试只须要针对集成后的包进行测试便可,到如今转变为泳道的开发模式,一个版本内会通常包含十几个左右的泳道分支甚至更多,咱们必须确保每一个泳道的分支的性能是达标的,若是有性能问题须要第一时间反馈出来,若是遗留到集成阶段,问题的排查(十几个分支中筛查),问题的解决将会耗费大量的时间,效率很可贵到大的提高;app
2.2 问题思考框架
体系化解决,要让每一个泳道分支都获得有效测试覆盖,测试件可以自动化执行,持续反馈结果工具
综合上述问题,梳理以下解决方案:性能
3.1 性能数据测试
[FPS]ui
解析处理 adb shell dumpsys SurfaceFlinger --latency 的数据,详细请见文末参考连接(该方式兼容Flutter及Native的解决方案,已在Android4.x-9.x验证可行),处理SurfaceFlinger核心代码以下:spa
dumpsys SurfaceFlinger --latency-clear #echo "dumpsys SurfaceFlinger..." if [[ $isflutter = 0 ]];then window=`dumpsys window windows | grep mCurrent | $bb awk '{print $3}'|$bb tr -d '}'` # Get the current window echo $window fi if [[ $isflutter = 1 ]];then window=`dumpsys SurfaceFlinger --list |grep '^SurfaceView'|$bb awk 'NR==1{print $0}'` if [ -z "$window" ]; then window="SurfaceView" fi echo $window fi $bb usleep $sleep_t dumpsys SurfaceFlinger --latency "$window"|$bb awk -v time=$uptime -v target=$target -v kpi=$KPI '{if(NR==1){r=$1/1000000;if(r<0)r=$1/1000;b=0;n=0;w=1}else{if(n>0&&$0=="")O=1;if(NF==3&&$2!=0&&$2!=9223372036854775807){x=($3-$1)/1000000/r;if(b==0){b=$2;n=1;d=0;D=0;if(x<=1)C=r;if(x>1){d+=1;C=int(x)*r;if(x%1>0)C+=r};if(x>2)D+=1;m=r;o=0}else{c=($2-b)/1000000;if(c>1000){O=1}else{n+=1;if(c>=r){C+=c;if(c>kpi)o+=1;if(c>=m)m=c;if(x>1)d+=1;if(x>2)D+=1;b=$2}else{C+=r;b=sprintf("%.0f",b+r*1000000)}}};if(n==1)s=sprintf("%.3f",$2/1000000000)};if(n>0&&O==1){O=0;if(n==1)t=sprintf("%.3f",s+C/1000);else t=sprintf("%.3f",b/1000000000);T=strftime("%F %T",time+t)"."sprintf("%.0f",(time+t)%1*1000);f=sprintf("%.2f",n*1000/C);m=sprintf("%.0f",m);g=f/target;if(g>1)g=1;h=kpi/m;if(h>1)h=1;e=sprintf("%.2f",g*60+h*20+(1-o/n)*20);print s","t","T","f+0","n","d","D","m","o","e","w;n=0;if($0==""){b=0;w+=1}else{b=$2;n=1;d=0;D=0;if(x<=1)C=r;if(x>1){d+=1;C=int(x)*r;if(x%1>0)C+=r};if(x>2)D+=1;m=r;o=0}}}}' >>$file
[CPU]
使用的是top的命令获取(该方式获取性能数据时,数据收集带来的损耗最少)
export bb="/data/local/tmp/busybox" $bb top -b -n 1|$bb awk 'NR==4{print NF-1}'
[内存]
解析 dumpsys meminfo $package 拿到 Java Heap,Java Heap Average,Java Heap Peak,Native Heap,Native Heap Average,Native Heap Peak,Graphics,Unknown,Pss 数据
do_statistics() { ((COUNT+=1)) isExist="$(echo $OUTPUT | grep "Dalvik Heap")" if [[ ! -n $isExist ]] ; then old_dumpsys=true else old_dumpsys=false fi if [[ $old_dumpsys = true ]] ; then java_heap="$(echo "$OUTPUT" | grep "Dalvik" | $bb awk '{print $6}' | $bb tr -d '\r')" else java_heap="$(echo "$OUTPUT" | grep "Dalvik Heap[^:]" | $bb awk '{print $8}' | $bb tr -d '\r')" fi echo "1."$JAVA_HEAP_TOTAL "2."$java_heap "3."$JAVA_HEAP_TOTAL ((JAVA_HEAP_TOTAL+=java_heap)) ((JAVA_HEAP_AVG=JAVA_HEAP_TOTAL/COUNT)) if [[ $java_heap -gt $JAVA_HEAP_PEAK ]] ; then JAVA_HEAP_PEAK=$java_heap fi if [[ $old_dumpsys = true ]] ; then native_heap="$(echo "$OUTPUT" | grep "Native" | $bb awk '{print $6}' | $bb tr -d '\r')" else native_heap="$(echo "$OUTPUT" | grep "Native Heap[^:]" | $bb awk '{print $8}' | $bb tr -d '\r' | $bb tr -d '\n')" fi ((NATIVE_HEAP_TOTAL+=native_heap)) ((NATIVE_HEAP_AVG=NATIVE_HEAP_TOTAL/COUNT)) if [[ $native_heap -gt $NATIVE_HEAP_PEAK ]] ; then NATIVE_HEAP_PEAK=$native_heap fi g_Str="Graphics" if [[ $OUTPUT == *$g_Str* ]] ; then echo "Found Graphics..." Graphics="$(echo "$OUTPUT" | grep "Graphics" | $bb awk '{print $2}' | $bb tr -d '\r')" else echo "Not Found Graphics..." Graphics=0 fi Unknown="$(echo "$OUTPUT" | grep "Unknown" | $bb awk '{print $2}' | $bb tr -d '\r')" total="$(echo "$OUTPUT" | grep "TOTAL"|$bb head -1| $bb awk '{print $2}' | $bb tr -d '\r')" }
[流量]
经过 dumpsys package packages 解析出当前待测试包来获取流量信息
uid="$(dumpsys package packages|$bb grep -E "Package |userId"|$bb awk -v OFS=" " '{if($1=="Package"){P=substr($2,2,length($2)-2)}else{if(substr($1,1,6)=="userId")print P,substr($1,8,length($1)-7)}}'|grep $package|$bb awk '{print $2}')" echo "Net:"$uid initreceive=`$bb awk -v OFS=" " 'NR>1{if($2=="wlan0"){wr[$4]+=$6;wt[$4]+=$8}else{if($2=="rmnet0"){rr[$4]+=$6;rt[$4]+=$8}}}END{for(i in wr){print i,wr[i]/1000,wt[i]/1000,"wifi"};for(i in rr){print i,rr[i]/1000,rt[i]/1000,"data"}}' /proc/net/xt_qtaguid/stats | grep $uid|$bb awk '{print $2}'` inittransmit=`$bb awk -v OFS=" " 'NR>1{if($2=="wlan0"){wr[$4]+=$6;wt[$4]+=$8}else{if($2=="rmnet0"){rr[$4]+=$6;rt[$4]+=$8}}}END{for(i in wr){print i,wr[i]/1000,wt[i]/1000,"wifi"};for(i in rr){print i,rr[i]/1000,rt[i]/1000,"data"}}' /proc/net/xt_qtaguid/stats | grep $uid|$bb awk '{print $3}'` echo "initnetarray"$initreceive","$inittransmit getnet(){ local data_t=`date +%Y/%m/%d" "%H:%M:%S` netdetail=`$bb awk -v OFS=, -v initreceive=$initreceive -v inittransmit=$inittransmit -v datat="$data_t" 'NR>1{if($2=="wlan0"){wr[$4]+=$6;wt[$4]+=$8}else{if($2=="rmnet0"){rr[$4]+=$6;rt[$4]+=$8}}}END{for(i in wr){print datat,i,wr[i]/1000-initreceive,wt[i]/1000-inittransmit,"wifi"};for(i in rr){print datat,i,rr[i]/1000-initreceive,rt[i]/1000-inittransmit,"data"}}' /proc/net/xt_qtaguid/stats | grep $uid` echo $netdetail>>$filenet }
3.2 性能自动化脚本
An integration testGenerally, an integration test runs on a real device or an OS emulator, such as iOS Simulator or Android Emulator. The app under test is typically isolated from the test driver code to avoid skewing the results.
Flutter的UI自动化及Flutter/Native混合页面的处理在测试上的应用后续单独开文章介绍,原理相关能够先参考 千人千面录制回放技术
3.3 性能自动化CI流程
3.4 性能数据报表
FPS相关
CPU
Memory
4.1 指定泳道分支性能监控
泳道分支出现了性能问题再报表上一目了然
4.2 性能专项支撑
一、Flutter商品详情页重构 14轮测试
二、客户端图片统一资源测试 4轮测试
性能自动化只是整个CI流程中的一个环节,为了极致效率的大目标,闲鱼质量团队还产出了不少支撑工具,CI平台,遍历测试,AI错误识别,用例自动生成等等,后续也会分享给你们。
https://testerhome.com/topics/2232
https://testerhome.com/topics/4775
本文为云栖社区原创内容,未经容许不得转载。