摘要
这是2018年在小厂的老方案了,现在看方案已经过时了也不太合理,仅供参考,上层框架开启一个5分钟定时器,检测5分钟内总cpu负载和每个线程cpu负载情况,当检测到cpu负载大于绿盟性能或功耗定义的阈值时,结合场景策略,若满足非合理场景的cpu高负载异常,则根据不同等级策略进行对应cgroup控制CPU使用率、冻结和查杀等管控。
根据cpu负载值(cpu使用率)=CPU执行任务的时间与总时间的比例,故可以根据不同的获取方式有如下计算公式:
cpu负载值获取方式 | 计算公式 | 优点 | 缺点 |
adb shell top | 直接可查看进程级的cpu负载百分百值 | 获取方便且准确度高 | 本身top命令会存在高cpu负载的占用 |
adb shell top -H | |||
adb shell cat /proc/pid/stat | cpuload = (utime + stime / totalTime)*100% utime数据第14位,stime数据第15位 | 读文件节点获取,方便代码或脚本实现 | 线程数量多,有时需要频繁读节点就性能很不友好 |
Framework 框架读文件节点/proc/pid/stat | |||
内核层 | sched.h 中task_struc接口获取utime + stime,cpuload = (utime + stime / totalTime)*100% | 内存方式读取,性能效率最高,且本身cpu占用率极其低,0.3%以内 | 内核层到框架层通信和策略联动,虽然麻烦,从性能角度来说我觉得是最佳方案 |
Perfetto或trace | 线程的cpu负载值 = 该线程运行总时长 / 总时长 = WallDuration / totalTime | 直观准确 | 需要抓trace哈 |
adb shell dumpsys cpuinfo | 直接查看进程及对应线程的cpu负载百分百值 | 获取方便又详细且准确率高 | dump命令本身也会存在高cpu负载占用,即性能耗时 |
utime: 线程或进程在用户模式下花费的时间,单位是 jiffies。
stime: 线程或进程在内核模式下花费的时间,单位是 jiffies。
我认为的最佳方案是:内核层通过内存方式读取线程或进程用户态CPU时间(utime)和内核态CPU时间(stime)并换算为cpu负载值+ 框架层场景策略进行cpu高负载管控
1.cpu负载含义
因为很多人日常用语是cpu负载或cpu load,故本文cpu使用率后续都按cpu负载进行描述。
cpu使用率主要反映了CPU的忙碌程度,例如功耗待机分析中,发现灭屏下依旧有应用后台持续视频解码,带来media相关线程cpu高负载,导致cpu要一直处于工作,无法休眠,带来待机掉电快的现象。
本文的cpu负载特指cpu使用率,cpu使用率的含义:CPU在特定时间内被使用的比例,通俗的计算规则
单个线程cpu使用率 = (线程cpu运行时长 / 总时长)* 100% = (utime+stime / totalTime)* 100%
我觉得看trace文件可能更加直观描述cpu使用率:例如pid 786线程的cpu负载值 = 该线程运行总时长 / 总时长 = WallDuration / totalTime = 1552ms/9s832ms = 15%
上述中具体到代码中CPU负载(CPU使用率)分细为用户空间使用率(User CPU)和系统空间使用率(System CPU),分别表示在用户空间和内核空间执行的CPU时间比例。
2.cpu负载值获取方式
2.1.adb shell top
直接可查看进程级的cpu负载百分百值,优点:获取方便且准确度高,缺点:本身top命令会存在高cpu负载的占用
2.2.adb shell top -H
直接可查看线程级的cpu负载百分百值,优点:获取方便且准确度高,缺点:本身top命令会存在高cpu负载的占用
2.3.adb shell cat /proc/pid/stat
cpuload = (utime + stime / totalTime)*100%,其中utime数据第14位,stime数据第15位,优点:内存方式读取,性能效率最高,且cpu占用率及其低,缺点:线程数量多,有时需要频繁读节点就性能很不友好
如上获取的信息统计线程pid为1528的一些信息,其中第14位表示该线程在用户模式下的执行时间,第15位表示该进程在内核模式下的执行时间,单位为jiffies,通过这个可以获取线程的CPU使用率情况
2.3.Framework 框架读取/proc/pid/stat文件节点
cpuload = (utime + stime / totalTime)*100%,其中utime数据第14位,stime数据第15位,线程数量多,有时需要频繁读节点就性能很不友好,优点:内存方式读取,性能效率最高,且cpu占用率极其低,缺点:内核层到框架层通信和策略联动,虽然麻烦,从性能角度来说我觉得是最佳方案
// 等同于 adb shell cat /proc/pid/stat
public long getProcessLoad(int pid) {
StringBuilder targetPath = new StringBuilder();
targetPath.append(PTAH_PROC_INFO);
targetPath.append(pid);
targetPath.append(PATH_STAT);
String content = getContentWithOneLine(targetPath.toString(), String.valueOf(pid));
if (content != null) {
return getPorcseeTime(content);
}
return -1;
}
private long getPorcseeTime(String content) {
String[] conts = content.split(" ");
String str;
if (conts.length < 15) {
return -1;
}
str = conts[13];
String strSTime = conts[14];
long utime = parseLong(str);
long stime = parseLong(strSTime);
if (utime != -1 && stime != -1) {
return utime + stime;
}
return -1;
}
2.4.内核层直接内存读取cpu运行时间
android/vendor/kernel_platform/common/include/linux/sched.h,从task_struc获取utime + stime,cpuload = (utime + stime / totalTime)*100%,优点:内存方式读取,性能效率最高,且本身cpu占用率极其低,0.3%以内,缺点:
内核层到框架层通信和策略联动,虽然麻烦,从性能角度来说我觉得是最佳方案
struct task_struct {
...
u64 utime;
u64 stime;
...
}
2.5.Perfetto或trace换算cpu使用率
抓取trace日志方法
抓取trace日志方式1:抓atrace
adb shell "atrace -o /sdcard/systrace.trace -z -b 50000 -t 60 am wm view res ss gfx view hal bionic pm sched irq freq idle disk sync binder_driver binder_lock memreclaim dalvik input"
50000:缓冲区的大小,单位是消息数。
30:抓取时间,单位是秒。
导出trace
adb pull /sdcard/systrace.trace xxx(文件存放路径)
抓取trace日志方式2:perfetto命令
adb shell perfetto -t 10s sched freq idle am wm gfx view binder_driver hal dalvik camera input res memory power -o /data/misc/perfetto-traces/trace_file.perfetto-trace
10:抓取时间,单位是秒
导出perfetto
adb pull /data/misc/perfetto-traces/trace_file.perfetto-trace xxx(文件存放路径)
把Trace文件放到Perfetto UI即可,将cpu0~cpu7的区域框起来,我们就可以得到总时间和某个pid的线程运行时间。
例如pid 786线程的cpu负载值 = 该线程运行总时长 / 总时长 = WallDuration / totalTime = 1552ms/9s832ms = 15%
2.6.adb shell dumpsys cpuinfo
直接查看进程及对应线程的cpu负载百分百值,优点:获取方便又详细且准确率高,缺点:dump命令本身也会存在高cpu负载占用,即耗时
3.Framework层cpu高负载检测方案
从代码实现角度,肯定不适合直接top或dump了哈。故只需要Framework层读取节点或内核层内存读取utime + stime,然后对2次读取的时间点进行差值计算,就可以换算出对应cpu负载值了哈。
即cpuload = (utime + stime / totalTime)*100%,本文主要介绍Framework层的实现方案思路,内核层以后有缘再续.
3.1 cpu高负载检测
上层框架开启一个5分钟定时器,检测5分钟内总cpu负载和每个线程cpu负载情况,当cpu负载大于绿盟性能或功耗定义的阈值时,结合场景策略,若满足非合理场景的cpu负载占用,则根据不同等级策略进行对应cgroup控制CPU使用率、冻结和查杀等管控。
从文件节点/proc/stat读取数据,并换算系统cpu负载值(cpu使用率) = delta(userTime + nice + system)/delta(userTime + nice + system + idle + iowait + irq + softIrq) * 100%
从文件节点/proc/pid/stat读取数据,每个pid的负载值(cpu使用率)=delta(utime+stime)/delta(totalTime)* 100%
相关背景知识
/proc/stat 文件节点介绍 在 Linux 系统中,/proc/stat 文件提供了系统级别的统计信息,包括 CPU 使用情况。对于 CPU,这个文件提供了多个计时器,表示 CPU 在不同状态下花费的时间,单位通常是 jiffies(系统启动以来的时钟滴答数)。主要字段包括: user: 用户模式下花费的时间。 nice: 改变过优先级的用户模式下花费的时间。 system: 系统模式下花费的时间。 idle: 闲置时间。 iowait: 等待 I/O 完成的时间。 irq: 处理硬件中断的时间。 softirq: 处理软件中断的时间。 /proc/[pid]/stat 文件节点介绍 对于 Linux 系统中的每个进程,/proc 文件系统包含了一个以进程 ID 命名的目录,例如 /proc/[pid],其中包含了有关该进程的各种信息。在这个目录下,stat 文件提供了进程的状态信息,包括多个用于计算 CPU 使用率的时间度量值: utime: 该进程在用户模式下花费的时间,单位通常是 jiffies。 stime: 该进程在内核模式下花费的时间,单位同样是 jiffies。
3.2 性能管控策略
性能异常的阈值标准可以参考《软件绿色联盟应用体验标准3.0_性能标准》
绿盟性能标准文档链接:https://www.china-sga.com/spring/upload/doc/bb4ade0546b44a4c8bccb7f65a7df57a/%E8%BD%AF%E4%BB%B6%E7%BB%BF%E8%89%B2%E8%81%94%E7%9B%9F%E5%BA%94%E7%94%A8%E4%BD%93%E9%AA%8C%E6%A0%87%E5%87%863.0_%E6%80%A7%E8%83%BD%E6%A0%87%E5%87%86V1.1.pdf
3.3 功耗管控策略
功耗异常的阈值可以参考:软件绿色联盟应用体验标准6.0-功耗标准
绿盟功耗标准文档参考:https://www.china-sga.com/spring/upload/doc/e9c968ba5a284f80afaa286b6931c4cf/%E8%BD%AF%E4%BB%B6%E7%BB%BF%E8%89%B2%E8%81%94%E7%9B%9F%E5%BA%94%E7%94%A8%E4%BD%93%E9%AA%8C%E6%A0%87%E5%87%863.0_%E5%8A%9F%E8%80%97%E6%A0%87%E5%87%86V1.1.pdf
今天是春节前最后一天上班了,来年继续在这里搬砖。
cpu高负载检测的方案还是需要内核实现最佳,因为内存读取性能效率最高,且本身cpu负载占用率极其低可以到0.3%以内。