GT3.1 的版本更新,带来了全面的维度分析。那么这些功能是如何实现的呢?本章 GT 君将详细的从 CUP 维度、内存维度、流量维度、流畅度维度为大家讲解这些功能的作用和实现原理。
/proc 文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为内核与进程提供通信的接口。 从 proc 文件中可以获取系统、进程、线程的 CPU 时间片使用情况,所以两次采集时间片的数据就可以获取进程 CPU 占用率, CPU 占用率 = (进程 T2-进程 T1)/(系统 T2-系统 T1) 的时间片比值。
获取系统 CPU 时间片使用情况:读取 proc/stat,文件的内容如下:
文件第一行各个字段的含义:
总的 CPU 时间:totalCpuTime = user + nice + system + idle + iowait + irq + softirq
获取进程 CPU 时间片使用情况:读取 proc/pid/stat,获取线程 CPU 时间片使用情况:读取 proc/pid/task/tid/stat,这两个文件的内容相同,如下:
标记中四位有对应字段的含义:
utime=41958:该任务在用户态运行的时间,单位为 jiffies
stime=31:该任务在核心态运行的时间,单位为 jiffies
cutime=0:累计的该任务的所有的 waited-for 进程曾经在用户态运行的时间,单位为 jiffies
cstime=0:累计的该任务的所有的 waited-for 进程曾经在核心态运行的时间,单位为 jiffies
进程的总 CPU 时间:
processCpuTime = utime + stime + cutime + cstime
线程的总 CPU 时间:
threadCpuTime = utime + stime + cutime + cstime
基础性能维度 CPU 取值是采用的是相对值:
processCpuTime /totalCpuTime。
线程时间片维度 CPU 取值采用的是每秒的绝对值,即 threadCpuTime。
我们统计的 CPU 使用,也已经将 GT 引入线程的损耗在总体的 CPU 使用中排除,因此结果可靠。
(1)系统内存总容量:只需要读取 “/proc/meminfo” 文件的第一个字段 “MemTotal” 就可以了,代表着系统所有可用的 RAM 大小,文件的内容如下:
(2)系统空闲的内存:只需要通过 ActivityManager 即可获取。
(3)系统已用内存:总内存与空闲内存做差。
(1)进程内存上限:
(2)进程总内存:
TrafficStats 类是由 Android 提供的一个从你的手机开机开始,累计到现在使用的流量总量,或者统计某个或多个进程或应用所使用的流量,当然这个流量包括的 Wifi 和移动数据网 Gprs。
获取进程流量的方法:
Android 系统每隔 16.7ms 发出垂直同步信号 (VSync 信号)(1000ms/60=16.66ms),触发对 UI 进行渲染, 如果每次渲染都成功,这样就能够达到流畅的画面所需要的 60 帧/s,为了能够实现 60 帧/s,这意味着计算渲染的大多数操作都必须在 16.7ms 内完成。
所以当绘帧间隔超过 16.7ms,垂直同步机制会让显示器硬件等待 GPU 完成栅格化渲染操作, 我们就可以说此时掉帧了,也就会造成用户直接感官的卡顿。
在这里,我们把 1 秒内 vSync 信号的次数,定义为流畅值,即 SM。
对于卡顿的不同情况我们分为以下两类:
(1)低流畅值区间:连续小卡顿造成的丢帧,即平均流畅值低于 40 帧/s 的区间;
(2)单次大卡顿:单次大卡顿造成的丢帧,既两次绘帧间隔大于 70ms,相当于丢了 4 帧以上的区间。
首先 Android 的帧绘制流程是:CPU 主线程图像处理->GPU 进行光栅化->显示帧。APP 产生掉帧的情况大多是由 “CPU 主线程图像处理” 这一步超负载引起的,所以我们思考如何去监控主线程绘制情况。要检测 CPU 绘制帧的时间,就必须找到那个调用 View.dispatchDraw 的类,Choreographer 类就是那个接受系统垂直同步信号 (VSync 信号),在每次接受 VSync 信号时顺序执行 View 的 Input、Animation、Draw 等 3 个操作,然后等待下一个信号,再次顺序执行 3 个操作。如果第二个信号到来时,Draw 操作没有按时完成,界面将不会更新,显示的还是第一帧的内容。这就表示丢帧了,丢帧是造成画面卡顿的原因。
所以我们可以向 Choreographer 类中加入自己的 Callback,通过此 Callback 的 doFrame 函数我们可以统计一秒内帧绘制的次数,即流畅值 SM,它能直观的代表当前时间段的流畅度。之所以不用 FPS 来代表当前流畅度,是因为 Android 系统默认在前台页面静止时,FPS 可能为 0,FPS 低无法直接代表当前处于卡顿。
利用 Choreographer.FrameCallback 计算流畅值相关代码:
上述代码中的 pushData 会记录 doFrame 的执行信息,这样就可以统计出 1S 内的执行次数,算出 SM。
要获取主线程的调用栈信息,我们需要创建一个新的线程,在新的线程中使用 uiThread.getStackTrace() 来获取主线程的栈信息。这里主要说明,采集栈信息的时机。
实现逻辑,使用 handler 的 postDelayed 方法延时发送 stackCollectRunnable 采集栈信息,延时时间为 interval(30ms,大于正常绘制 1 帧的时间,略小于正常绘制 2 帧的时间)。每一次帧绘制皆会回调 doFrame 函数,如果每次回调的时间间隔不超过 interval,那么消息队列中的栈采集消息将被移除,如果超时,那么消息未被移除,将开始栈信息的采集。基于此,就可以正确定位造成丢帧的耗时代码了。
建立一个新的线程:
栈采集实现:
从代码可以注意到,每次采集完成,会发起下一条采集。如果下一次 doFrame 回调小于 interval 时间,stackCollectRunnable 被移除,不再采集;如果下一次 doFrame 回调时间大宇 interval 时间,则 stackCollectRunnable 会执行采集操作;同时 doFrame 会在移除上一个 stackCollectRunnable 后新添一个 stackCollectRunnable,保证如此循环,就能把所有丢帧时主线程的调用栈记录下来。
下一篇《GT3.1 简化您的 App 性能测试(3)——原理讲解,溯本求源续》,GT 君将继续为大家讲解页面启动时长维度、布局的构建与绘制维度、数据库操作维度的实现原理。
未完待续……
项目开源地址:
https://github.com/Tencent/GT
如果您有好的建议,可以留言给我们,谢谢!
版权所属,禁止转载!
扫描下方二维码,关注微信公众号:腾讯移动品质中心 TMQ,获取更多测试干货!