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

浮云 · 2016年05月02日 · 最后由 testnupt 回复于 2022年04月22日 · 16312 次阅读
本帖已被设为精华帖!

开篇前述:

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

一、设计初衷

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)

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 130 条回复 时间 点赞

@ 浮云 您好,请问下游戏 FPS 和普通的 FPS 有什么区别,算法有什么不一样呢,谢谢!

浮云 回复

大佬你好,我用 adb shell dumpsys SurfaceFlinger --latency \"SurfaceView - 包名/窗口名 #1\"指令获取了数据,第一行是有的,下面 3 列的数据都是 0,这是怎么回事呢?

浮云 #63 · 2020年03月26日 Author
不二 回复

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

tao 回复

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

140楼 已删除
浮云 Android FPS 方法探讨 中提及了此贴 10月29日 23:54
simple Android FPS 方法探讨 中提及了此贴 10月25日 10:03
浮云 #59 · 2019年08月29日 Author

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

浮云 #58 · 2019年08月29日 Author
nancyyang0409 回复

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

浮云 回复

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

浮云 #57 · 2019年08月29日 Author
nancyyang0409 回复

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

浮云 回复

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

浮云 android 端监控方案分享 中提及了此贴 08月13日 01:16

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

浮云 #52 · 2019年04月25日 Author
Cloris 回复

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

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

浮云 #51 · 2019年03月13日 Author
shansss 回复

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

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

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

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

浮云 回复

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

浮云 回复

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

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

138楼 已删除
浮云 #64 · 2018年11月07日 Author
tao 回复

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

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

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

浮云 回复

多谢大佬👍 👍 👍

浮云 #41 · 2018年11月02日 Author
巩程师 回复

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

浮云 回复

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

浮云 #40 · 2018年11月02日 Author
巩程师 回复

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

浮云 回复

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

浮云 #38 · 2018年11月02日 Author
巩程师 回复

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

浮云 回复

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

浮云 #36 · 2018年10月31日 Author
巩程师 回复

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

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

浮云 #34 · 2018年10月16日 Author
cortana_x 回复

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

浮云 回复

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

浮云 #32 · 2018年10月15日 Author
cortana_x 回复

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

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

浮云 #31 · 2018年09月26日 Author
TD 回复

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

浮云 回复

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

浮云 #29 · 2018年09月25日 Author
小王子 回复

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

浮云 #28 · 2018年09月25日 Author
TD 回复

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

浮云 回复

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

@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
            }
    }
}
浮云 #26 · 2018年09月19日 Author
TD 回复

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

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

浮云 #22 · 2018年08月22日 Author
小王子 回复

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

浮云 回复

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

浮云 #20 · 2018年08月21日 Author
小王子 回复

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

浮云 回复

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

浮云 #19 · 2018年08月21日 Author
小王子 回复

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

浮云 #18 · 2018年08月21日 Author
小王子 回复

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

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

Frames 这个不太好理解~?

as108104121 回复

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

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

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

浮云 #14 · 2018年06月12日 Author

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.

浮云 回复

@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

135楼 已删除
浮云 #12 · 2018年04月19日 Author
Damon_N 回复

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

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

浮云 #10 · 2018年02月02日 Author
燕子 回复

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

浮云 回复

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

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

燕子 回复

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

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

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

Leon 回复

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

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

浮云 #144 · 2017年09月15日 Author
CC 回复

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

感谢楼主的分享精神,我这边针对获取当前 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
134楼 已删除
浮云 android 端取 cpu,fps,men,wifi/gprs 流量等值 中提及了此贴 07月28日 11:23
浮云 #130 · 2017年03月16日 Author
依兰听风 回复

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

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

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

浮云 #128 · 2017年02月24日 Author

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

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

浮云 #125 · 2017年02月21日 Author

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

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

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

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

#60 楼 @merlin.zhou 这个命令

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

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

浮云 #122 · 2016年12月26日 Author

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

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

浮云 #120 · 2016年12月07日 Author

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

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

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

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

浮云 #117 · 2016年09月21日 Author

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

@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'

浮云 [该话题已被删除] 中提及了此贴 09月01日 11:12
浮云 #114 · 2016年09月01日 Author

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

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

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

浮云 #112 · 2016年08月30日 Author

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

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

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

浮云 #109 · 2016年07月21日 Author

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

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

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

浮云 #107 · 2016年07月21日 Author

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

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

浮云 #105 · 2016年07月21日 Author

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

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

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

浮云 #101 · 2016年06月28日 Author

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

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

浮云 #99 · 2016年06月27日 Author

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

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

浮云 #97 · 2016年05月30日 Author

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

浮云 #96 · 2016年05月26日 Author

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

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

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

#3 楼 @zsg5566 很好的工具

浮云 #93 · 2016年05月13日 Author

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

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

浮云 #91 · 2016年05月06日 Author

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

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

浮云 #89 · 2016年05月06日 Author

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

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

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

浮云 #87 · 2016年05月05日 Author

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

浮云 #86 · 2016年05月05日 Author

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

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

浮云 #84 · 2016年05月05日 Author

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

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

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

浮云 #81 · 2016年05月04日 Author

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

浮云 #80 · 2016年05月04日 Author

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

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

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

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

浮云 #76 · 2016年05月03日 Author

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

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

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

浮云 #132 · 2016年05月03日 Author

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

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

浮云 #73 · 2016年05月03日 Author

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

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

hi

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

看过的最靠谱的

有意思,看起来不错

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

浮云 #69 · 2016年05月03日 Author

#5 楼 @CloudHuan Highcharts

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

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

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

浮云 #66 · 2016年05月02日 Author

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

141楼 已删除
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册