很多时候在使用APP的时候,手机可能会发热发烫。这是因为CPU使用率过高,CPU过于繁忙,会使整个手机无法响应用户,整体性能降低,用户体验就会很差,也容易引起ANR等等一系列问题。以下会根据实际app性能测试案例,展开进行app性能评测之CPU使用率的分析和总结。
在Linux系统下,CPU利用率分为用户态、系统态、空闲态,分别表示CPU处于用户态执行的时间,系统内核执行的时间,和空闲系统进程执行的时间。
平时所说的CPU利用率是指:CPU执行非系统空闲进程的时间 / CPU总的执行时间。
那么我们来看看这个时间究竟是什么?
先介绍几个和Linux时间有关的名词:HZ、tick与jiffies。
HZ:Linux 核心每隔固定周期会发出timer interrupt (IRQ 0),HZ是用来定义每一秒有几次timer interrupts。例如HZ为1000,就代表每秒有1000次timer interrupts。
Tick :Tick是HZ的倒数,Tick = 1/HZ 。即timer interrupt每发生一次中断的时间。如HZ为250时,tick为4毫秒(millisecond)。
Jiffies :在Linux的内核中,有一个全局变量:Jiffies。 Jiffies代表时间。它的单位随硬件平台的不同而不同。jiffies的单位就是 1/HZ。Intel平台jiffies的单位是1/100秒,这就是系统所能分辨的最小时间间隔了。每个CPU时间片,Jiffies都要加1。 CPU的利用率就是用执行用户态+系统态的Jiffies除以总的Jifffies来表示。
CPU利用率计算公式也就是:
CPU使用率=(用户态Jiffies+系统态Jiffies)/总Jiffies
腾讯GT
平安云测试助手+评测中心(http://fit-stg1.jryzt.com/Hyperion-server/html/index.html) ---极力推荐
安测试
腾讯APT
网易Emagee
CPU测试大家一般都使用外部提供的第三方工具来辅助测试,类似上方列举的这些、这些工具的原理都是基于调用 android 底层的一些 api 来获取到测试所用到的值,当然我们也可以采用其他方法,如使用 android 本身提供的一套 adb 即可完成上述测试。
CPU是系统非常重要的资源,在Android中/proc/stat, 包含了所有CPU的相关详情信息,查看CPU使用情况,CPU不是一个瞬时态,而是一个过程态的体现,一般可以使用top命令和dump cpuinfo命令进行CPU占用率获取。
一般获取Android CPU数据的有两个命令:top和dump cpuinfo
top是比较经典的CPU计算方法,top的代码在androidm/system/core/toolbox/top.c下面,输出process的cpu使用率在print_procs里面,CPU的计算是proc->delta_time * 100 / total_delta_time
先看total_delta_time由:
total_delta_time = (new_cpu.utime + new_cpu.ntime + new_cpu.stime + new_cpu.itime + new_cpu.iowtime + new_cpu.irqtime + new_cpu.sirqtime)
- (old_cpu.utime + old_cpu.ntime + old_cpu.stime + old_cpu.itime
+ old_cpu.iowtime + old_cpu.irqtime + old_cpu.sirqtime);
而这些变量的值,是在read_procs通过读取/proc/stat的jiffies得到:
所以总的cpu时间 = user + nice + system + idle + iowait + irq + softirq ,例如:User 147 + Nice 11 + Sys 79 + Idle 408 + IOW 1 + IRQ 0 + SIRQ 6 = 652
而proc->delta_time是两次读取/proc/pid/stat相减得到,可见,top是一段时间内,计算process的cpu jiffies与总的cpu jiffies差值得到。
附注释:
列名 | 描述 |
---|---|
/proc/stat文件: | 该文件包含了所有CPU活动的信息,该文件中的所有值都是从系统启动开始累计到当前时刻。不同内核版本中该文件的格式可能不大一致。 |
user (147): | 从系统启动开始累计到当前时刻,处于用户态的运行时间,不包含 nice值为负进程。 |
nice (11): | 从系统启动开始累计到当前时刻,nice值为负的进程所占用的CPU时间 |
system (79): | 从系统启动开始累计到当前时刻,处于核心态的运行时间 |
idle (408): | 从系统启动开始累计到当前时刻,除IO等待时间以外的其它等待时间 |
iowait (1): | 从系统启动开始累计到当前时刻,IO等待时间(since 2.5.41) |
irq (0) | 从系统启动开始累计到当前时刻,硬中断时间(since 2.6.0-test4) |
softirq (6): | 从系统启动开始累计到当前时刻,软中断时间(since 2.6.0-test4) |
adb shell top -m 100 -n 1 -s cpu | grep 包名
adb shell top -m 100 -n 1 -s cpu | grep com.pafinancialtech.fuzhoubank
zhangmeiyuan-5:~ zhangmeiyuan$ adb shell top -m 100 -n 1 -s cpu | grep com.pafinancialtech.fuzhoubank
18540 0 42% S 108 2102520K 317264K fg u0_a858 com.pafinancialtech.fuzhoubank
dump cpuinfo是Android特有的命令,dump cpuinfo命令的实现在androidm/frameworks/base/core/java/com/android/internal/os/ProcessCpuTracker.java类里面,方法是printCurrentState:
而printProcessCPU输出process CPU的使用情况:
private void printProcessCPU(PrintWriter pw, String prefix, int pid, String label,
int totalTime, int user, int system, int iowait, int irq, int softIrq,
int minFaults, int majFaults) {
pw.print(prefix);
if (totalTime == 0) totalTime = 1;
printRatio(pw, user+system+iowait+irq+softIrq, totalTime);
...
}
user+system+iowait+irq+softIrq就是totalTime。 st变量的赋值,在collectStats里面,st.rel_utime 和 st.rel_stime还是通过读/proc/pid/stat相减得到,而st.rel_uptime却是通过 SystemClock.uptimeMillis()差值,并不是跟top一样,通过proc/stat得到总CPU jiffies,
所以,进程的总Cpu时间processCpuTime = utime + stime + cutime + cstime,该值包括其所有线程的cpu时间。(例外,一般cpu按100%计算,如果是多核情况下还需乘以cpu的个数)
附注释:
/proc/pid/stat文件:
该文件包含了某一进程所有的活动的信息,该文件中的所有值都是从系统启动开始累计 到当前时刻。
Utime(39) 该任务在用户态运行的时间,单位为jiffies
Stime(25) 该任务在核心态运行的时间,单位为jiffies
Cutime(0) 所有已死线程在用户态运行的时间,单位为jiffies
Cstime(0) 所有已死在核心态运行的时间,单位为jiffies
adb shell dumpsys cpuinfo |grep 包名
zhangmeiyuan-5:~ zhangmeiyuan$ adb shell dumpsys cpuinfo |grep com.pafinancialtech.fuzhoubank
117% 16322/com.pafinancialtech.fuzhoubank: 106% user + 11% kernel / faults: 45403 minor 99 major
0.5% 14032/com.pafinancialtech.fuzhoubank:pushservice: 0.5% user + 0% kernel / faults: 1610 minor
+0% 16547/com.pafinancialtech.fuzhoubank:remote: 0% user + 0% kernel
从上图我们可以看出:80%是针对这个CPU的占用率是80%,其中72%占用率是用户使用的,8.4%是内核的占用率,这个数只是针对1核来说,现在手机都是多核的了,那这样的值也不会太准确,如果是多核情况下还需除以cpu的个数。
综上:top跟dump cpuinfo的区别在于:top分母是总测CPU jiffies,而dump cpuinfo是是时间,而并非jiffies,也能解释为什么top出来的cpu,大部分时间会比dump cpuinfo小的原因。
如果APP某场景进行操作时出现发烫、卡顿、ANR现象时,可以怀疑出现CPU问题,一般解决思路如下:
a. 如果已经导致ANR, 则去log里面搜索"ANR in"
b. 没有导致ANR则基于以上方法获取到的CPU占用率,如果某场景的CPU占用率走势异常、峰值存在异常、均值大于基线,则可以利用DDMS查看分析Trace文件,或者使用Android studio里面的Android Monitor根据Monitor中的CPU可以看出目前CPU明细使用。
c.查找程序中有没有特殊布局或者特殊操作(GPS定位,一直刷新类的服务等),特殊加载(Gif图片加载,视频,音频加载等)
拿到APP源码,在Android studio中构建测试DEBUG包进行调试如下截图:
双击我绿色框标记的这个按钮,就会生成这么一个文件,如图:
上图就一目了然的看到了耗费CPU 都有哪些方法。此时点击黑色的文本,几秒钟后studio会生成.trace文件,我们就可以分析各方法使用cpu的情况了。
TraceView 是 Android SDK 中内置的1个工具,它可以加载 trace 文件,用图形的情势展现代码的履行时间、次数及调用栈,便于我们分析。
生成Traceview 进行分析查看具体Trace期间各方法调用关系,调用次数以及耗时比例
附注释:
Traceview 面板分上下两部分
上面是时间轴面板 (Timeline Panel)
左侧显示的是线程信息
右侧黑色部分是显示执行时间段、白色是线程暂停时间段,
右侧鼠标放在上面会出现时间线纵轴,在顶部会显示当前时间线所执行的具体函数信息
下面是分析面板(Profile Panel) - 每一列内容
Inclusive time - 函数本身运行花费时间 + 函数调用其他函数时间
Exclusive time - 函数本身运行花费时间。
Calls + RecurCall/Total 调用 + 重复调用次数 / 函数总调用次数
Cpu Time/Call 总的Cpu时间与总的调用次数之比
附: Profile Panel各列作用说明
列名 | 描述 |
---|---|
Name | 该线程运行过程中所调用的函数名 |
Incl Cpu Time | 某函数占用的CPU时间,包含内部调用其它函数的CPU时间 |
Excl Cpu Time | 某函数占用的CPU时间,但不含内部调用其它函数所占用的CPU时间 |
Incl Real Time | 某函数运行的真实时间(以毫秒为单位),内含调用其它函数所占用的真实时间 |
Excl Real Time | 某函数运行的真实时间(以毫秒为单位),不含调用其它函数所占用的真实时间 |
Call+Recur Calls/Total | 某函数被调用次数以及递归调用占总调用次数的百分比 |
Cpu Time/Call | 某函数调用CPU时间与调用次数的比。相当于该函数平均执行时间 |
Real Time/Call | 同CPU Time/Call类似,只不过统计单位换成了真实时间 |
Debug.startMethodTracing("shixintrace");
//开始 trace,保存文件到 "/sdcard/shixintrace.trace"
// ...
Debug.stopMethodTracing(); //结束
代码很简单,当你调用开始代码的时候,系统会生产 trace 文件,并且产生追踪数据,当你调用结束代码时,会将追踪数据写入到 trace 文件中。
下1步使用 adb 命令将 trace 文件导出到电脑:
adb pull /sdcard/shixintrace.trace /tmp
使用代码生成 trace 方式的好处是容易控制追踪的开始和结束,缺点就是步骤略微多了一点。
一般cpu检测我们要分4种情况:
1.在空闲时间的消耗,基本没大应用使用cpu
如果APP在退出界面后还有进程长期运行,那需要关注下待机场景的CPU。待机场景下CPU的消耗一般不会很大,例如福州直销银行APP在后台运行时,可能消耗经常是0%,长时间平均下,可能只有0.1%、0.2%,看看竞品,也是差不多,好像没有太大区别。那么CPU消耗这么少是不是就不用管CPU了呢,然而即使是平均值很小,但是长时间待机,例如安全类工具,CPU的消耗还是不容忽视。
这种场景下我们测试时常用的单位有:消耗XX jiffies/分钟;半/1小时共增加XX jiffies。
2.在运行一些应用的情况下,cpu已占50%的情况下,观察应用程序占用cpu的情况
简单说这种情况就是后台已经有几个应用在运行已经并且消耗了系统的一些资源的情况下进行测试。
3.在高负荷的情况下看CPU的表现,我定义这个高负荷,cpu占用应是在80%以上
满规格状态下的应用CPU消耗情况
4.观察App 相同/不同场景下CPU走势、峰值情况
对比不同场景页面CPU占用大小
对比不同时间段同一场景页面CPU占用走势情况
此次质量开放平台-评测中心(http://fit-stg1.jryzt.com/Hyperion-server/html/index.html)的性能测试的采集的CPU占用数据主要是针对场景页面的CPU占用测试,CPU占用数据获取原理是CPU执行非系统空闲进程的时间 / CPU总的执行时间。
从CPU消耗对比来看,行业竞品均值为8.4%,90分位约4.9%,75分位约7.8%,中位数约9.3%,25分位约16.2%。
【榕商Bank】和10家竞品分析对比,CPU占用12.0%,表现较差,不及行业平均水准。但是从APP本品各场景CPU占用率来看,占用率最大的为理财产品详情页22.7%,主要原因是该页面存在6个不同时段近七日和万份收益率曲线走势图绘制,但是仍然未超过CPU占用率基线30%,且目前大部分手机是四核、八核系统,所以目前测试数据表明整体表现良好不存在瓶颈,但是从行业标准来看,理财产品详情页仍然有优化空间,建议优化。
(1)是否有非常多的网络请求
(2)是否开了很多进程OR 应用,尝试关闭其他应用再查看CPU是否降下来
(3)是否有大量大图片、视频处理跟加载或布局
(4)查找程序中有没有特殊布局或者特殊操作(GPS定位,一直刷新类的服务等),特殊加载(Gif图片加载,视频,音频加载等)
(5)当前页面是否有过多的图表、曲线图等绘制操作
(6)通过Android Studio 自带的monitor查找是哪个Activity或者哪个方法有一直不停止的运算消耗CPU(比如:不停止的while 或者for 循环)