原文地址:https://developer.android.com/training/testing/performance.html
使用dumpsys gfxinfo 测UI性能
dumpsys是一款运行在设备上的Android工具,将 gfxinfo命令传递给dumpsys可在logcat中提供输出,其中包含各阶段发生的动画以及帧相关的性能信息。
> adb shell dumpsys gfxinfo < PACKAGE_NAME >
该命令可用于搜集帧的耗时数据。运行该命令后,可以等到如下的 结果:
Applications Graphics Acceleration Info:
Uptime: 102809662 Realtime: 196891968
** Graphics info for pid 31148 [com.android.settings] **
Stats since: 102794621664587ns
Total frames rendered: 105
Janky frames: 2 (1.90%)
50th percentile: 5ms
90th percentile: 7ms
95th percentile: 9ms
99th percentile: 19ms
Number Missed Vsync: 0
Number High input latency: 0
Number Slow UI thread: 2
Number Slow bitmap uploads: 0
Number Slow issue draw commands: 1
HISTOGRAM: 5ms=78 6ms=16 7ms=4 8ms=1 9ms=2 10ms=0 11ms=0 12ms=0 13ms=2 14ms=0 15ms=0 16ms=0 17ms=0 18ms=0 19ms=1 20ms=0 21ms=0 22ms=0 23ms=0 24ms=0 25ms=0 26ms=0 27ms=0
...
...
这里将逐一解释以上重点信息:
- Graphics info for pid 31148 [com.android.settings]: 表明当前dump的为设置界面的帧信息,pid为31148
- Total frames rendered: 105 本次dump搜集了105帧的信息
- Janky frames: 2 (1.90%) 105帧中有2帧的耗时超过了16ms,卡顿概率为1.9%
- Number Missed Vsync: 0 垂直同步失败的帧
- Number High input latency: 0 处理input时间超时的帧数
- Number Slow UI thread: 2 因UI线程上的工作导致超时的帧数
- Number Slow bitmap uploads: 0 因bitmap的加载耗时的帧数
- Number Slow issue draw commands: 1 因绘制导致耗时的帧数
- HISTOGRAM: 5ms=78 6ms=16 7ms=4 ... 直方图数据,表面耗时为0-5ms的帧数为78,耗时为5-6ms的帧数为16,同理类推。
那么以上这些数据是怎么统计得到的呢,这时候就要引入framestat了,framestat其实是每一个frame的相信信息,记录这不同阶段下的时间戳。也就是更精准的帧的时间戳信息。
精确的帧时间信息
在Android 6.0以后为gfxinfo 提供了一个新的参数framestats,其作用可以从最近的帧中提供非常详细的帧信息,以便您可以更准确地跟踪和调试问题。
> adb shell dumpsys gfxinfo < PACKAGE_NAME > framestats
此命令将应用程序生成的最后120帧信息打印出,其中包含纳秒时间戳。以下是来自adb dumpsys gfxinfo <PACKAGE_NAME>的示例原始输出framestats:
0 ,27965466202353 ,27965466202353 ,27965449758000 ,27965461202353 ,27965467153286 ,27965471442505 ,27965471925682 ,27965474025318 ,27965474588547 ,27965474860786 ,27965475078599 ,27965479796151 ,27965480589068 ,0 ,27965482993342 ,27965482993342 ,27965465835000 ,27965477993342 ,27965483807401 ,27965486875630 ,
27965487288443 ,27965489520682 ,27965490184380 ,27965490568703 ,27965491408078 ,27965496119641 ,27965496619641 ,0 ,27965499784331 ,27965499784331 ,27965481404000 ,27965494784331 ,27965500785318 ,27965503736099 ,27965504201151 ,27965506776568 ,27965507298443 ,27965507515005 ,27965508405474 ,27965513495318 ,27965514061984 ,
0,27965516575320,27965516575320,27965497155000,27965511575320,27965517697349,27965521276151,27965521734797,27965524350474,27965524884536,27965525160578,27965526020891,27965531371203,27965532114484,
此输出的每一行代表应用程序生成的一帧。每一行的列数都相同,每列对应描述帧在不同的时间段的耗时情况。下一节将详细介绍这种格式,包括每列代表的内容。
Framestats数据格式
由于数据块以CSV格式输出,因此将其粘贴到您选择的电子表格工具中非常简单,或者通过脚本进行收集和分析。下表说明了输出数据列的格式。所有的时间戳都是纳秒。
-
FLAGS
- FLAGS列为'0'的行可以通过从FRAME_COMPLETED列中减去INTENDED_VSYNC列计算其总帧时间。
- 如果非零,则该行应该被忽略,因为该帧的预期布局和绘制时间超过16ms,为异常帧。
-
INTENDED_VSYNC
- 帧的的预期起点。如果此值与VSYNC不同,是由于 UI 线程中的工作使其无法及时响应垂直同步信号所造成的。
-
VSYNC
- 花费在vsync监听器和帧绘制的时间(Choreographer frame回调,动画,View.getDrawingTime()等)
- 要了解有关VSYNC的更多信息以及它如何影响应用程序,请查看 Understanding VSYNC视频。
-
OLDEST_INPUT_EVENT
- 输入队列中最旧输入事件的时间戳,如果没有输入事件,则输入Long.MAX_VALUE。
- 此值主要用于平台工作,对应用程序开发人员的用处有限。
-
NEWEST_INPUT_EVENT
- 输入队列中最新输入事件的时间戳,如果帧没有输入事件,则为0。
- 此值主要用于平台工作,对应用程序开发人员的用处有限。
- 然而,通过查看(FRAME_COMPLETED - NEWEST_INPUT_EVENT),可以大致了解应用程序添加的延迟时间。
-
HANDLE_INPUT_START
- 将输入事件分派给应用程序的时间戳。
- 通过查看这段时间和ANIMATION_START之间的时间,可以测量应用程序处理输入事件的时间。
- 如果这个数字很高(> 2ms),这表明程序花费了非常长的时间来处理输入事件,例如View.onTouchEvent(),也就是说此工作需要优化,或者分发到不同的线程。请注意,某些情况下这是可以接受的,例如发起新活动或类似活动的点击事件,并且此数字很大。
-
ANIMATION_START
- 运行Choreographer注册动画的时间戳。
- 通过查看这段时间和PERFORM_TRANVERSALS_START之间的时间,可以确定评估运行的所有动画器(ObjectAnimator,ViewPropertyAnimator和常用转换器)需要多长时间。
- 如果此数字很高(> 2ms),请检查您的应用是否编写了自定义动画以确保它们适用于动画。
- 要详细了解Choreographer,请查看For Butter or Worse 视频。
-
PERFORM_TRAVERSALS_START
- PERFORM_TRAVERSALS_STAR-DRAW_START,则可以提取布局和度量阶段完成的时间。(注意,在滚动或动画期间,你会希望这应该接近于零..)
- 要详细了解渲染管道的度量和布局阶段,请查看 Invalidations,Layouts和Performance视频
-
DRAW_START
-
SYNC_QUEUED
- 同步请求发送到RenderThread的时间。
- 这标志着开始同步阶段的消息被发送到RenderThread的时刻。如果此时间和SYNC_START之间的时间很长(> 0.1ms左右),则意味着RenderThread忙于处理不同的帧。在内部,这被用来区分帧做了太多的工作,超过了16ms的预算,由于前一帧超过了16ms的预算,帧被停止了。
-
SYNC_START
- 绘图的同步阶段开始的时间。
- 如果此时间与ISSUE_DRAW_COMMANDS_START之间的时间很长(> 0.4ms左右),则通常表示有许多新的位图必须上传到GPU。
- 要了解有关同步阶段的更多信息,请查看 Profile GPU Rendering视频
-
ISSUE_DRAW_COMMANDS_START
- 硬件渲染器开始向GPU发出绘图命令的时间。
- 这段时间和FRAME_COMPLETED之间的时间间隔显示了应用程序正在生产多少GPU。像这样出现太多透支或低效率渲染效果的问题。
-
SWAP_BUFFERS
- eglSwapBuffers被调用的时间。
-
FRAME_COMPLETED
- 帧的完整时间。花在这个帧上的总时间可以通过FRAME_COMPLETED - INTENDED_VSYNC来计算。
你可以用不同的方式使用这些数据。例如下面的直方图,显示不同帧时间的分布(FRAME_COMPLETED - INTENDED_VSYNC),如下图所示。
这张图一目了然地告诉我们,大多数的帧耗时都远低于16ms(用红色表示),但几帧明显超过了16ms。随着时间的推移,我们可以查看此直方图中的变化,以查看批量变化或新创建的异常值。您还可以根据数据中的许多时间戳来绘制出输入延迟,布局花费的时间或其他类似的感兴趣度量。
帧耗时数据的 获取
如果在开发者选项中的CPU呈现模式分析中选择adb shell dumpsys gfxinfo,则adb shell dumpsys gfxinfo
命令将输出最近120帧的时间信息,并将其分成几个不同的类别,可以直观的显示各部分的快慢。
与上面的framestats类似,将它粘贴到您选择的电子表格工具中非常简单,或者通过脚本进行收集和解析。下图显示了应用程序生成的帧每个阶段的详细耗时。
运行gfxinfo,复制输出,将其粘贴到电子表格应用程序中,并将数据绘制为直方图的结果。
每个垂直条代表一帧动画; 其高度表示计算该动画帧所用的毫秒数。条形图中的每个彩色段表示渲染管道的不同阶段,以便您可以看到应用程序的哪些部分可能会造成瓶颈。有关了解渲染管道的详细信息以及如何优化渲染管道,请参阅 Invalidations Layouts和Performance视频。
关于如何控制framestats的搜集
framestats信息和frame耗时信息通常为2s收集一次(一次120帧,一帧16ms,耗时约2s)。为了精确控制时间窗口,例如,将数据限制为特定的动画 ,您可以重置所有计数器,并重新收集的数据。
> adb shell dumpsys gfxinfo < PACKAGE_NAME > reset
同样 也适用于需要捕获小于2s的数据。
诊断性能
dumpsys是能发现问题或者判断问题的严重性,但无法定位真正的原因。如果要定位原因,应当配合systrace工具使用,请参照systrace工具。
其他资源
有关Android渲染管道如何工作的更多信息,您可以在其中找到的常见问题以及如何解决这些问题,以下某些资源可能对您有用:
自动化UI性能测试
用户界面性能测试的一种方法是简单地让人工测试人员在目标应用上执行一组用户操作,并且可以直观地查找是否出现问题,或者使用工具驱动方法花费大量时间来查找它。但是这种手动方法充满了危险 - 人类感知帧速率变化的能力差别很大,这也是耗时,乏味和容易出错的。
更有效的方法是从自动UI测试中记录和分析关键性能指标。Android 6.0包含新的日志记录功能,可以轻松确定应用程序动画中jank的数量和严重程度,并可用于构建严格的流程以确定当前的性能并跟踪未来的性能目标。
要了解有关Android性能测试的更多信息,请参阅 自动化性能测试代码实验室。在此代码中,您将学习如何编写和执行自动化测试,并查看结果以了解如何提高应用性能。
网友评论