android 中CPU 也是性能监控的重要指标之一,主要关注两个指标点:
- cpu 总使用率
- 应用程序 cpu 使用率
如何检测/监控 cpu 数据?
一种是在开发过程中使用工具去检测,如 DDMS 或者 android studio 的 cpu profile,也可以使用 adb shell top 去获取cpu 的具体使用情况,后面会一一介绍。
基本概念
在 android 中,cpu 的信息获取基本和 linux 一致,所以概念也是一致的,比如 cpu 时间片的划分,如果系统切换线程,需要系统调用,会消耗额外的资源。
linux 系统下,CPU 可能处于这几种状态,用户态,系统态,空闲态;
平时所说的CPU利用率是指:CPU执行非系统空闲进程的时间 / CPU总的执行时间。
linux 下 cpu 的时间片是以 Jiffies 为单位的,不同平台可能大小不一样。
开发环境获取 cpu 使用率
一般,我们使用 top 命令 和 dumpcpu info 命令去获取 cpu 占用率。
top 命令获取 cpu 使用率
top 命令如下:
adb shell top -m 100 -n 1 -s cpu | grep com.yy.hiyo
输出结果如下:
xiewenzhongdeMacBook-Pro% adb shell top -m 100 -n 1 -s cpu | grep com.yy.hiyo
22578 u0_a467 10 -10 1% S 159 2274080K 393856K ta com.yy.hiyo
或者进入 adb shell,间隔打印 cpu 调用情况,如下
xiewenzhongdeMacBook-Pro% adb shell
jason:/ $ top -d 5 | grep com.yy.hiyo
结果如下:
22578 u0_a467 10 -10 7% S 152 2267368K 401284K ta com.yy.hiyo
22762 u0_a467 20 0 0% S 44 1921224K 202512K fg com.yy.hiyo:pushservice
22578 u0_a467 10 -10 7% S 152 2267368K 401672K ta com.yy.hiyo
22762 u0_a467 20 0 0% S 44 1921224K 202512K fg com.yy.hiyo:pushservice
22578 u0_a467 10 -10 7% S 152 2267368K 401292K ta com.yy.hiyo
22762 u0_a467 20 0 0% S 44 1921224K 202512K fg com.yy.hiyo:pushservice
22578 u0_a467 10 -10 7% S 152 2267368K 398808K ta com.yy.hiyo
22762 u0_a467 20 0 0% S 44 1921224K 202512K fg com.yy.hiyo:pushservice
22578 u0_a467 10 -10 6% S 152 2267368K 401240K ta com.yy.hiyo
22762 u0_a467 20 0 0% S 44 1921224K 202512K fg com.yy.hiyo:pushservice
22578 u0_a467 10 -10 8% S 152 2267408K 404704K ta com.yy.hiyo
22762 u0_a467 20 0 0% S 44 1921224K 202512K fg com.yy.hiyo:pushservice
22578
一般排查 cpu 性能问题是否修复,肯定要用到这个命令的。
dump 命令获取 cpu 使用率
具体命令如下:
xiewenzhongdeMacBook-Pro% adb shell dumpsys cpuinfo |grep com.yy.hiyo
73% 22578/com.yy.hiyo: 55% user + 17% kernel / faults: 9777 minor
top 和 dump 的区别
top 是通过计算 cpu 时间片的,而 dump cpuinfo 是实际时间,所以,一般 top 的数值会比 dump 出来的数值要小一点。
使用 android studio cpu profile 获取 cpu 使用率
前面介绍的都是通过命令行去获取 cpu 信息,但是在我们的 ide 中,也包含了类似的功能,而且附带函数的分析结果,现在来看一下 android studio 的 cpu profile 怎么获得这个cpu 数据,并且我们怎么去分析这个数据。
这个就比较简单了,只要手机有个 debug 包,就能开始玩了~
这里简单说一下,怎么通过这个 cpu profile 去发现问题。
特定场景优化 cpu 指标
- 查看 flame chart,选择 thread time 查看耗时函数
- 查看不同线程的 cpu 使用情况,查找频繁切换 cpu 时间片的线程,看是否能够优化
自动测试如何获取 cpu 数据
读取 /proc 伪文件
/proc文件系统是一个伪文件系统,它只存在内存当中,而不会占用外存空间,它以文件系统的方式为内核与进程提供通信的接口。用户和应用程序可以通过 /proc 得到系统的信息,并且可以改变内核的某些餐你输。由于系统的信息,如进程是动态改变的,所有用户或者应用程序读取 /proc 目录中的文件时,/proc 文件系统会动态的从系统内核读取所需要的信息并且提交返回。
cpu 的使用率在 /proc/stat ,应用程序占CPU 使用率 /proc/<pid>/stat
先来看一些概念:
| CPU 指标 | 含义 |
|---|---|
| user | 用户态时间 |
| nice | 用户态时间(低优先级 nice>0) |
| system | 内核态时间 |
| idle | 空闲时间 |
| iowait | io等待时间 |
| irq | 硬中断 |
| sofirq | 软中断 |
先来查看 /proc/stat/,进入 adb shell,之后执行命令 cat /proc/stat,window 控制台输出如下
C:\Users\wumen>adb shell
HWELE:/ $ cat /proc/stat
//这两行的我自己加的
// user nice system idle iowait irq softirq stealstolen guest guest_nice
cpu 316074 61343 253065 1444324 6172 40364 19834 0 0 0
cpu0 44059 9452 46981 451869 2436 23154 10826 0 0 0
cpu1 49085 7764 43845 135308 317 4271 2779 0 0 0
cpu2 57030 12457 46039 129292 311 4997 2692 0 0 0
cpu3 53797 11908 45292 130017 270 4829 2634 0 0 0
cpu4 38564 4322 23863 144488 1805 1200 372 0 0 0
cpu5 35105 4924 18579 147982 713 972 295 0 0 0
cpu6 21082 5762 16562 151543 178 505 132 0 0 0
cpu7 17349 4751 11901 153822 138 433 101 0 0 0
intr 36009451 0 0 0 0 8990394 0 543378 4630 530 530909 536270 0 0 0 19683 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 399258 1948 1052126 0 0 0 0 0 0 0 0 0 0 71 0 1441531 24562 546 309283 63745 0 45943 0 0 0 0 19 0 54275 332468 0 0 0 0 1225 1036 1174 1132 941 905 887 710 162 0 0 0 189196 0 0 0 0 0 0 0 14297 0 0 0 0 0 0 0 53 0 360583 0 1303 0 0 0 0 0 0 6147 0 0 0 0 0 0 284 0 0 0 118 19 8893 853680 0 0 0 0 0 111 0 1 1 1 0 0 54 54 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 389 4044 0 10 0 25 0 0 3 0 232988 547 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 389 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 12 0 0 237 0 232 0 0 0 0 0 0 0 0 0 0 0 0 0 0 17759 122 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
ctxt 52644985
btime 1581121871
processes 39820
procs_running 5
procs_blocked 0
softirq 21861214 46801 5509747 115704 476006 237918 0 2571152 6232069 0 6671817
HWELE:/ $
- 这些时间单位都是 jiffies,jiffies 是linux 内核中的全局变量,用来记录系统启动以来产生的节拍数,可能是1-10ms 之间的数值,具体视内核版本。
- 具体的数值,我在上面标注了,可以参考表格或者阅读 linux 手册理解
那么怎么计算某个时刻cpu 的总体使用率呢?
首先要得到 cpu 的总体使用时间,也就是,第一行的全部数据:
totalCPUTime = user + nice + system + idle + iowait + irq + softirq + stealstolen + guest + guest_nice
那么 cpu 使用率的计算,则需要取两次数据,计算公式如下:
totalCPUrate = ((totalCPUTime2-idle2)-(totalCPUTime1-idle1))/(totalCPUTime2-totalCPUTime1)x100%
获取 app cpu 使用率也是类似的计算方式,读取的是 /proc/pid/stat 伪文件
具体的代码实现如下(kotlin):
private val procPath = "/proc/stat"
private var pid = 0
private val appProcPath = "/proc/$pid/stat"
private var mLastCpuIdle = 0L
private var mLastCpuTotalUse = 0L
private var mCpuUsageRate = 0L
private var mAPPLastCpuUsage = 0L
private var mAppCpuUsageRate = 0L
fun doSmaple() {
try {
//读取cpu
val ips = InputStreamReader(FileInputStream(procPath))
val bufferReader = BufferedReader(ips, BUFF_SIZE)
val cpuCodaRate = bufferReader.readLine()
if (pid == 0) {
pid = android.os.Process.myPid()
}
val appIps = InputStreamReader(FileInputStream(appProcPath))
val appBufferedReader = BufferedReader(appIps, BUFF_SIZE)
val appCpuCodeRate = appBufferedReader.readLine()
if (cpuCodaRate.isEmpty()) {
//do something
} else {
val cpuInfoArray = cpuCodaRate.split(" ")
if (cpuInfoArray.size >= 9) {
val user = cpuInfoArray[2].toLong()
val nice = cpuInfoArray[3].toLong()
val system = cpuInfoArray[4].toLong()
val idle = cpuInfoArray[5].toLong()
val ioWait = cpuInfoArray[6].toLong()
val total = user + nice + system + idle + ioWait +
cpuInfoArray[7].toLong() +
cpuInfoArray[8].toLong()
mCpuUsageRate = if (mLastCpuIdle <= 0L) {
0
} else {
val currentTotal = total - mLastCpuTotalUse
val currentIdle = idle - mLastCpuIdle
//计算app cpu 使用率
val appCpuInfo = appCpuCodeRate.split(" ")
if (appCpuInfo.size >= 17) {
val appTotalTime = appCpuInfo[13].toLong() +
appCpuInfo[14].toLong() +
appCpuInfo[15].toLong() +
appCpuInfo[16].toLong()
if (mAPPLastCpuUsage <= 0L) {
mAppCpuUsageRate = 0L
} else {
mAPPLastCpuUsage = appTotalTime
mAppCpuUsageRate =
(appTotalTime - mAPPLastCpuUsage) * 100 / currentTotal
}
}
(currentTotal - currentIdle) * 100 / (currentTotal)
}
mLastCpuIdle = idle
mLastCpuTotalUse = total
}
}
} catch (exception: Exception) {
exception.printStackTrace()
}
}
然后根据这个代码进行简单的测试。
自动使用命令获取 cpu 使用率
除此之外,还可以使用这个类 Runtime 类执行下面接口去获取 cpu 信息:
//Runtime类
public Process exec(String command) throws IOException {
return exec(command, null, null);
}
如何评价 cpu 使用异常
我们主要关注的是 cpu 的使用率,但是 cpu 的 使用情况,还要根据线程的使用情况去评估,就是找出值得优化的点,主要关注:
- Cpu 使用的峰值
- 关注线程的 stime 和 utime,可以忽略 io wait time 等
- 关注频繁切换cpu 时间片的线程