能用機器完成的,千萬別堆工做量|持續集成中的性能自動化測試

1.背景

當前閒魚在精益開發模式下,整個技術團隊面臨了諸多的能力落地和挑戰,尤爲是效能方面的2-1-1的目標(2周需求交付週期,1周需求開發週期,1小時達到發佈標準),具體可見 閒魚工程師是如何構建持續集成流水線,讓研發效率翻倍的 ,在這個大目標下,就必須把每一個環節都作到極致。自動化的建設是決定CI成敗的關鍵能力,今天分享一下閒魚Android客戶端性能自動化環節的實踐。java

2.面臨的問題

2.1 主要是兩個方面的問題shell

  • 工具缺失:

目前淘寶系,對於線上性能水位的監控有一套完善的體系,可是針對新功能的性能測試,每一個業務團隊都有對應的性能專項小組,產出的工具都是根據本身業務特色的定製開發的,閒魚客戶端目前使用Flutter作爲客戶端主開發語言,對於Flutter性能數據的獲取及UI自動化測試支撐工具目前是缺失的,同時業界對Flutter自動化和性能相關的實踐幾乎沒有;windows

  • 測試工做量翻N倍(N=一個版本週期內的分支數):

原先的開發模式是功能測試集成測試一塊兒進行的,因此性能測試只須要針對集成後的包進行測試便可,到如今轉變爲泳道的開發模式,一個版本內會通常包含十幾個左右的泳道分支甚至更多,咱們必須確保每一個泳道的分支的性能是達標的,若是有性能問題須要第一時間反饋出來,若是遺留到集成階段,問題的排查(十幾個分支中篩查),問題的解決將會耗費大量的時間,效率很可貴到大的提高;app

2.2 問題思考框架

體系化解決,要讓每一個泳道分支都獲得有效測試覆蓋,測試件可以自動化執行,持續反饋結果工具

3. 解決方案

綜合上述問題,梳理以下解決方案:性能

  • 針對Flutter性能數據的獲取(好比,Flutter有本身的SurfaceView,原有Native計算FPS的方式沒法直接使用)
  • 針對Flutter UI自動化的實現(Flutter/Native UI混合棧的處理)
  • 性能自動化腳本 / 性能數據自動採集、上報 融入CI流程
  • 性能問題的通知 / 報表展現 / 分析

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 性能自動化腳本

  • 基於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【次數越少越好】

CPU

Memory

img

4. 成果展現

4.1 指定泳道分支性能監控

泳道分支出現了性能問題再報表上一目瞭然

4.2 性能專項支撐

一、Flutter商品詳情頁重構 14輪測試

二、客戶端圖片統一資源測試 4輪測試

5. 總結

性能自動化只是整個CI流程中的一個環節,爲了極致效率的大目標,閒魚質量團隊還產出了不少支撐工具,CI平臺,遍歷測試,AI錯誤識別,用例自動生成等等,後續也會分享給你們。

6. 參考

https://testerhome.com/topics/2232
https://testerhome.com/topics/4775



本文做者:閒魚技術-燈陽

閱讀原文

本文爲雲棲社區原創內容,未經容許不得轉載。

相關文章
相關標籤/搜索