能用机器完成的,千万别堆工作量|持续集成中的性能自动化测试
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 ]];thenwindow=`dumpsys window windows | grep mCurrent | $bb awk '{print $3}'|$bb tr -d '}'` # Get the current windowecho $window fi if [[ $isflutter = 1 ]];thenwindow=`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 ]] ; thenold_dumpsys=trueelseold_dumpsys=falsefiif [[ $old_dumpsys = true ]] ; thenjava_heap="$(echo "$OUTPUT" | grep "Dalvik" | $bb awk '{print $6}' | $bb tr -d '\r')"elsejava_heap="$(echo "$OUTPUT" | grep "Dalvik Heap[^:]" | $bb awk '{print $8}' | $bb tr -d '\r')"fiecho "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 ]] ; thenJAVA_HEAP_PEAK=$java_heapfiif [[ $old_dumpsys = true ]] ; thennative_heap="$(echo "$OUTPUT" | grep "Native" | $bb awk '{print $6}' | $bb tr -d '\r')"elsenative_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 ]] ; thenNATIVE_HEAP_PEAK=$native_heapfig_Str="Graphics"if [[ $OUTPUT == *$g_Str* ]] ; thenecho "Found Graphics..."Graphics="$(echo "$OUTPUT" | grep "Graphics" | $bb awk '{print $2}' | $bb tr -d '\r')"elseecho "Not Found Graphics..."Graphics=0fiUnknown="$(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 性能自動化腳本
- 基于Appium的自動化用例,這個技術業界已經有非常多的實踐了,這里我不再累述,如果不了解的同學,可以到Appium官網?http://appium.io
- Flutter和Native頁面切換使用App內的Schema跳轉
- Flutter頁面的文本輸入等交互性較強的場景使用基于Flutter框架帶的Integration Test來操作
An?integration test
Generally, 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 就記一次jank2
- MFS: 在一個刷新周期內單幀最大耗時(每兩行垂直同步的時間差代表兩幀繪制的幀間隔)
- OKT: 在一個刷新周期內,幀耗時超過16.67ms的次數
- SS: 流暢度,通過FPS,MFS,OKT計算出來,流暢度 = 實際幀率比目標幀率比值60【目標幀率越高越好】 + 目標時間和兩幀時間差比值20【兩幀時間差越低越好】 + (1-超過16ms次數/幀數)*20【次數越少越好】
4. 成果展示
4.1 指定泳道分支性能監控
泳道分支出現了性能問題再報表上一目了然
4.2 性能專項支撐
1、Flutter商品詳情頁重構 14輪測試
2、客戶端圖片統一資源測試 4輪測試
5. 總結
性能自動化只是整個CI流程中的一個環節,為了極致效率的大目標,閑魚質量團隊還產出了很多支撐工具,CI平臺,遍歷測試,AI錯誤識別,用例自動生成等等,后續也會分享給大家。
原文鏈接
本文為云棲社區原創內容,未經允許不得轉載。
總結
以上是生活随笔為你收集整理的能用机器完成的,千万别堆工作量|持续集成中的性能自动化测试的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大侦探福老师——幽灵Crash谜踪案
- 下一篇: 看完这些干货帖,大数据产品从入门到精通