1.背景当前闲鱼在精益开发模式下,整个技术团队面临了诸多的能力落地和挑战,尤其是效能方面的2-1-1的目标(2周需求交付周期,1周需求开发周期,1小时达到发布标准),具体可见 闲鱼工程师是如何构建持续集成流水线,让研发效率翻倍的 ,在这个大目标下,就必须把每个环节都做到极致。自动化的建设是决定CI成败的关键能力,今天分享一下闲鱼Android客户端性能自动化环节的实践。2.面临的问题2.1 主要是两个方面的问题工具缺失:目前淘宝系,对于线上性能水位的监控有一套完善的体系,但是针对新功能的性能测试,每个业务团队都有对应的性能专项小组,产出的工具都是根据自己业务特点的定制开发的,闲鱼客户端目前使用Flutter做为客户端主开发语言,对于Flutter性能数据的获取及UI自动化测试支撑工具目前是缺失的,同时业界对Flutter自动化和性能相关的实践几乎没有;测试工作量翻N倍(N=一个版本周期内的分支数):原先的开发模式是功能测试集成测试一起进行的,所以性能测试只需要针对集成后的包进行测试即可,到现在转变为泳道的开发模式,一个版本内会一般包含十几个左右的泳道分支甚至更多,我们必须确保每个泳道的分支的性能是达标的,如果有性能问题需要第一时间反馈出来,如果遗留到集成阶段,问题的排查(十几个分支中筛查),问题的解决将会耗费大量的时间,效率很难得到大的提升;2.2 问题思考体系化解决,要让每个泳道分支都得到有效测试覆盖,测试件能够自动化执行,持续反馈结果3. 解决方案综合上述问题,梳理如下解决方案:针对Flutter性能数据的获取(比如,Flutter有自己的SurfaceView,原有Native计算FPS的方式无法直接使用)针对Flutter UI自动化的实现(Flutter/Native UI混合栈的处理)性能自动化脚本 / 性能数据自动采集、上报 融入CI流程性能问题的通知 / 报表展示 / 分析3.1 性能数据[FPS]解析处理 adb shell dumpsys SurfaceFlinger –latency 的数据,详细请见文末参考链接(该方式兼容Flutter及Native的解决方案,已在Android4.x-9.x验证可行),处理SurfaceFlinger核心代码如下: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 $windowfiif [[ $isflutter = 1 ]];then window=dumpsys SurfaceFlinger --list |grep '^SurfaceView'|$bb awk 'NR==1{print $0}'if [ -z “$window” ]; then window=“SurfaceView"fiecho $windowfi$bb usleep $sleep_tdumpsys 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+r1000000)}}};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)%11000);f=sprintf("%.2f",n1000/C);m=sprintf("%.0f",m);g=f/target;if(g>1)g=1;h=kpi/m;if(h>1)h=1;e=sprintf("%.2f",g60+h20+(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:"$uidinitreceive=$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”,"$inittransmitgetnet(){ 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 性能自动化脚本基于Appium的自动化用例,这个技术业界已经有非常多的实践了,这里我不再累述,如果不了解的同学,可以到Appium官网 http://appium.ioFlutter和Native页面切换使用App内的Schema跳转Flutter页面的文本输入等交互性较强的场景使用基于Flutter框架带的Integration Test来操作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相关Framediff: 绘制帧的开始时间和结束时间差FPS: 每秒展示的帧数Frames: 一个刷新周期内所有的帧jank: 一帧开始绘制到结束超过16.67ms 就记一次jank,jank非零代表硬件绘制掉帧,和屏幕硬件性能及相关驱 动性能有关jank2: 一帧开始绘制到结束超过33.34ms 就记一次jank2MFS: 在一个刷新周期内单帧最大耗时(每两行垂直同步的时间差代表两帧绘制的帧间隔)OKT: 在一个刷新周期内,帧耗时超过16.67ms的次数SS: 流畅度,通过FPS,MFS,OKT计算出来,流畅度 = 实际帧率比目标帧率比值60【目标帧率越高越好】 + 目标时间和两帧时间差比值20【两帧时间差越低越好】 + (1-超过16ms次数/帧数)*20【次数越少越好】CPUMemory4. 成果展示4.1 指定泳道分支性能监控泳道分支出现了性能问题再报表上一目了然4.2 性能专项支撑1、Flutter商品详情页重构 14轮测试2、客户端图片统一资源测试 4轮测试5. 总结性能自动化只是整个CI流程中的一个环节,为了极致效率的大目标,闲鱼质量团队还产出了很多支撑工具,CI平台,遍历测试,AI错误识别,用例自动生成等等,后续也会分享给大家。6. 参考https://testerhome.com/topics/2232https://testerhome.com/topics/4775本文作者:闲鱼技术-灯阳阅读原文本文为云栖社区原创内容,未经允许不得转载。
...