移动性能测试 shell 脚本通过 dumpsys SurfaceFlinger --latency 数据计算 FPS 和评价流畅度。

浮云 · May 02, 2016 · Last by 浮云 replied at March 26, 2020 · 17594 hits
本帖已被设为精华帖!

开篇前述:

由于采购机械臂测试性能用例和屏幕实际帧率变化,最终工具方案实现后搁置,拿出来”晒晒“。

一、设计初衷

1、面临用户和公司内领导试用中反馈的卡顿问题,思考如何能有效量化评估?
2、如何在尝试复现卡顿的过程中持续监控FPS和丢帧情况?

二、设定预期倒推查找解决方案

1、无root权限限制,可直接采集数据计算FPS、丢帧率、最大单帧间隔。
2、控制脚本影响,不要使监控脚本成为“负担”。
3、数据获取灵活,即可控制台实时输出数据,也可以后台长时间监控。
4、设计评价得分标准,可按:百分比*用例单项评分量化每条用例,从而计算总分使用。

设计实现部分

参考了网上现有的FPS计算方式原理,绕来绕去也没有满足自己的预期需求,索性自己从数据源出发自己设计脚本计算逻辑处理。参考如下
用第三方库(surface_stats_collector.py) 获取 AndroidFPS 过程分析 (https://testerhome.com/topics/2232)
如何准确评测Android应用的流畅度? (http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=75&extra=page%3D1)

一、确定数据来源原因(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、一次监视频控播放窗口结果图实例:

下方总趋势图是可选的,鼠标按住左键拖动选取查看范围。

五、脚本源码

(https://pan.baidu.com/s/1qYjzIZ6)

原理介绍引用

下面文章介绍的挺清晰的,虽然实现方案不一样,但原理出发点一样,可以作为参考文章。
(http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=936&extra=page%3D1)

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 128 条回复 时间 点赞
1Floor has been deleted
浮云 #2 · May 02, 2016 作者

#1楼 @monkey 假期时常昼夜颠倒。。。

是东舟的机械臂吗? 我也使用过,感觉还是可以的,不过最后公司没有买。

#3楼 @zsg5566 是的,性能落地到数据,还是客观操作录像获取的数据能让相关团队人员信服。

那图形是什么做出来的 看着很高大上

浮云 #6 · May 03, 2016 作者

#5楼 @CloudHuan Highcharts

cool~UI流畅度全套方案了~~

有意思,看起来不错

看过的最靠谱的

hi

#2楼 @sandman 我做了一个测试,检测了视频直播app播放视频时的 流畅度性能(仅观看直播视频10分钟 无任何交互点击或跳转)。 但有个疑问,如图。数据上前半段和后半段相差甚远。但直观并没有感觉到前半段有什么卡顿。这个该是什么问题?

浮云 #11 · May 03, 2016 作者

#10楼 @zhangzhao_lenovo 什么图?csv直接excel作图的?我工具里提供了csv转html交互图的脚本。

至于帧率,本身视频的帧率就是动态的,一般是23到25帧吧,得看具体的码流。如果帧率到了50到60帧就是UI刷新的帧率了,可能是加载缓冲,切换内容之类的ui展示操作引起帧率提高。

浮云 #12 · May 03, 2016 作者

PS播放器本身在处理视频流是是有丢帧处理的,视频播放场景主要可以判断的是帧率提高到50-60的现象基本都是卡顿加载,换内容切换的时刻。

测试是按窗口测试的,sh /data/local/tmp/fps.sh -t 25 -w SurfaceView。视频类流畅度我是按25帧目标值打分的,很多视频流都是24帧的。

#12楼 @sandman 恩 懂你意思了。thx。 推流都是24。我换25看看。

#13楼 @sandman 视频上有浮弹幕的话 是还应该按60帧来取吧? 没弹幕纯视频才25?

浮云 #15 · May 03, 2016 作者

#14楼 @zhangzhao_lenovo 其实视频播放就是监控SurfaceView的绘制,弹幕的话得看是不是也是在这个window上绘制。

#15楼 @sandman 哦 ,我是监控的 dumpsys SurfaceFlinger | grep "|....|" 所示window 。 不是纯视频的话 25帧感觉又不太对

#16楼 @sandmano 这个获取帧,计算流畅度的 是用的什么理论原理? 也和GT不大一样。求了解

@sandman 你好,感谢分享,我在测试过程中遇到个问题,我想指定一个surfaceview,然而查看当前显示的窗口名时,有两个同名的surfaceview,请问楼主有木有经验指定其中一个surfaceview

浮云 #19 · May 04, 2016 作者

#18楼 @fanlei1014 我知道这种情况,其实乐视的SurfaceView更多,live3路视频流。后面还要上9路视频。。。
这个场景下监控SurfaceView获取的是所有SurfaceView窗口帧累计的情况,其实就是屏幕被SurfaceView分割不同部分分别刷新,但记录都是记在SurfaceView名下。由于是累计刷新帧率会相对单片源提高一倍左右。40帧+的情况。多路场景将目标KPI提高进行评价就是了。
建议正常UI响应按60帧评价,单路视频流按25帧,多路视频流按50

浮云 #20 · May 04, 2016 作者

#17楼 @zhangzhao_lenovo 写的很清楚啊基于SurfaceFlinger的数据,数据本身是安卓记录提供的,我只是拿来计算。计算方法和逻辑是我按自己目标需求设定的。

#19楼 @sandman 我试了一下监控surfaceview,我播放单路视频得到的平均FPS在15左右,开启弹幕的情况是在60+,我的测试对象是弹幕的fps,能简单的做减法来得出吗?或者我通过提高FPS评价的话就是加上15?。。弹幕的评价应该是60吧

浮云 #22 · May 05, 2016 作者

#21楼 @fanlei1014 单路视频片源帧率还真低,看来视频码流和清晰度打折扣了。弹幕是刷的ui不是视频,相当于一路是视频的15帧,一路是弹幕ui的的50-60。一路视频一路ui相当于25+60来评价了,不过视频本身也有扣分。
视频流卡顿刷ui loading也是会带来帧率上升的。相当于顺时两路ui的50-60范围帧率了。

弹幕场景还是按整体评估吧,毕竟用户体验需求是“视频+弹幕“

#22楼 @sandman 今天测了一个版本,发现是单路视频25FPS,弹幕+视频42FPS,就感觉不太懂了,怎么计算的FPS。。中间可能还有其他因素吧。。

浮云 #24 · May 05, 2016 作者

#23楼 @fanlei1014 看了下数据,发现是我设计脚本逻辑时没处理这个场景。dumpsys SurfaceFlinger --latency SurfaceView的数据在多窗口情况下是间隔一个空行保存的。数据本身是可以分别计算每个SurfaceView的帧率的,之前做完方案搁置,就没有把所有场景考虑全面。
后续有时间我更新下这种同名window共存情况的数据处理逻辑。

浮云 #25 · May 05, 2016 作者

#23楼 @fanlei1014 PS:为什么把弹幕做成了两个SurfaceView,好奇怪。我看了下我们的弹幕UI,视频流是一个SurfaceView。弹幕是占用独立的PlayerActivity刷新。

#25楼 @sandman 之前的版本是做成了2个surfaceview的,最新的版本把弹幕做在了activity上,我这次两个版本需要都测下,而且我们这边的团队只负责弹幕,所以希望撇开视频的因素来测试。

我这对一个全屏的直播视频 关弹幕。监控surfaceview 反而没有记录什么数据..

浮云 #28 · May 06, 2016 作者

#27楼 @zhangzhao_lenovo 先看窗口再测试,不是说所有播放都是用的surfaceview,也有自己封装播放器播放的,例如youtube

#28楼 @sandman 对的。求教下 如何看视频用的哪个窗口呢?

浮云 #30 · May 06, 2016 作者

#29楼 @zhangzhao_lenovo 。。。脚本执行说明很详细的写了

#30楼 @sandman busybox 是干嘛的。我在本地没有打开,本地是mac。

浮云 #32 · May 13, 2016 作者

#31楼 @xiaolinzi 哎,百度就知道busybox是干什么的了,这是手机端版本。

#3楼 @zsg5566 很好的工具

您好。请问我这里FPStoHTML.exe 怎么提示是木马?而且,将生产的csv文件拉倒FPStoHTML.exe,没反应

浮云 #35 · May 26, 2016 作者

#34楼 @ayeahaa exe就是用pyinstaller-2.0把FPStoHTML.py打包成exe,之后用Enigma Virtual Box把html模板和exe打在了一起。
版本老会有误报,pyinstaller2.1之后的打包无法在中文路径下用,一直没关注这个bug是否有解。

有python环境可以直接用py,我打成exe也是为了方便手工执行的人不用装python环境罢了。

浮云 #36 · May 30, 2016 作者

#34楼 @ayeahaa 试了下PyInstaller-3.2已经没有中文路径不能执行这个bug了,用PyInstaller-3.2重新打包个exe就没有误报木马这个问题了。
PyInstaller-3.2需要把pefile的pefile.py、peutils.py和ordlookup文件夹放到PyInstaller-3.2的根目录下。

#32楼 @sandman 有联系方式吗?有几个问题请教一下

浮云 #38 · June 27, 2016 作者

#37楼 @xiaolinzi TesterHome的QQ群可以找到我

新手报到,请问fps.sh运行出现/data/local/tmp/fps.sh[137]: 1467106541-: unexpected 'end of expression'错误需要如何解决;另外,fps相关数据未循环写入csv中应该怎样调整呢?@sandman

浮云 #40 · June 28, 2016 作者

#39楼 @lvkuchadechuntian 你没给busybox付权限吧。
adb shell chmod 755 /data/local/tmp/busybox

@sandman busybox付了权限的,可能是设备(诺基亚 N1平板)的问题,我换个设备再试试。感谢回答😀

浮云大神,是不是检测游戏性能的时候还是会不准的,我测试下来,发现游戏基本都是帧数都不变

浮云 #43 · July 21, 2016 作者

#42楼 @caicaiwu2016 游戏是监控SurfaceView。而且有些游戏是锁帧30的。

@sandman 是的,游戏里面好像不会超过30帧,是不是我用SurfaceView来做监听是获取不到引擎的数据的啊?

浮云 #45 · July 21, 2016 作者

#44楼 @caicaiwu2016 锁帧是游戏设计,很多游戏为了保证流畅度做了锁帧30的设计,这是代码写死的。

@sandman 但是游戏获取引擎的帧数,值是变化的

@sandman 而且,浮云大神,我发现root的手机使用service call SurfaceFlinger 1013命令是可以获取到帧数值的,但是用dumpsys命令是获取不到相关值的,这两者是不是有什么地方不一样啊?

浮云 #48 · July 21, 2016 作者

#46楼 @caicaiwu2016 引擎的帧数超30了?安卓的帧管理底层是SurfaceFlinger,而绘制时根据垂直同步时间来周期绘制的。 service call SurfaceFlinger 1013用的应该是句柄变化时间估算的,原理不太一样。SurfaceFlinger是利用每个window历史127帧的垂直同步时间进行的计算。
游戏引擎是通过GL在SurfaceView上绘制,实际帧率变化以垂直同步为准,引擎应该是已绘制耗时为准的吧。

遇到同样的问题,搜到两个不同的解决方法,大赞

没太理解jank(硬件渲染掉帧数)的计算方式,我用安卓6.0系统的手机执行了一下fps.sh取到的jank值一直是0,但是用 adb shell dumpsys gfxinfo 取到的Janky frames值和前者不一致,不知道问题出在哪里

浮云 #51 · August 30, 2016 作者

#50楼 @simple Jank是按照之前参考文章中的统计方式,把硬件掉帧记为Jank。这个是第三列减第一列的时间差,这个差值是硬件渲染耗时。总超时按OKT来统计的。

@simple 我在5.1的设备上也出现利用脚本获取的值一直是0.但使用三星的机器获取的jank值一直存在数据。
@sandman 你解释说Jank是第三列减第一列的时间差----是指利用adb shell dumpsys SurfaceFlinger --latency获取的三列数据吗?但是我使用这种方式获取的数据第一列和第三列的值并不相同,还存在jank值为0的情况。这部分能详细讲解一下吗?

#52楼 @yonglezhu adb shell dumpsys SurfaceFlinger --latency 会有128行数据,如果存在同名窗口会空行间隔追加另一组127行数据。
第一行代表刷新周期
第一列是开始绘制的时间
第二列是垂直同步的时间
第三列是硬件绘制完成的时间

jank非零代表硬件绘制掉帧,和屏幕硬件性能及相关驱动性能有关。
每两行垂直同步的时间差代表两帧绘制的帧间隔,如果差值小于第一行刷新周期或为负数,说明第二帧提前准本好的进行了又一次绘制,这里我是按刷新周期时长进行累加总时间的。

浮云 [Topic was deleted] 中提及了此贴 01 Sep 11:12

@sandman 求助!!!
在Mac上将csv文件转换成图表时,执行命令“ python FPStoHTML.py csv完整路径” 一直报错。求解答
错误提示
2016-09-14 16:22:08.224198 Copying BTM_HTML
/FPS_HTML
2016-09-14 16:22:08.224277 copy /FPS_HTML the 0th file
Traceback (most recent call last):
File "FPStoHTML.py", line 253, in
File "FPStoHTML.py", line 40, in copyFiles
OSError: [Errno 2] No such file or directory: '/FPS_HTML'

#55楼 @xieyexin 多明显的提示“OSError: [Errno 2] No such file or directory: '/FPS_HTML'”,没找到模板所在文件夹。
我写脚本时习惯拖拽直接执行,配了默认的执行配置,都是拖拽的py脚本完整路径,就在脚本写了在脚本所在路径下查找FPS_HTML而不是缺省路径下查找。
python FPStoHTML.py完整路径 csv完整路径,应该就正常了。或者你自己改下脚本的参数传入部分。

@sandman 非常感谢!私下联系以后忘记回复了,抱歉哈。
sandman大神人非常好,技术也很牛,很善于技术分享交流。相信他的团队氛围也很不错。大家支持一下他们团队的招聘工作
https://testerhome.com/topics/4268

@sandman 有一些机器就无法适配?想问一下这个怎么修改

浮云 #59 · December 07, 2016 作者

#58楼 @merlin.zhou 本身这个方案就只有两点限制:
1、支持dumpsys SurfaceFlinger --latency命令
2、依赖push的busybox,(可以busybox官网下对应版本)

个别Rom在开发限制上可能会禁止dumpsys在非root下使用,很少量的Rom是做这种严格限制的。

#59楼 @sandman 其实不是不能执行,执行了却不能抓到fps。在测试中发现只要你用dumpsys SurfaceFlinger|grep "|....|"拿到当前窗口名,就无法抓到fps。你能留下一个联系方式吗?我测试的都是乐视的产品

浮云 #61 · December 26, 2016 作者

#60楼 @merlin.zhou 乐视的手机、TV、盒子都是可以正常使用脚本测试获取数据,可能是窗口名参数传错了。testerhome群里有我的qq,而且我的招聘贴也留过联系方式。

#60楼 @merlin.zhou 这个命令

dumpsys SurfaceFlinger|grep "|....|"

抓到的最上面一层未必是你需要的层,我测磨皮功能的时候遇到过,得试。脚本是管用的

小米工程师 目前负责性能方面工作 方便加微信交流下不 shaoxy1992 我们有自己性能优化交流群 里面有tx sf hy等性能负责人。

@sandman 本人小白一个,想问一下,如果想获取测试的性能数据,通过你这种方式与在PC端通过adb shell 不断地获取手机中的数据再发到PC端进行解析,有什么区别或者说你这种方式最大的好处在哪里?🌹

浮云 #65 · February 21, 2017 作者

#64楼 @lixingdeyongyuan 我实现的思路方式首先是考虑提供脱机解决办法,也就是执行后可断开连接,等结束后再收集处理结果。很实际的是测试过程可以减少设备占用,另一点是规避数据线或网络adb连接的稳定性及性能传输不稳定场景的因素。

所以我把部分数据分析处理的工作放在设备端,毕竟现在移动平台的硬件性能能力在正常使用下可以承担脚本开销。再有这只是实现抓取FPS的数据脚本,目前我已经把这部分改善维护成函数形式,整体应用到综合测试场景的数据抓取。

@sandman 感谢大神,你的这种方式和做一个apk,然后用这个apk(比如Emmagee)获取性能数据,又有什么区别呀?
刚才我还遇到了一个问题,就是在android 7.x的设备上循环调用dumpsys SurfaceFlinger --latency SurfaceView,只能获取到16666667,这是什么情况?另外通过dumpsys SurfaceFlinger|grep "|....|"也确定当前窗口为SurfaceView也执行过dumpsys SurfaceFlinger --latency-clear

浮云 #67 · February 24, 2017 作者

apk会受系统限制,比如管家的后台清理的管理,不在白名单会被后台服务回收的。dumpsys SurfaceFlinger --latency-clear是清除历史数据,之后新数据生成才能取到。无新数据的也会取到127行“0 0 0”。按窗口名抓取,不在显示的窗口无数据。安卓6.0之后,surfaceFlinger dump到的格式内容有变化,我是用dumpsys SurfaceFlinger|grep "|......|" 获取。

可以再加一个简单的批处理,将步骤串联起来,到时候只双击运行start.bat和stop.bat就可以了

浮云 #69 · March 16, 2017 作者
依兰听风 回复

初期介绍原理时做的,主要是考虑引导手底下人不要死执行,要了解原理才没写。目前这个工具方案做了维护,昨天竞品对比需求刚调整的兼容安卓7.0。

再有做分享是主要为了介绍原理和实现,本身这个fps计算的算法已经让我作为函数“模块”话,集成到其他监控专项的评估中了。

浮云 android 端取 cpu,fps,men,wifi/gprs 流量等值 中提及了此贴 28 Jul 11:23
71Floor has been deleted

感谢楼主的分享精神,我这边针对获取当前activiy比较麻烦,这边简单的做了个adb shell自动获取当前activity,并自动传参给fps.sh
脚本如下:

#!/system/bin/sh
window=$(adb shell dumpsys activity activities | sed -En -e '/Running activities/,/Run #0/p' | awk 'NR==3{print}' | awk '{print $5}')
echo $window
tempvar0="/"
tempvar1=${window%%/*}
tempvar2=${window#*/}
resultwindow=$tempvar1$tempvar0$tempvar1$tempvar2
echo $resultwindow

adb shell sh /data/local/tmp/fps.sh -t 60 -w $resultwindow
飞狐 回复

配surface名主要是按需定制,毕竟不同测试场景需要监控的surface不同,不一定就是一个activity。可能是surfaceView,甚至是特定弹窗的activity,主要为了匹配特定测试需求。其实做整体监控也可以全部抓数据后处理,这样就不用配参了,我另一个监控应用场景是动态取的数据,把当前所有surface帧率数据做保存,就是后续出图麻烦了些。

记录的Date时间对不上,不知道大家有没有该问题

浮云 #76 · December 08, 2017 作者
Leon 回复

这个时间就是为了对应下log时间点加的算了下结束帧对应时间,之前脚本没加时区的8小时,后来优化了逻辑和算法,见正文。

试了下,7.0华为麦芒6和 7.1.1 OPPO A77,都是获取不到性能数据的,楼主7.0及8.0的手机,都是用这个脚本来测试的么

浮云 #78 · January 30, 2018 作者
燕子 回复

7.0以上SurfaeViev窗口名改了,包名的窗口并没有变,需要注意下取SurfaeViev的窗口需要把实际窗口名补全,对帧率监控没影响,就是得注意传参。 SurfaceView的应该是“SurfaceView - org.videolan.vlc/org.videolan.vlc.gui.video.VideoPlayerActivity#0” 有空格注意加引号,其他的UI主窗口并没有变。

安卓7.0之后,就是不知哪个开发看meminfo的格式不爽加了千分位,和内存单位,我只是把内存监控重新适配了下。

浮云 #79 · January 30, 2018 作者

安卓7.0之后具体的窗口名在 adb shell dumpsys SurfaceFlinger最后的Allocated buffers中有,打开目标窗口实际取下就知道了

浮云 回复

多谢多谢,7.0和8.0的还都分别不一样,好坑啊

浮云 #81 · February 02, 2018 作者
燕子 回复

7.0是因为分屏,8.0是悬浮窗口,在SurfaceView的分辨上做的区分,格式都有调整

辛苦楼主了。最近在研究FPS这一块内容,我有个疑惑请教下,dumsys SurfaceFlinger出来的3列数据,计算fps只用第二列的上下行相减,和每行的第三列减第一列。哪个比较准确呢?我本地试发现第二列上下行相减有200多ms的,但是相应的用第三列减第一列都没有超过16.66ms的。两种方法感觉对不上

浮云 #83 · April 19, 2018 作者
Damon_N 回复

要先搞明白三列的意思,第二列是vsync时间,是通知上层准备帧数据的信号通知;第一列是帧数据交给SurfaceFlinger绘制的起始时间点;第三列是SurfaceFlinger绘制完成的时间点。其中第一列和第三列只有经过openGL绘制的才有时间,比如系统动效这两列只是相等的最大正整数。
vsync可用于帧率的统计计算,而第三列和第一例的差值可以衡量SurfaceFlnger绘制的性能。帧率高低用于评估流畅度,可SurfaceFlnger绘制的性能只是为了说明硬件性能承担不了绘制的帧数据大小,需要优化framework层的绘制性能及精简布局和图片,减轻SurfaceFlnger绘制压力。

84Floor has been deleted
浮云 回复

@sandman: could you please show the param of adb shell dumpsys SurfaceFlinger to get those info on Android 7 and 8?
I'm researching how to get it but stuck at this point.
Thanks

浮云 #86 · June 12, 2018 作者

step1: open the app
step2: adb shell dumpsys SurfaceFlinger
step3: find the window name in Allocated buffers

The window name has been added more info. Such as #?, the ? is a window's numbr for a same window name. For SurfaceFlinger has buffer to save the last 127 frames' info for each window. I design this scripts is for get fps by those frames' vsync time.

您好,看到您在別的帖回:
1、dumpsys gfxinfo 包名 framestats 命令只能获取除了SurfaceView和系统动效的帧数据;
2、获取SurfaceView和系统动效需要用到dumpsys SurfaceFlinger --latency 包名;不加包名即是系统动效的帧数据

請問您是怎麼知道的呢?我查了好久的相關資訊都查不到
若dumpsys gfxinfo 包名 framestats 獲得的不是SurfaceView,那他是獲得的是什麼數據呢?

as108104121 回复

看dumpsys gfxinfo源码分析的文章看到过,而且实际试验了,确实SurfaceView的都没记录。dumpsys gfxinfo取的是每个app,所有activity的绘制数据记录,统计数据累计,最多保存历史127帧绘制数据。

@sandman 请教一下 MFS(ms): Max Frame Spacing. 这个是指当前帧和上一帧的时间间隔吗? 那可以看成是上一帧的帧耗时吗?
您这边是将MFS最低设置为16.6(csv中数据是17)ms ?

Frames这个不太好理解~?

浮云 #90 · August 21, 2018 作者
小王子 回复

帧耗时和帧间隔不是一个概念,现在动辄双framebuffer及三framebuffer,framebuffer的准备时间都是提前预处理的,实际帧绘制开销我只是关注了jank,就是三列数据中第三列减第一列,在低端芯片的优化上有帮助,可以看到芯片能力的限制,推动framework优化及布局精简。
而帧间隔(抛去刷相同帧画面的情况)和人眼看到的间隔是差不多的。所以用它来参考屏幕实际流畅度体验。
MFS是最大一次帧间隔时间(小于500ms,因为我把大于500ms的算做了静置,没计算fps的意义)为了评估最卡一次有多卡
Frames就是一组样本数据中的总评估帧数。

浮云 #91 · August 21, 2018 作者
小王子 回复

你是锤子手机自动化的人?注册时间点很巧合,还是直接来看的我两年前这几篇分享。

浮云 回复

我不是锤子的喔~ 是搜索看到的,
Frames就是一组样本数据中的总评估帧数?

浮云 #93 · August 21, 2018 作者
小王子 回复

是的。
这个方案是两年前发的,后续陆续有些优化调整,并变成独立函数集成到了不同专项需求中,根本原理没变。

浮云 回复

awk后面感觉是混淆过的,读起来好累。。。😭

浮云 #95 · August 22, 2018 作者
小王子 回复

自己换行就好看了,我是不想再搞个.awk文件,就放到一行执行了,有时我自己写也是先写好再删换行符。awk -f 文件名.awk 也是可以执行,就是多了零碎的文件。

xiang
想请教一下这个使用Python是这样用的吗?生成了一个文件夹,打开html并没有内容,我猜是不是数据格式不对,然后执行dumpsys SurfaceFlinger|grep "|....|"也没看到楼主说的name列就是窗口名称,请指教一下

TD 回复

看下面回复,你这手机是安卓8.0的?窗口名需要看framebuffer中完整的,后面又#0这种,要不取不到。本身这个方案是16年写的,当然后续的7.0/8.0也能用,就是android的窗口名命名规则和SurfaceFlinger输出信息的格式变了。

@sandman 请教一下,我把帧率处理的格式化出来,加了一些注释,变量改一下名字,但是还是有几处不清楚,能抽空解答一下吗?

if(NR==1){ # 如果只有一行数据的话
r=$1/1000000; # r =16.6 刷新时间间隔
if(r<0)
r=$1/1000;
secondcolumn=0;
Frames=0;
WN=1
}
else{ #第一列:app里数据(bitmap/material等)准备时间,第二列:cpu将数据传递给h/whardware)绘制时的同步时间(这里可以理解为cpugpu绘制一帧时,二个线程的同步时间)第二行第三列表示:h/whardware)绘制完成的时间
if(Frames>0&&$0=="")
tag=1;
if(NF==3&&$2!=0&&$2!=9223372036854775807){ # NF 一行有3列【正常情况下】 第二列不等于0【正常情况】 第二列不等于那一串数字【正常情况】

refeshround=($3-$1)/1000000/r; # 第一列减第三列 / 1ms的纳秒数 / 刷新时间间隔 x应该为这次的帧绘制跨越了几次的刷新时间间隔

if(secondcolumn==0){ # 刚开始刷SurfaceFlinger的数据第二列是为0
secondcolumn=$2; # 第二列标示刷新的开始时间
Frames=1;
jank=0;
janktwo=0;
if(refeshround<=1) # 如果一个刷新周期内就完成绘制
totaltimediff=r; # C等于16.9ms
if(refeshround>1){ # 如果大于一个刷新周期的话
jank+=1; # jank就加1
totaltimediff=int(refeshround)*r; # C就等于x取整倍的r
if(refeshround%1>0) # 如果x不是整数的话,C要加上一个刷新周期?用意是???
totaltimediff+=r
};
if(refeshround>2) # 如果x大于2个刷新周期话
janktwo+=1;
MFS=r; # 初始MFS等于一个刷新周期
OKT=0
}
else{
timediff=($2-secondcolumn)/1000000; # timediff 第二列和上一行第二列的时间差->ms
if(timediff>500){ # 两帧之间的时间差
tag=1 # 大于500ms认为是用户没有操作
}
else{# 两帧如果是小于500ms
Frames+=1;
if(timediff>=r){ # 时间差大于等于一个刷新周期
totaltimediff+=timediff; # ???
if(timediff>kpi) # kpi=16
OKT+=1; # 超过kpi 16ms的次数就加1
if(timediff>=MFS) # 如果时间差大于记录的MFS,则MFS记录为时间差
MFS=timediff;
if(refeshround>1)
jank+=1;
if(refeshround>2)
janktwo+=1;
secondcolumn=$2 #
}
else{ # 如果时间差小于一个刷线周期
totaltimediff+=r;
secondcolumn=sprintf("%.0f",secondcolumn+r*1000000) # ?为啥第二列要加上一个刷新周期呢?
}
}
};
if(Frames==1) # 我理解是如果是第一帧的时候,把FU时间记录下来
FU=sprintf("%.3f",$2/1000000000)
};
if(Frames>0&&tag==1){ # tag0的时候表示是超过了127行后的一个空行,
tag=0;
if(Frames==1) # Frames如果等于1的话就认为是缓存帧?
LU=sprintf("%.3f",FU+totaltimediff/1000); # 把最后时间加上这一帧的时间,然后继续
else
LU=sprintf("%.3f",secondcolumn/1000000000);
Date=strftime("%F %Date",time+LU)"."sprintf("%.0f",(time+LU)%1*1000);
FPS=sprintf("%.2f",Frames*1000/totaltimediff); # 帧率 出现的帧数【大于1*1000 / 两帧的时间差 1*1000 / 16.6 = 60FSS
MFS=sprintf("%.0f",MFS);
g=FPS/target; # 实际帧率和目标帧率的比值
if(g>1) # 如果实际帧率大于了目标帧率
g=1;
h=kpi/MFS; #kpi 16ms / 两帧时间差
if(h>1)
h=1;
SS=sprintf("%.2f",g*60+h*20+(1-OKT/Frames)*20); # 流畅度 = 实际帧率比目标帧率比值*60【目标帧率越高越好】 + 目标时间和两帧时间差比值*20【两帧时间差越低越好】 + (1-超过16ms次数/帧数)*20【次数越少越好】
print FU"\t"LU"\t"Date"\t"FPS+0"\t"Frames"\t"jank"\t"janktwo"\t"MFS"\t"OKT"\t"SS"\t"WN;
Frames=0; # Frames 重置为0
if($0==""){
secondcolumn=0;WN+=1
}
else{
secondcolumn=$2;Frames=1;jank=0;janktwo=0;
if(refeshround<=1)totaltimediff=r;if(refeshround>1){
jank+=1;
totaltimediff=int(refeshround)*r;
if(refeshround%1>0)
totaltimediff+=r
};
if(refeshround>2)
janktwo+=1;MFS=r;OKT=0
}
}
}
浮云 回复

我现在能取到数据,但是通过FPStoHTML.py文件生成不了图表是为什么呢?格式应该没有错吧?

浮云 #101 · September 25, 2018 作者
TD 回复

得看报错信息,一般是没按具体的要求执行。

浮云 #102 · September 25, 2018 作者
小王子 回复

你看下awk语法吧,很多语法理解不太对,好好理解下文本流处理的意思,awk是一套逻辑逐行处理。匹配每一行后会按代码逻辑处理数据。
原理就是处理了下三列数据的计算统计,主题逻辑中做了不同安卓版本的兼容,和异常数据情况的处理。再有就是累计的统计、输出时刻的设计。

浮云 回复

没有报错。。。如上上个截图上面显示的

浮云 #104 · September 26, 2018 作者
TD 回复

可以把csv和生成后的文件夹分享个链接给我,我看下结果。没看到具体情况,看不出问题,可以试下换火狐浏览器查看看下。

请问一下,游戏应用帧率有获取方法吗?

浮云 #106 · October 15, 2018 作者
cortana_x 回复

一样用,就是换下窗口名。一般游戏引擎开发的都是在SurfaceView中绘制,安卓7.0以下直接监控SurfaceView窗口,安卓7.0之后有了分屏,名字被改了。变成SurfaceView - 包名#0这种,具体取一下传进去就可以用,只是注意有空格需要英文双引号。

浮云 回复

请问下是这么用吗,比如说查看王者荣耀的FPS
adb shell sh /data/local/tmp/fps.sh -t 60 -w "SurfaceView - com.tencent.tmgp.sgame#0"

浮云 #108 · October 16, 2018 作者
cortana_x 回复

是这个格式,这个是实时窗口查看,也可以存文件出图。具体的窗口名得打开游戏用 adb shell dumpsys SurfaceFlinger 实际看下。

大佬 我用sh /data/local/tmp/fps.sh -t 60 -w SurfaceView -f /sdcard/check.csv 后生成的csv文件中没有数据是怎么回事 求解,谢谢!

浮云 #110 · October 31, 2018 作者
巩程师 回复

下面回了好几次了,测试窗口要和目标测试的一致,而且安卓7.1后窗口名改了,手机打开要测的窗口,实际获取后再测试。这种情况主要就是窗口名配错。

浮云 回复

好的,谢谢,再问一下我点击进入app页面控制台打印的是实时fps吗,我才介入性能这块,望大佬能指点一下

浮云 #112 · November 02, 2018 作者
巩程师 回复

理解下原理,数据源是安卓SurfaceFlinger的接口,安卓系统源生的数据,每个窗口都是独立的数据,只要窗口显示就会一直保存历史127帧的绘制数据。而获取数据上我的脚本是间隔1.6秒累计的从窗口创建到退出一直在统计。

浮云 回复

我用adb shell dumpsys gfxinfo 抓到帧率和你提供的工具获取的数据计算出来的fps 有差异 是不是我的计算方式和你的统计方式不一样啊,我现在不晓得怎么获取较为精准的页面刷新时的帧率

浮云 #114 · November 02, 2018 作者
巩程师 回复

每一帧绘制都是由vsync通知开始,我这个方式就是用vsync间隔来衡量的fps,刨除重复绘制相同画面这种异常情况(如反复刷布局),实际vsync就是和用户看到的帧一一对应的。gfxinfo是和帧绘制耗时相关的,你可以打开开发者模式中的显示数据到屏幕看到,每一帧的数据展示是实际开销,有小于vsync间隔的也有大于的,这部分并不是实际画到屏幕上的时间点。

浮云 回复

哦哦 ,现在APP的fps一般多少算达标,我们用第三方平台测得结果是平均fps只有28,但是我用您得工具基本上都高于这个数值,平均值也没这么低,我现在有点懵了,初期做app页面帧率测试应该从哪方面下手啊

浮云 #116 · November 02, 2018 作者
巩程师 回复

流畅度是按场景把控的,主要是滑动列表场景、动效场景等,具体看你们的把控范围要求。流畅度是硬指标,手机屏幕是60帧、MTK有些59帧屏的产品。UI交互场景帧率一般良好体验是要50帧以上的,重点关注帧间隔大于100ms的卡顿,游戏的话大于50ms的卡顿就很严重了,具体看是否锁帧,锁帧的一般是30帧,不锁的都是40帧起步,50帧以上良好。

浮云 回复

多谢大佬👍 👍 👍

想问一下这种方式是不是不能获取到unity的游戏的fps?自己测试了一下,数据都是0,不知道是不是我的命令没用对?

浮云 #119 · November 07, 2018 作者
tao 回复

窗口名错了,你取的是activity名,实际上窗口名是“SurfaceView - com.youzu.unity.test/com.unity3d.player.UnityPlayerActivity”,有空格传参需加双引号

还有就是adb一行命令执行,双引号需转译,要不就adb shell之后再获取。

120Floor has been deleted
浮云 回复

请问这个现在优化了吗 同名window共存的

我现在使用绘制出来是这样的,因为我有三个同名Surfaceview的原因吗

浮云 回复

嗯,谢谢,我已经通过这种方式计算出来FPS了

大佬,我使用adb shell sh /data/local/tmp/fps.sh -t 60 -w SurfaceView来获取MX4 Pro手机中一款游戏的FPS值时出现下图问题(其他机型都可以获取到),求解:

用楼主提供的脚步,测出音乐播放列表界面滑动帧率超过了 60,理论上手机最高帧率是60。这个是什么原因呢?
另外这个脚步是2年前的,请教楼主有最新的脚步能共享?

adb shell sh /data/local/tmp/fps.sh -t 60 -w SurfaceView来获取三星手机的参数,为什么没有数据,这样是什么意思???

浮云 #128 · March 13, 2019 作者
shansss 回复

一是你窗口配置宽度不够被自动换行了,二是你没看懂原理,窗口名是要配参的,要实际查到要监控的窗口名再测。

大神你好,想请教下,我这个弄出来这个样子是为什么啊?

浮云 #130 · April 25, 2019 作者
Cloris 回复

没影响,当初写的时候是为了取了下网卡的mac来标识设备用的,由于权限和芯片区别会使节点读取受限制。

但是我这个好像还是有点问题,能方便加个QQ嘛,方便点,谢谢

浮云 android 端监控方案分享 中提及了此贴 13 Aug 01:16
浮云 回复

请教大神,我执行的时候也碰到这个问题,如果在8.0的手机上直接运行adb shell sh /data/local/tmp/fps.sh -t 60 -w ***(activity名),也一样报这个错误(查了下好像是adrress这个目录没权限),且无数据输出,6和7的系统下运行正常,这要怎么弄呢?

浮云 #134 · August 29, 2019 作者
nancyyang0409 回复

这个不影响,可以注释掉,最初只是为了用mac地址对应下测试结果和手机。

浮云 回复

注释完之后,报错是没有了,但是数据还是没有

浮云 #136 · August 29, 2019 作者
nancyyang0409 回复

数据对不对要看窗口名是否对,这里的窗口名不是activity名,是SurfaceFlinger的framebuffer名。
adb shell dumpsys SurfaceFlinger 中最后一部分Allocated buffers: 下面有对应名字。因为安卓8.0之后有分屏,后面有#0 编号区分

浮云 #137 · August 29, 2019 作者

还有就是监控上整合在一起了,fps这个就没单独维护

simple Android FPS 方法探讨 中提及了此贴 25 Oct 10:03
浮云 Android FPS 方法探讨 中提及了此贴 29 Oct 23:54
140Floor has been deleted
tao 回复

能分享一下你的方法吗?使用楼主的方式实现的吗

浮云 #142 · March 26, 2020 作者
不二 回复

你可以看下最新的处理方式:https://testerhome.com/topics/20187

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up