一、监控数据及工具概述


相信移动测试方向工作的同仁,针对业界经常用到的性能监控工具和常见的性能监控数据,都不会陌生。这里也简单的聊一聊,接触过的主要监控工具有:网易的Emmagee,腾讯的GT,阿里的EasyTest,讯飞的iTest。

相对而言比较简单,主要用于监控单个App的CPU、内存、流量,且仅适用于App的单进程,同时监控整机的CPU、剩余内存、电量、电流、温度、电压。因为Emmagee的功能比较简单,所以实现也简单些,后面原理也将主要根据Emmagee的代码来介绍。

侧重于随身调试,功能比较多,主要的监控数据包括单个App各进程的CPU、PSS内存、Private Dirty内存、jiffies,单个App整体的流量,此外,还可以监控到整机的CPU、内存、信号强度、FPS、电量。GT之所以称之为随身调,因为他除了监控功能之外,还包括查看日志、抓包、自定义输入参数的监控。

定位于无线研发场景的客户端测试工具,包括的基础监控功能,监控数据包括单个App的CPU、内存、流量、电量,EasyTest的辅助测试能力是其主要功能,也是一大特色,比如:一键启动monkey、弱网模拟等。EasyTest除客户端外,还支持将监控数据实时上传到云端,可同步在web平台查看数据趋势。

是讯飞出品的除Emmagee之外的国内另一款定位于监控的工具。iTest可以监控多个App或linux进程的CPU、PSS、流量、电量,针对多进程的App也同样适用,同时监控整机的CPU占用、剩余内存、CPU温度、FPS。此外,提供了CPU、内存、弱网等辅助测试场景的模拟。值得一提的是,iTest也提供了数据持久化,能够在web端查看数据走势图,且提供了版本间数据的对比。

针对以上几款工具,体验相关工具的最新版本后简单对比如下表1所示,大家也可以下载后体验,选取适合自己的工具。

总的来说,对于App,监控数据最常见的是CPU、PSS、流量;此外,为了分析问题,还需要有整机数据监控的能力,整机数据主要包括CPU、内存、电量,也有一些个别的做法:针对App的内存进行细分(nativie、dalvik),FPS数据获取。因本文主要介绍数据监控的内容,相关辅助能力及场景模拟能力不作详细介绍。


android 性能监控工具 安卓性能监视_App


二、方法介绍

相关数据的获取无外乎几个来源,一种是Android从linux继承来的特性,与进程相关的信息会保存在文件中,监控工具做的事情就是去读取解析文件;一种是Android提供的API,监控工具直接调API再进行相应的处理既可;还有一种是从Android命令中获取需要的数据,监控工具先采用Runtime调用命令获取命令返回值进而解析处理得到需要的数据。下文分别针对常见的几种数据获取原理进行介绍。

1.    PID/UID概念及获取


上面提到的三种方法,前两个都需要有一个参数,有了这个参数,才能把数据文件和应用对应上,这个参数通常是PID或者UID。

说到PID,UID,这里简单介绍一下,与linux、windows一样,Android中PID标识的也是进程号,其实一个Android应用可以是单进程,也可以是多个进程,多进程时一个App则对应多个PID,且App退出(进程销毁)再打开后进程号通常会被系统重新分配。UID与PID不同,UID与Android应用是一一对应的,是应用的身份标识,UID是App安装后固定不变的,从这里可以看出,这个与linux、windows中的UID也存在一些不一样的地方。

PID的获取其实并没有非常直接的API(比如根据App包名查询到),通常是采用曲线的方式,Android中可以获取所有当前运行的App应用进程,通过遍历查询这些进程的包名信息是否与被监控的App包名信息一致,来得到App的当前运行进程,获取进程后即可通过查询该进程信息的属性得到PID,UID也可以通过相同方法获得。当然如果这个App当前并没有在运行,那么PID则获取不到。Emmagee相关代码在ProcessInfo类中,摘取关键代码如代码块1所示。可以看到其在API大于等于22后的是通过解析top命令结果获取PID的。当App不在运行的时候,UID的获取可以直接使用API获取得到,代码可参见代码块2。

有了PID和UID,可以开始讨论其他数据的获取原理了。

android 性能监控工具 安卓性能监视_android 性能监控工具_02


代码块1 PID获取方法

android 性能监控工具 安卓性能监视_代码块_03


代码块2 UID获取方法

2.    进程CPU数据

进程CPU数据是根据PID通过读取系统生成的文件信息得到,文件路径为/proc/${PID}/stat。读取文件后,根据该文件参数定义挑选出该进程使用的CPU使用时间,Android中该文件如图1所示。图1以GT进程为例,当然为方便这里使用ps找到PID,另外在使用GT过程中,可以看到主要有几个值在变(方框里的22 12到中间的23 13到第三次的37 18),如果把这个文件以空格切分的话,那么方框里的4个数据是第14~17个。这四个值分别是utime,stime,cutime,cstime。分别代表进程在用户代码执行的累积时间、进程在内核代码执行的累积时间[i]、进程在用户代码等待线程执行的累积时间、进程在内核代码等待线程执行的累积时间[ii],单位为jiffies(通常是10ms)。不过通常情况下,cutime、cstime为0,这也就是为什么Emmagee代码CpuInfo类(代码块3)中并未计算这两个值的原因!这里要注意下,第14个对应数组元素的第13个。

说到这里,还只是介绍了进程的CPUTime值,这个值是累积值,既然是累积值,如果想得到CPU的瞬时值,则需要取到A时刻和B时刻的CPUTime值,相减即得到A时刻到B时刻该进程增加的CPUTime,最后只要除以A时刻到B时刻消耗的时间即得到该进程的CPU了。


android 性能监控工具 安卓性能监视_App_04

图1 进程CPUTime示意图


android 性能监控工具 安卓性能监视_代码块_05

代码块3 CPUTime获取

3.    整机CPU数据

A时刻到B时刻的时间通常是读取系统文件/proc/stat得到,第一行中cpu后面所有数值相加即得到系统开机后的累积时间,第4个代表系统开机后累积的空闲值,A时刻到B时刻消耗的时间则可以通过B时刻的开机累积时间减去A时刻的开机累积时间得到。利用空闲值则可以得到从A时刻到B时刻的整机CPU占用率。具体方法可参见Emmagee的CpuInfo类。

android 性能监控工具 安卓性能监视_android 性能监控工具_06


图2 整机总CPU统计文件图

4.    内存数据


内存只要获取当前值即可,并不像CPUTime存在累积的概念,相对而言比较简单,就是把PID作为参数使用API即可获取到。进程的内存数据获取如代码块4所示,这里摘自Emmagee代码MemoryInfo类,因为内存有很多数据,比如:PSS/VSS/RSS/USS,这里获取的是PSS内存(通常大家采用的是PSS,设置中应用占用内存也是PSS)。

android 性能监控工具 安卓性能监视_android 性能监控工具_07


代码块4 进程内存获取


系统总内存、剩余内存可以通过查看/proc/meminfo,如图3所示。剩余内存也可以通过API查询得到,可参见代码块5,同样摘自Emmagee代码MemoryInfo类。

android 性能监控工具 安卓性能监视_数据_08


代码块5 剩余内存获取

android 性能监控工具 安卓性能监视_代码块_09


图3 整机内存文件

5.    流量数据

App的CPU/PSS获取都是先获取PID,再计算该进程的CPU/PSS。而流量则是根据UID来获取。代码块6也是引入Emmagee的TrafficInfo类,其中的TrafficStats.getUidRxBytes(uid)是在Android2.2引入的API。

android 性能监控工具 安卓性能监视_android 性能监控工具_10


代码块6 流量数据获取

6.    多进程App的数据获取


以上介绍了单进程CPU/PSS的获取,对于多进程,方法类似,区别仅仅在于多个PID的获取上,代码块2其实是通过与包名比较得到PID,同样的,对于某多个进程的App,如果进程名与该App的多进程名之一相同,则作为需要监控的进程,直至找到所有的PID为止,找到所有的PID,根据相同的方法就可以找到所有进程的CPU/PSS。这也就是多进程App的数据获取思路。

因为流量是通过UID获取,所以对于单进程、多进程的App,流量数据获取方法是完全一致的。

三、总结


上面的方法介绍其实是通用的一些方法介绍,很多监控工具和脚本都采用类似方法,相信大家看后对此监控原理会有大体的认识,我们的iTest也是基于类似的方法,只是在某些实现方面做了优化,后面再解读我们iTest的优化点和具体方法。