由于采购机械臂测试性能用例和屏幕实际帧率变化,最终工具方案实现后搁置,拿出来” 晒晒 “。
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)
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)
是东舟的机械臂吗? 我也使用过,感觉还是可以的,不过最后公司没有买。
那图形是什么做出来的 看着很高大上
#5 楼 @CloudHuan Highcharts
cool~UI 流畅度全套方案了~~
有意思,看起来不错
看过的最靠谱的
#10 楼 @zhangzhao_lenovo 什么图?csv 直接 excel 作图的?我工具里提供了 csv 转 html 交互图的脚本。
至于帧率,本身视频的帧率就是动态的,一般是 23 到 25 帧吧,得看具体的码流。如果帧率到了 50 到 60 帧就是 UI 刷新的帧率了,可能是加载缓冲,切换内容之类的 ui 展示操作引起帧率提高。
PS 播放器本身在处理视频流是是有丢帧处理的,视频播放场景主要可以判断的是帧率提高到 50-60 的现象基本都是卡顿加载,换内容切换的时刻。
测试是按窗口测试的,sh /data/local/tmp/fps.sh -t 25 -w SurfaceView。视频类流畅度我是按 25 帧目标值打分的,很多视频流都是 24 帧的。
#14 楼 @zhangzhao_lenovo 其实视频播放就是监控 SurfaceView 的绘制,弹幕的话得看是不是也是在这个 window 上绘制。
@sandman 你好,感谢分享,我在测试过程中遇到个问题,我想指定一个 surfaceview,然而查看当前显示的窗口名时,有两个同名的 surfaceview,请问楼主有木有经验指定其中一个 surfaceview
#18 楼 @fanlei1014 我知道这种情况,其实乐视的 SurfaceView 更多,live3 路视频流。后面还要上 9 路视频。。。
这个场景下监控 SurfaceView 获取的是所有 SurfaceView 窗口帧累计的情况,其实就是屏幕被 SurfaceView 分割不同部分分别刷新,但记录都是记在 SurfaceView 名下。由于是累计刷新帧率会相对单片源提高一倍左右。40 帧 + 的情况。多路场景将目标 KPI 提高进行评价就是了。
建议正常 UI 响应按 60 帧评价,单路视频流按 25 帧,多路视频流按 50
#17 楼 @zhangzhao_lenovo 写的很清楚啊基于 SurfaceFlinger 的数据,数据本身是安卓记录提供的,我只是拿来计算。计算方法和逻辑是我按自己目标需求设定的。
#21 楼 @fanlei1014 单路视频片源帧率还真低,看来视频码流和清晰度打折扣了。弹幕是刷的 ui 不是视频,相当于一路是视频的 15 帧,一路是弹幕 ui 的的 50-60。一路视频一路 ui 相当于 25+60 来评价了,不过视频本身也有扣分。
视频流卡顿刷 ui loading 也是会带来帧率上升的。相当于顺时两路 ui 的 50-60 范围帧率了。
弹幕场景还是按整体评估吧,毕竟用户体验需求是 “视频 + 弹幕 “
#23 楼 @fanlei1014 看了下数据,发现是我设计脚本逻辑时没处理这个场景。dumpsys SurfaceFlinger --latency SurfaceView 的数据在多窗口情况下是间隔一个空行保存的。数据本身是可以分别计算每个 SurfaceView 的帧率的,之前做完方案搁置,就没有把所有场景考虑全面。
后续有时间我更新下这种同名 window 共存情况的数据处理逻辑。
#23 楼 @fanlei1014 PS:为什么把弹幕做成了两个 SurfaceView,好奇怪。我看了下我们的弹幕 UI,视频流是一个 SurfaceView。弹幕是占用独立的 PlayerActivity 刷新。
我这对一个全屏的直播视频 关弹幕。监控 surfaceview 反而没有记录什么数据..
#27 楼 @zhangzhao_lenovo 先看窗口再测试,不是说所有播放都是用的 surfaceview,也有自己封装播放器播放的,例如 youtube
#29 楼 @zhangzhao_lenovo 。。。脚本执行说明很详细的写了
#31 楼 @xiaolinzi 哎,百度就知道 busybox 是干什么的了,这是手机端版本。
您好。请问我这里 FPStoHTML.exe 怎么提示是木马?而且,将生产的 csv 文件拉倒 FPStoHTML.exe,没反应
#37 楼 @xiaolinzi TesterHome 的 QQ 群可以找到我
新手报到,请问 fps.sh 运行出现/data/local/tmp/fps.sh[137]: 1467106541-: unexpected 'end of expression'错误需要如何解决;另外,fps 相关数据未循环写入 csv 中应该怎样调整呢?@sandman
#39 楼 @lvkuchadechuntian 你没给 busybox 付权限吧。
adb shell chmod 755 /data/local/tmp/busybox
@sandman busybox 付了权限的,可能是设备(诺基亚 N1 平板)的问题,我换个设备再试试。感谢回答
浮云大神,是不是检测游戏性能的时候还是会不准的,我测试下来,发现游戏基本都是帧数都不变
#42 楼 @caicaiwu2016 游戏是监控 SurfaceView。而且有些游戏是锁帧 30 的。
@sandman 是的,游戏里面好像不会超过 30 帧,是不是我用 SurfaceView 来做监听是获取不到引擎的数据的啊?
#44 楼 @caicaiwu2016 锁帧是游戏设计,很多游戏为了保证流畅度做了锁帧 30 的设计,这是代码写死的。
@sandman 但是游戏获取引擎的帧数,值是变化的
@sandman 而且,浮云大神,我发现 root 的手机使用 service call SurfaceFlinger 1013 命令是可以获取到帧数值的,但是用 dumpsys 命令是获取不到相关值的,这两者是不是有什么地方不一样啊?
#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 值和前者不一致,不知道问题出在哪里
#52 楼 @yonglezhu adb shell dumpsys SurfaceFlinger --latency 会有 128 行数据,如果存在同名窗口会空行间隔追加另一组 127 行数据。
第一行代表刷新周期
第一列是开始绘制的时间
第二列是垂直同步的时间
第三列是硬件绘制完成的时间
jank 非零代表硬件绘制掉帧,和屏幕硬件性能及相关驱动性能有关。
每两行垂直同步的时间差代表两帧绘制的帧间隔,如果差值小于第一行刷新周期或为负数,说明第二帧提前准本好的进行了又一次绘制,这里我是按刷新周期时长进行累加总时间的。
@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'
@sandman 非常感谢!私下联系以后忘记回复了,抱歉哈。
sandman 大神人非常好,技术也很牛,很善于技术分享交流。相信他的团队氛围也很不错。大家支持一下他们团队的招聘工作
https://testerhome.com/topics/4268
@sandman 有一些机器就无法适配?想问一下这个怎么修改
#58 楼 @merlin.zhou 本身这个方案就只有两点限制:
1、支持 dumpsys SurfaceFlinger --latency 命令
2、依赖 push 的 busybox,(可以 busybox 官网下对应版本)
个别 Rom 在开发限制上可能会禁止 dumpsys 在非 root 下使用,很少量的 Rom 是做这种严格限制的。
#60 楼 @merlin.zhou 乐视的手机、TV、盒子都是可以正常使用脚本测试获取数据,可能是窗口名参数传错了。testerhome 群里有我的 qq,而且我的招聘贴也留过联系方式。
#60 楼 @merlin.zhou 这个命令
dumpsys SurfaceFlinger|grep "|....|"
抓到的最上面一层未必是你需要的层,我测磨皮功能的时候遇到过,得试。脚本是管用的
小米工程师 目前负责性能方面工作 方便加微信交流下不 shaoxy1992 我们有自己性能优化交流群 里面有 tx sf hy 等性能负责人。
@sandman 本人小白一个,想问一下,如果想获取测试的性能数据,通过你这种方式与在 PC 端通过 adb shell 不断地获取手机中的数据再发到 PC 端进行解析,有什么区别或者说你这种方式最大的好处在哪里?
#64 楼 @lixingdeyongyuan 我实现的思路方式首先是考虑提供脱机解决办法,也就是执行后可断开连接,等结束后再收集处理结果。很实际的是测试过程可以减少设备占用,另一点是规避数据线或网络 adb 连接的稳定性及性能传输不稳定场景的因素。
所以我把部分数据分析处理的工作放在设备端,毕竟现在移动平台的硬件性能能力在正常使用下可以承担脚本开销。再有这只是实现抓取 FPS 的数据脚本,目前我已经把这部分改善维护成函数形式,整体应用到综合测试场景的数据抓取。
@sandman 感谢大神,你的这种方式和做一个 apk,然后用这个 apk(比如 Emmagee) 获取性能数据,又有什么区别呀?
刚才我还遇到了一个问题,就是在 android 7.x 的设备上循环调用 dumpsys SurfaceFlinger --latency SurfaceView,只能获取到 16666667,这是什么情况?另外通过 dumpsys SurfaceFlinger|grep "|....|"也确定当前窗口为 SurfaceView 也执行过 dumpsys SurfaceFlinger --latency-clear
apk 会受系统限制,比如管家的后台清理的管理,不在白名单会被后台服务回收的。dumpsys SurfaceFlinger --latency-clear 是清除历史数据,之后新数据生成才能取到。无新数据的也会取到 127 行 “0 0 0”。按窗口名抓取,不在显示的窗口无数据。安卓 6.0 之后,surfaceFlinger dump 到的格式内容有变化,我是用 dumpsys SurfaceFlinger|grep "|......|" 获取。
可以再加一个简单的批处理,将步骤串联起来,到时候只双击运行 start.bat 和 stop.bat 就可以了
初期介绍原理时做的,主要是考虑引导手底下人不要死执行,要了解原理才没写。目前这个工具方案做了维护,昨天竞品对比需求刚调整的兼容安卓 7.0。
再有做分享是主要为了介绍原理和实现,本身这个 fps 计算的算法已经让我作为函数 “模块” 话,集成到其他监控专项的评估中了。
感谢楼主的分享精神,我这边针对获取当前 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 时间对不上,不知道大家有没有该问题
这个时间就是为了对应下 log 时间点加的算了下结束帧对应时间,之前脚本没加时区的 8 小时,后来优化了逻辑和算法,见正文。
试了下,7.0 华为麦芒 6 和 7.1.1 OPPO A77,都是获取不到性能数据的,楼主 7.0 及 8.0 的手机,都是用这个脚本来测试的么
7.0 以上 SurfaeViev 窗口名改了,包名的窗口并没有变,需要注意下取 SurfaeViev 的窗口需要把实际窗口名补全,对帧率监控没影响,就是得注意传参。 SurfaceView 的应该是 “SurfaceView - org.videolan.vlc/org.videolan.vlc.gui.video.VideoPlayerActivity#0” 有空格注意加引号,其他的 UI 主窗口并没有变。
安卓 7.0 之后,就是不知哪个开发看 meminfo 的格式不爽加了千分位,和内存单位,我只是把内存监控重新适配了下。
安卓 7.0 之后具体的窗口名在 adb shell dumpsys SurfaceFlinger 最后的 Allocated buffers 中有,打开目标窗口实际取下就知道了
辛苦楼主了。最近在研究 FPS 这一块内容,我有个疑惑请教下,dumsys SurfaceFlinger 出来的 3 列数据,计算 fps 只用第二列的上下行相减,和每行的第三列减第一列。哪个比较准确呢?我本地试发现第二列上下行相减有 200 多 ms 的,但是相应的用第三列减第一列都没有超过 16.66ms 的。两种方法感觉对不上
要先搞明白三列的意思,第二列是 vsync 时间,是通知上层准备帧数据的信号通知;第一列是帧数据交给 SurfaceFlinger 绘制的起始时间点;第三列是 SurfaceFlinger 绘制完成的时间点。其中第一列和第三列只有经过 openGL 绘制的才有时间,比如系统动效这两列只是相等的最大正整数。
vsync 可用于帧率的统计计算,而第三列和第一例的差值可以衡量 SurfaceFlnger 绘制的性能。帧率高低用于评估流畅度,可 SurfaceFlnger 绘制的性能只是为了说明硬件性能承担不了绘制的帧数据大小,需要优化 framework 层的绘制性能及精简布局和图片,减轻 SurfaceFlnger 绘制压力。
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,那他是獲得的是什麼數據呢?
看 dumpsys gfxinfo 源码分析的文章看到过,而且实际试验了,确实 SurfaceView 的都没记录。dumpsys gfxinfo 取的是每个 app,所有 activity 的绘制数据记录,统计数据累计,最多保存历史 127 帧绘制数据。
@sandman 请教一下 MFS(ms): Max Frame Spacing. 这个是指当前帧和上一帧的时间间隔吗? 那可以看成是上一帧的帧耗时吗?
您这边是将 MFS 最低设置为 16.6(csv 中数据是 17) ms ?
Frames 这个不太好理解~?
帧耗时和帧间隔不是一个概念,现在动辄双 framebuffer 及三 framebuffer,framebuffer 的准备时间都是提前预处理的,实际帧绘制开销我只是关注了 jank,就是三列数据中第三列减第一列,在低端芯片的优化上有帮助,可以看到芯片能力的限制,推动 framework 优化及布局精简。
而帧间隔(抛去刷相同帧画面的情况)和人眼看到的间隔是差不多的。所以用它来参考屏幕实际流畅度体验。
MFS 是最大一次帧间隔时间(小于 500ms,因为我把大于 500ms 的算做了静置,没计算 fps 的意义)为了评估最卡一次有多卡
Frames 就是一组样本数据中的总评估帧数。
自己换行就好看了,我是不想再搞个.awk 文件,就放到一行执行了,有时我自己写也是先写好再删换行符。awk -f 文件名.awk 也是可以执行,就是多了零碎的文件。
xiang
想请教一下这个使用 Python 是这样用的吗?生成了一个文件夹,打开 html 并没有内容,我猜是不是数据格式不对,然后执行 dumpsys SurfaceFlinger|grep "|....|"也没看到楼主说的 name 列就是窗口名称,请指教一下
看下面回复,你这手机是安卓 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/w(hardware)绘制时的同步时间(这里可以理解为cpu和gpu绘制一帧时,二个线程的同步时间)第二行第三列表示:h/w(hardware)绘制完成的时间
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){ # tag为0的时候表示是超过了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
}
}
}
你看下 awk 语法吧,很多语法理解不太对,好好理解下文本流处理的意思,awk 是一套逻辑逐行处理。匹配每一行后会按代码逻辑处理数据。
原理就是处理了下三列数据的计算统计,主题逻辑中做了不同安卓版本的兼容,和异常数据情况的处理。再有就是累计的统计、输出时刻的设计。
请问一下,游戏应用帧率有获取方法吗?
一样用,就是换下窗口名。一般游戏引擎开发的都是在 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"
是这个格式,这个是实时窗口查看,也可以存文件出图。具体的窗口名得打开游戏用 adb shell dumpsys SurfaceFlinger 实际看下。
大佬 我用 sh /data/local/tmp/fps.sh -t 60 -w SurfaceView -f /sdcard/check.csv 后生成的 csv 文件中没有数据是怎么回事 求解,谢谢!
下面回了好几次了,测试窗口要和目标测试的一致,而且安卓 7.1 后窗口名改了,手机打开要测的窗口,实际获取后再测试。这种情况主要就是窗口名配错。
理解下原理,数据源是安卓 SurfaceFlinger 的接口,安卓系统源生的数据,每个窗口都是独立的数据,只要窗口显示就会一直保存历史 127 帧的绘制数据。而获取数据上我的脚本是间隔 1.6 秒累计的从窗口创建到退出一直在统计。
我用 adb shell dumpsys gfxinfo 抓到帧率和你提供的工具获取的数据计算出来的 fps 有差异 是不是我的计算方式和你的统计方式不一样啊,我现在不晓得怎么获取较为精准的页面刷新时的帧率
每一帧绘制都是由 vsync 通知开始,我这个方式就是用 vsync 间隔来衡量的 fps,刨除重复绘制相同画面这种异常情况(如反复刷布局),实际 vsync 就是和用户看到的帧一一对应的。gfxinfo 是和帧绘制耗时相关的,你可以打开开发者模式中的显示数据到屏幕看到,每一帧的数据展示是实际开销,有小于 vsync 间隔的也有大于的,这部分并不是实际画到屏幕上的时间点。
哦哦 ,现在 APP 的 fps 一般多少算达标,我们用第三方平台测得结果是平均 fps 只有 28,但是我用您得工具基本上都高于这个数值,平均值也没这么低,我现在有点懵了,初期做 app 页面帧率测试应该从哪方面下手啊
流畅度是按场景把控的,主要是滑动列表场景、动效场景等,具体看你们的把控范围要求。流畅度是硬指标,手机屏幕是 60 帧、MTK 有些 59 帧屏的产品。UI 交互场景帧率一般良好体验是要 50 帧以上的,重点关注帧间隔大于 100ms 的卡顿,游戏的话大于 50ms 的卡顿就很严重了,具体看是否锁帧,锁帧的一般是 30 帧,不锁的都是 40 帧起步,50 帧以上良好。
想问一下这种方式是不是不能获取到 unity 的游戏的 fps?自己测试了一下,数据都是 0,不知道是不是我的命令没用对?

窗口名错了,你取的是 activity 名,实际上窗口名是 “SurfaceView - com.youzu.unity.test/com.unity3d.player.UnityPlayerActivity”,有空格传参需加双引号
还有就是 adb 一行命令执行,双引号需转译,要不就 adb shell 之后再获取。
大佬,我使用 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 来获取三星手机的参数,为什么没有数据,这样是什么意思???
大神你好,想请教下,我这个弄出来这个样子是为什么啊?

但是我这个好像还是有点问题,能方便加个 QQ 嘛,方便点,谢谢
请教大神,我执行的时候也碰到这个问题,如果在 8.0 的手机上直接运行 adb shell sh /data/local/tmp/fps.sh -t 60 -w ***(activity 名),也一样报这个错误(查了下好像是 adrress 这个目录没权限),且无数据输出,6 和 7 的系统下运行正常,这要怎么弄呢?
数据对不对要看窗口名是否对,这里的窗口名不是 activity 名,是 SurfaceFlinger 的 framebuffer 名。
adb shell dumpsys SurfaceFlinger 中最后一部分 Allocated buffers: 下面有对应名字。因为安卓 8.0 之后有分屏,后面有 #0 编号区分
还有就是监控上整合在一起了,fps 这个就没单独维护
大佬你好,我用 adb shell dumpsys SurfaceFlinger --latency \"SurfaceView - 包名/窗口名 #1\"指令获取了数据,第一行是有的,下面 3 列的数据都是 0,这是怎么回事呢?
@ 浮云 您好,请问下游戏 FPS 和普通的 FPS 有什么区别,算法有什么不一样呢,谢谢!
打开百度网盘里的分享没有了,还能再分享下吗