當前閒魚在精益開發模式下,整個技術團隊面臨了諸多的能力落地和挑戰,尤爲是效能方面的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
本文爲雲棲社區原創內容,未經容許不得轉載。