1. 程式人生 > >shell 指令碼通過 dumpsys SurfaceFlinger --latency 資料計算 FPS 和評價流暢度。

shell 指令碼通過 dumpsys SurfaceFlinger --latency 資料計算 FPS 和評價流暢度。

由於採購機械臂測試效能用例和螢幕實際幀率變化,最終工具方案實現後擱置,拿出來”晒晒“。

一、設計初衷

1、面臨使用者和公司內領導試用中反饋的卡頓問題,思考如何能有效量化評估?
2、如何在嘗試復現卡頓的過程中持續監控FPS和丟幀情況?

二、設定預期倒推查詢解決方案

1、無root許可權限制,可直接採集資料計算FPS、丟幀率、最大單幀間隔。
2、控制指令碼影響,不要使監控指令碼成為“負擔”。
3、資料獲取靈活,即可控制檯實時輸出資料,也可以後臺長時間監控。
4、設計評價得分標準,可按:百分比*用例單項評分量化每條用例,從而計算總分使用。

設計實現部分

一、確定資料來源原因(dumpsys SurfaceFlinger --latency)

1、可以清零重新記錄,避免如何分清哪些資料是上次的。(dumpsys SurfaceFlinger --latency-clear)
2、按window獲取資料,可以配合手工操作逐一獲取每個case的流暢度。
3、歷史記錄127行資料,按60幀算可記錄2.12S資料,從而不用頻繁獲取。(最終考慮設定1.6S間隔重新整理資料。)

二、根據需求確定計算規則

1、有重新整理則計算幀率,無重新整理則不輸出資料。
原因:
(1)要做成監控指定視窗流暢度的功能,所以要控制無意義資料。
(2)配合手工操作,靜置狀態不輸出,操作停止後直接重新整理資料,從而使資料和操作對應。
2、間隔500ms以上則判定為操作延遲,每到間隔500ms情況發生重新計算幀率。
原因:
(1)一般做monkey壓力測試設定的是500ms間隔
(2)一般使用者操作頻率間隔是大於500ms情況
3、每次取樣資料大於等於1幀則計算FPS,丟幀率,最大幀間隔。
原因:幀數/總耗時=幀率,所以無論有多少幀都可以直接計算
4、設定流暢度評價規則:
(1)滿足KPI幀率則達成一半需求,佔比50%
(2)小於KPI單幀耗時比例評價畫面變化是否穩定,佔比40%
(3)單幀渲染峰值代表瞬時卡頓最大影響,佔比10%
5、程式碼實現過程中遇到一坑:SurfaceFlinger中同一幀存在間隔複用情況,即相同一行資料間隔幾幀出現兩次。(通過監控微信紅包點選後的彈出框的幀率發現的。)
補充規則:發生兩幀同步時間做差小於第一行幀重新整理週期,則總時間+幀重新整理週期,上一幀資料=前一幀同步時間+幀重新整理週期,總幀數+1

三、程式碼實現

#!/system/bin/sh

show_help() {
echo "
Usage: sh fps.sh [ -t target_FPS ] [ -w monitor_window ] [ -k KPI ] [ -f csv_path ] [ -h ]

Show: FU(s) LU(s) Date FPS Frames jank MFS(ms) OKT SS(%)

    FU(s): Uptime of the first frame.
    LU(s): Uptime of the last frame.
    Date: The date and time of LU.
    FPS: Frames Per Second.
    Frames: All frames of a loop.
    jank: When the frame latency crosses a refresh period, jank is added one.
    MFS(ms): Max Frame Spacing.
    OKT: Over KPI Times. The KPI is the used time of one frame.
    SS(%): Smoothness Score. SS=(FPS/target FPS)*60+(KPI/MFS)*20+(1-OKPIT/Frames)*20
           IF FPS > target FPS: FPS/The target FPS=1
           IF KPI > MFS: KPI/MFS=1
    WN: the window number of same name's window. Eg. SurfaceView

POSIX options | GNU long options

    -t   | --target         The target FPS of the choosed window. Default: 60
    -w   | --window         The choosed window. Default: no window.
    -k   | --KPI            The used time of a frame. Default: KPI=1000/The target FPS.
    -f   | --file           The path of the csv file. Default: output result to console.
    -h   | --help           Display this help and exit
"
} file="" window="" target=60 KPI=16 while : do case $1 in -h | --help) show_help exit 0 ;; -t | --target) shift target=$1 KPI=$((1000/$1)) shift ;; -w | --window) shift window="$1" shift ;; -k | --KPI) shift KPI=$1 shift ;; -f | --file) shift file="$1" shift ;; --) # End of all options shift break ;; *) # no more options. Stop while loop break ;; esac done if [ -f /data/local/tmp/busybox ];then export bb="/data/local/tmp/busybox" else echo "No /data/local/tmp/busybox" exit fi if [ -f /data/local/tmp/stop ];then $bb rm /data/local/tmp/stop fi if [ -f /data/local/tmp/FPS.pid ];then pid=`cat /data/local/tmp/FPS.pid` if [ -f /proc/$pid/cmdline ];then if [ `$bb awk 'NR==1{print $1}' /proc/$pid/cmdline`"a" == "sha" ];then echo "The $pid is sh command." exit fi fi fi echo $$ >/data/local/tmp/FPS.pid if [ $target -le 60 -a $target -gt 0 ];then sleep_t=1600000 else echo "$target is out of (0-60]" exit fi mac=`cat /sys/class/net/*/address|$bb sed -n '1p'|$bb tr -d ':'` model=`getprop ro.product.model|$bb sed 's/ /_/g'` build=`getprop ro.build.fingerprint` if [ -z $build ];then build=`getprop ro.build.description` fi uptime=`$bb awk -v T="$EPOCHREALTIME" 'NR==3{printf("%.6f",T-$3/1000000000+8*3600)}' /proc/timer_list` if [ -z "$file" ];then echo "" echo `date +%Y/%m/%d" "%H:%M:%S`": $window" if [ `$bb awk -F. '{print $1}' /proc/uptime` -lt 1000 ];then echo -e "FU(s) \tLU(s) \tDate \t\t\tFPS:$target\tFrames\tjank\tjank2\tMFS(ms)\tOKT:$KPI\tSS(%)\tWN" else echo -e "FU(s) \t\tLU(s) \t\tDate \t\t\tFPS:$target\tFrames\tjank\tjank2\tMFS(ms)\tOKT:$KPI\tSS(%)\tWN" fi while true;do dumpsys SurfaceFlinger --latency-clear $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>500){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"\t"T"\t"f+0"\t"n"\t"d"\t"D"\t"m"\t"o"\t"e"\t"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}}}}' if [ -f /data/local/tmp/stop ];then break fi done else start_time="`date +%Y/%m/%d" "%H:%M:%S`" echo "PID:$$\nWindow:$window\nT-FPS:$target\nKPI:$KPI\nStart time:$start_time\nmodel:$model\nmac:$mac\nbuild:$build" echo "FU(s),LU(s),Date:$window,FPS:$target,Frames,jank,jank2,MFS(ms),OKT:$KPI,SS(%),WN" >$file while true;do dumpsys SurfaceFlinger --latency-clear if [ -f /data/local/tmp/stop ];then echo "Stop Time:`date +%Y/%m/%d" "%H:%M:%S`" break 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>500){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 done fi

四、監控資料視覺化互動結果設計

既然設計了資料監控的形式,自然要設計配套的資料視覺化呈現方式
1、呈現資料
(1)x軸為同步時間點,每次取樣資料為起始時間點到取樣結束時間的一條橫線。
(2)y軸資料為平均FPS、超KPI幀數比例、和流暢度得分。左右雙y軸設計,左側為幀率,右側為百分比。
(3)兩次資料起始時間間隔超500ms則斷開
(4)每點互動資料顯示此次原始資料記錄
(5)按每次間隔時間超過500ms為準計算每次操作對應的響應時長,作圖呈現。持續監控情況則每超10秒計算一次。
2、一次監視訊控播放視窗結果圖例項:


下方總趨勢圖是可選的,滑鼠按住左鍵拖動選取檢視範圍。

五、指令碼原始碼

原理介紹引用