背景
cpu 信息作为功能监控四大常用目标 (cpu、内存、网络、磁盘)之一,对衡量设备功能,剖析、承认一些线上功能问题有着较为重要的效果。
这儿举个运用场景: 在得物APM渠道的页面慢发动监控中,样本存在慢发动对应的函数火焰图中并未提取到明显耗时函数的状况,但经过剖析其发动阶段的CPU运用率信息、CPU频率信息,发现其体系CPU运用率极高,而进程、主线程CPU运用率并不高,发动阶段首要的资源运用是cpu 及 io, 由于未存在IO堵塞函数,因而能够将此类问题归由于体系CPU负载高、主线程未分配到充足时刻片来履行发动阶段的函数,因而导致页面慢发动问题。
核算体系CPU运用率
/proc 及 /sys 伪文件体系
在介绍详细完成时,需求先了解 一些要害的伪文件体系(pseudo filesystems),如procfs、sysfs。它们以文件的方法为内核与进程供给通讯的接口,但其信息只存在于内存傍边,而不占用外存空间。 用户和程序能够经过 读取 这些文件下的 目录及子目录下的相关文件得到体系供给的一些资源信息,咱们所知道的 ps、top、free等程序底层完成也是经过读取这些fs来获取体系的相关信息的。
举个比方,比方在/proc/cpuinfo 文件供给了每个cpu的相关信息(类型、缓存大小等)。
processor : 0
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
CPU implementer : 0x41
CPU architecture: 8
CPU variant : 0x2
CPU part : 0xd05
CPU revision : 0
processor : 1
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
CPU implementer : 0x41
CPU architecture: 8
CPU variant : 0x2
CPU part : 0xd05
CPU revision : 0
而/proc/stat文件供给了一切CPU的活动信息,该文件中的一切数值都是从体系发动开始累计到当时时刻
cpu 60174457 9663009 55832451 71782723 217812 9886952 2586380 0 0 0
cpu0 11196635 2001943 11939773 68088651 212914 2441300 665882 0 0 0
cpu1 11507874 2276717 11213445 436700 1056 2143323 556399 0 0 0
cpu2 11412154 2242954 11019498 440953 1110 2136361 523968 0 0 0
cpu3 4944155 744241 8900551 498496 1205 1987431 635147 0 0 0
cpu4 6428646 540160 3749526 564847 464 368445 63627 0 0 0
cpu5 6457629 570385 3797990 568408 499 369781 58906 0 0 0
cpu6 6400553 560137 3879073 567166 417 370833 57719 0 0 0
cpu7 1826808 726469 1332591 617497 144 69475 24730 0 0 0
intr 5891780796 0 0 0 1237792393 0 0 0 0 0 228957851 5575 4061 1310143 0 77591952 0 1928 1425 164383405 0 0 294267 0 17817217 14494461 90294 0 0 0 0 0 0 0 0 0 180421 41024658 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 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 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 1522 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 124 800439 0 0 0 0 0 0 0 0 0 0 0 0 47816585 614983 189532 0 0 0 0 0 0 0 0 0 0 0 0 2568146 62612 15144952 148716 211433 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 49109042 0 0 67340327 0 0 0 0 0 0 0 0 264827 32197 0 2 207 214 81540 48 2384 81 2165 19707 716458 0 2 0 2 355498 421 0 0 0 0 1788049 6956 2428 0 0 2007479 0 5997 0 0 0 0 0 0 0 10 0 0 0 0 0 0 0 0 0 3 673471 3 59 0 200861 1 6 11270883 0 0 1 0 1 0 1 0 1 0 1 0 1 42 9 10 0 0 10 1967 152466 3854 67 0 0 2354590 0 0 0 0 0 0 14536775 0 15033 142 395394 338959 60 903 12122483 1289409 0 0 0 0 0 0 32346 4617898 2602221 2081525 2177844 0 0 1140925 0 0 0 0 0 0 0 0 0 0 0 407084 46869118 32724648 785 2 0 0 0 0 0 0 0 0 0 0 0 0 4943944
ctxt 8547174056
btime 1658839741
processes 9876338
procs_running 1
procs_blocked 0
softirq 1499737914 1595873 498961882 9622280 70242366 145843255 0 15928568 411156254 37489 346349947
关于进程来说,经过/proc/${pid}/ 目录下供给的文件,能够获取单个进程的一些核算信息
除了procfs,另一个常用的文件体系是 sysfs,其根目录为/sys。 sysfs 是在procfs之后引入的,它将很多本来存在于procfs的信息迁移到sysfs,sysfs被规划用来导出设备树中出现的信息,这样就不会使得 /procfs文件显得混乱。se有一个procfs sysfs相关差异问题的评论,能够了解下: unix.stackexchange.com/questions/4…。
在本文中,首要重视 /sys/devices/system/cpu/ 目录下的信息,它供给了cpu的一些详细装备及活动信息,如 cpu最小、最大频率、cpu各频率活动时刻、cpu idle累计时刻等。
示例:读取 cpu${index}/cpufreq/scaling_cur_freq 能够获取某个cpu当时的工作频率
mars:/ $ cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
1209600
Android 8.0及以下
在Android低版别设备中,能够经过读取 /proc/stat 文件完成, /proc/stat 内容首行有8个数值, 别离供给了一切CPU在 用户态(user)、用户态-低优先级(nice)、内核态(sys)、闲暇态(idle)、io等候(iowait)、硬中止(irq)、软中止(softirq) 状况 下的时刻总和,将这些值累加作为体系总的CPU时刻(cpuTime),核算 iowait/cpuTime 为体系的CPU闲暇率,1-cpu闲暇率 及为cpu利用率 。留意这儿的时刻单位为 jiffies,一般一个jiffies 等于10ms。 在Android 体系下也能够经过Os.sysconf(OsConstants. _SC_CLK_TCK) 得到每秒的jiffies数。
以下是解析 /proc/stat 获取当时最新cpuTime的示例代码
private float getCPUTime() {
long cpuTime=0;
try {
if (mProcStatFile == null) {
mProcStatFile = new RandomAccessFile("/proc/stat", "r");
} else {
mProcStatFile.seek(0L);
}
String procStatString = mProcStatFile.readLine();
String procStats[] = procStatString.split(" ");
cpuTime = Long.parseLong(procStats[2]) + Long.parseLong(procStats[3])
+ Long.parseLong(procStats[4]) + Long.parseLong(procStats[5])
+ Long.parseLong(procStats[6]) + Long.parseLong(procStats[7])
+ Long.parseLong(procStats[8]);
return cpuTime
} catch (Exception e) {
}
return cpuTime;
}
Android 高版别完成计划
在Android 8.0以上版别,为了避免旁路进犯(Side Channel Attack),相关评论可见 issuetracker.google.com/issues/3714…, 一般运用程序现已无法访问/proc/stat 文件,所以无法经过/proc/stat 的方法核算体系cpu利用率。
另外阐明下,部分线下功能监控相关的开源库 如Dokit 会在Android8.0以上的设备 经过履行shell 命令 top -n 1 来直接获取某个进程CPU运用率信息,不过这种方法在高版别设备上也是无法运用的,得到的CPU运用率总是为0。
private float getCpuDataForO() {
java.lang.Process process = null;
try {
//调用shell 履行 top -n 1
process = Runtime.getRuntime().exec("top -n 1");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
int cpuIndex = -1;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (TextUtils.isEmpty(line)) {
continue;
}
int tempIndex = getCPUIndex(line);
if (tempIndex != -1) {
cpuIndex = tempIndex;
continue;
}
if (line.startsWith(String.valueOf(Process.myPid()))) {
if (cpuIndex == -1) {
continue;
}
String[] param = line.split("\s+");
if (param.length <= cpuIndex) {
continue;
}
String cpu = param[cpuIndex];
if (cpu.endsWith("%")) {
cpu = cpu.substring(0, cpu.lastIndexOf("%"));
}
float rate = Float.parseFloat(cpu) / Runtime.getRuntime().availableProcessors();
return rate;
}
}
} catch (IOException e) {
//...
} finally {
//...
}
return 0;
}
核算体系cpu运用率要害是获取cpu时刻 及idle 时刻。下面介绍另一种获取cputime 及 idletime的方法.
获取cputime
在 /sys/devices/system/cpu/cpu[x]/cpufreq/stats/ 目录下包括一些供给cpu频率相关的核算信息, 关于该目录下文件的详细阐明可参阅kernel文档:www.kernel.org/doc/Documen…,
knight-zxw:/ $ ls /sys/devices/system/cpu/cpu0/cpufreq/stats/
reset time_in_state total_trans trans_table
其间 time_in_state 供给了cpu 在每个频率下的运转时刻 (单位为10ms),该文件内容格局为多行文本,每行左边为频率值、右侧为在该频率下运转的时刻。
knight-zxw:/ $ cat /sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state
300000 0
403200 0
499200 0
595200 0
691200 55897525
806400 2729597
902400 1315020
998400 1019161
1094400 11892764
1209600 3945629
1305600 6093815
1401600 1252173
1497600 1166578
1612800 1782695
1708800 978913
1804800 20824808
咱们将文件内容 每行右边的数值累加 就是 cpuX 当时的运转时刻。 由于一个设备或许包括多个cpu 所以需求读取多个文件进行累加。
除了读取 /sys/devices/system/cpu/cpu[X]/cpufreq/stats/time_in_state 文件,还有一个方法是读取
/sys/devices/system/cpu/cpufreq/policy[X]/stats/time_in_state, 其文件内容共同。
关于 sysfs policy 相关内容可阅览linux 文档:www.kernel.org/doc/html/v4…。 这儿简略描绘下 /sys/devices/system/cpu/cpufreq/policy[X]/ 表明一个cpu频率战略,一般每个policy 操控多个cpu,一起进行频率操控(这些cpu硬件装备一般也相同)。以我的8核手机设备为例(现在Android的主流设备一般都包括8个中心),会供给 policy0、policy4、policy7 (一般别离操控 大中小核),每个policy操控的cpu能够经过读取/sys/devices/system/cpu/cpufreq/policy[x]/affected_cpus取得:
knight-zxw:/ $ cat /sys/devices/system/cpu/cpufreq/policy0/affected_cpus
0 1 2 3
该policy的最大频率、最小频率、当时频率 能够别离经过读取scaling_max_freq、scaling_min_freq、scaling_cur_freq获取。 由于policy下供给的 time_in_state 对应的是多个cpu的,因而读取 policy的 time_in_state相对 之前的方法能够读取更少的文件。
获取 idleTime
同频率相关的核算信息类似,在 /sys/devices/system/cpu/cpu[X]/cpuidle 下供给了每个cpu 在idle状况下运转相关的核算信息,详细信息见文档:www.kernel.org/doc/Documen…。
cpuidle目录下的子目录一般包括 driver 、以及多个 state[x]文件夹。
knight-zxw:/ $ ls /sys/devices/system/cpu/cpu0/cpuidle/
driver state0 state1
这儿的 state[x] 表明idle状况 休眠的深度,x值越大表明休眠状况越深,功耗越小、但进入和退出该状况的本钱也越大。 以不同状况的推迟度为例,在我的设备上 state0的推迟度为43, 而state1 的推迟度为531。
knight-zxw:/ $ cat /sys/devices/system/cpu/cpu0/cpuidle/state0/latency
43
knight-zxw:/ $ cat /sys/devices/system/cpu/cpu0/cpuidle/state1/latency
531
回到正文,/sys/devices/system/cpu/cpu7/cpuidle/state[x]/time 供给了 cpu 在 idle state[x] 下停留的时刻,因而经过累加 /sys/devices/system/cpu/cpu[x]/cpuidle/state[x]/time 的值即可获取 体系在idle状况的运转时刻(留意 这儿的时刻单位是微秒)
knight-zxw:/ $ cat /sys/devices/system/cpu/cpu0/cpuidle/state0/time
429942749686
而在 /sys/devices/system/cpu/cpu1/cpuidle/state1/usage下 记载了这个状况进入的次数
knight-zxw:/ $ cat /sys/devices/system/cpu/cpu1/cpuidle/state0/usage
555273877
idle 核算调整
上节讲了idle的核算方法,在测验过程中 立刻就发现了一些问题:
- 在采样周期内(假如较短) time并不一定会被更新,有或许会经过几个采样周期才会更新一次。 因而,假如没有留意到这个场景,在采样周期内 由于 cpu的 idle time值不变而以为当时cpu处于100%运用就是错误的,其实 cpu 是 100% 闲暇的,因而核算出的cpu运用率或许比实践高。
- 相同的问题, 由于time 或许在几个采样周期后才更新,核算出的idle值 或许超过 这个采样周期内对应的cputime,因而假如没有正确处理这个场景的,这个采样周期核算出的idle时刻过大,cpu运用率比实践低。
上面的问题,假如采样周期满足长(比方10s采样一次)一般不会有问题,有误差也在可接受规模内,假如是1s采样一次就会误差很大。下面简略描绘下这个问题的简略处理计划:
- 每次采样时,一起收集当时cpu的频率,当发现某个cpu在采样距离内 idle time时刻没有改变的时候,判别当时cpu是否处于最高频率下工作,假如是最高频率则无需调整,假如不是则 调整idletime 为采样周期的时刻 (理论上也能够经过读取 state[x]/usage 核算cpu 进入idle状况的次数来判别,个人暂未验证)
- 针对第二个问题,假如采样时 核算出的 CPU idle time 大于 采样周期的时刻,则将idle 调整为 采样周期的时刻,即以为在这个周期内该cpu彻底处于 idle状况。
上述调整的示例代码如下
public fun getSysIdleDeltaTime(
allCpu: List<Cpu>,
intervalMills: Long,
): Long {
//采样距离 奇妙
val realSampleIntervalMicros = intervalMills * 1000L;
// 返回的 采样周期内的 idle时刻
var totalIdleDeltaTime = 0L
for (cpu in allCpu) {
//获取cpu当时最新的idle时刻
val nowIdleTime = cpu.idleTime()
//获取上一次记载的该cpu idle时刻
val lastIdleTime = lastCpuIdleTimes[cpu.cpuIndex]
lastCpuIdleTimes[cpu.cpuIndex] = nowIdleTime
//第一次调用,只更新数据,直接越过
if (lastIdleTime == null) {
continue
}
var deltaIdleTime = (nowIdleTime - lastIdleTime)
if (deltaIdleTime == 0L) { //距离采样区间内idle时刻为0, 判别是CPU 100% use 还是 100% idle
//判别当时CPU是否处于根本满频运转
var maxFreq = 0L
//当时调频频率
val scalingCurFreq = cpu.cpuFreq.scalingCurFreq()
if (!allowReadScalingMaxFeqFile) {
maxFreq = cpu.cpuFreq.maxFreq()
} else {
try {
//读取当时的频率
maxFreq = cpu.cpuFreq.scalingMaxFreq()
} catch (e: Exception) {
//部分机型出现过读取失利的问题,未承认原因
allowReadScalingMaxFeqFile = false
maxFreq = cpu.cpuFreq.maxFreq()
}
}
//当时是否运转在最高频
val isRunningAtMaxFreq = maxFreq == scalingCurFreq
if (!isRunningAtMaxFreq) {
deltaIdleTime = realSampleIntervalMicros
}
} else if ((deltaIdleTime) > realSampleIntervalMicros) {
//一般idle时刻过长 根本是刚从idle状况退出,此时只能容错取采样周期作为idle时长
//因而本次采样周期的CPU运用率和实践状况有细微差别
deltaIdleTime = realSampleIntervalMicros
}
totalIdleDeltaTime += deltaIdleTime
}
return totalIdleDeltaTime / 1000;
}
核算 CPU SPEED
除了核算CPU利用率,咱们也能够收集cpu frequency 核算CPU频率相关的信息。
能够以cpu cluster为单位核算,在 /sys/devices/system/cpu/cpufreq/policy[x]/ 包括以下文件或目录
knight-zxw:/ $ ls /sys/devices/system/cpu/cpufreq/policy0/
affected_cpus cpuinfo_min_freq scaling_available_frequencies scaling_cur_freq scaling_max_freq schedutil
cpuinfo_cur_freq cpuinfo_transition_latency scaling_available_governors scaling_driver scaling_min_freq stats
cpuinfo_max_freq related_cpus scaling_boost_frequencies scaling_governor scaling_setspeed
这儿 cpuinfo_xxx 文件表明这些cpu硬件上支撑的频率信息( 最小频率、最大频率、当时频率),而 scaling_xxx 表明当时CPUFreq体系用相应调频驱动及战略进行调节时所支撑的频率信息,scaling_driver 表明当时的调频驱动,scaling_governor表明当时调频战略,比方现在常见的 schedutil governor。
体系出于一些功能上考虑,一般运用程序是无法读取 cpuinfo_cur_freq文件 ,在Java层直接读取该文件会抛出FileNotFoundException ,而 cpuinfo_min_freq 和 cpuinfo_max_freq 能够正常读取,究竟这些值是不变的。
因而核算cpu的当时频率运用状况 能够累加一切cpu的 scaling_cur_freq (单位为kHz)数值得出, cpu利用率百分比 能够经过 scaling_cur_freq/cpuinfo_max_freq 得出。
fun scalingMaxFreq(): Long {
return File(policyFile, "scaling_max_freq").readLong()
}
fun scalingCurFreq(): Long {
return File(policyFile, "scaling_cur_freq").readLong()
}
核算进/线程CPU运用率
经过读取/proc/{pid}/stat 文件能够获取进程的CPU运用信息, Android运用当时进程的pid 能够经过Process.myPid()获取。 在我的Android 12版别设备下 /proc/${pid}/stat的文件内容如下
14330 (uapp.apm.sample) S 845 845 0 0 -1 1077936448 34734 812 29 0 261 55 1 2 10 -10 41 0 181359922 7155191808 36467 18446744073709551615 1 1 0 0 0 0 4608 1 1073775868 0 0 0 17 1 0 0 0 0 0 0 0 0 0 0 0 0 0
由于不同的Android体系 对应linux内核版别不同,因而该文件的内容包括的信息也或许不同(一般是在后面新增一些信息),在linux3.5及以上总共包括52项信息。下面列出前25项信息的意义
按照次序其每个token表明的信息为:
(1) pid: 进程ID
(2) comm: 以圆括号包裹的程序名, 超过TASK_COMM_LEN(一般为16个字符)的名称会被截断
(3) state: 进程状况, R表明Running、S 表明Sleeping in an interruptible wait ,详见 文档 /proc/[pid]/stat 部分
(4) ppid: 父进程ID
(5) pgrp: 进程组ID
(6) session: 进程会话组ID
(7) tty_nr: The controlling terminal of the process.
(8) tpgid: The ID of the foreground process group of the controlling terminal of the process.
(9) flags: 当时进程内核标识位
(10) minflt: 非必须缺页中止次数 (非必须缺页表是无需从磁盘加载内存页)
(11) cminflt: 当时进程等候子进程的minflt
(12) majflt: 首要缺页中止次数 (首要缺页表是需求从磁盘加载内存页)
(13) cmajflt: 当时进程等候子进程的majflt
(14) utime: 当时进程处于用户态运转的时刻,单位为jiffies
(15) stime: 当时进程处于内核内运转的时刻,单位为jiffies
(16) cutime: 当时进程的一切子进程(包括子进程的子进程)在内核态履行的时刻
(17) cstime: 当时进程的一切子进程(包括子进程的子进程)在内核态履行的时刻
(18) priority: 动态优先级, 值是由体系剖析之后动态调整的,用户不能直接修正
(19) nice: 静态优先级,nice值取值规模[19,-20], 值越小表时优先级越高
(20) num_threads: 线程个数
(21) itrealvalue: 内核2.6.17后抛弃,值恒为0
(22) starttime: 自体系发动后的进程创立时刻
(23) vsize: 进程的虚拟内存大小,单位为bytes
(24) rss: 进程独占+同享库的内存页数
(25) rsslim: rss大小上线
经过解析该文本 取得 utime stime 核算 (utime+stime)/cputime ,即可得出进程的CPU运用率。
// 解析 procstat文件
fun readProcStatSummary(statFile: File): ProcStatSummary {
val procStatSummary = ProcStatSummary()
val statInfo = statFile.readText()
val segments = StringUtil.splitWorker(statInfo, ' ', false)
procStatSummary.pid = segments[0]
if (segments[1].endsWith(")")) {
procStatSummary.name = segments[1].substring(1, segments[1].length - 1)
}
procStatSummary.state = segments[2]
procStatSummary.utime = segments[13].toLong()
procStatSummary.stime = segments[14].toLong()
procStatSummary.cutime = segments[15].toLong()
procStatSummary.cstime = segments[16].toLong()
procStatSummary.nice = segments[18]
procStatSummary.numThreads = segments[19].toInt()
procStatSummary.vsize = segments[22].toLong()
return procStatSummary
}
// 经过 两次采样 核算采样距离内的 进程cpu运用率
fun calculateProcCpuUsage(prevProcStateSummary: ProcStatSummary, nowProcStateSummary: ProcStatSummary) {
procUsedCpuTimeMs = nowProcStateSummary.totalUsedCpuTimeMs - prevProcStateSummary.totalUsedCpuTimeMs
if (cpuTime > 0) {
procCpuUsage = procUsedCpuTimeMs.toFloat() / cpuTime
}
}
在/proc/[processId]/task 包括子线程相关的信息,在Java渠道每个Java Thread都对应一个实在的体系线程,遍历 task目录,内容如下:
knight-zxw:/proc/4348/task $ ls
4348 4357 4359 4361 4363 4365 4368 4383 4406 4444 4465 4477 4480 4482 4487 4490 4495 4501
4356 4358 4360 4362 4364 4367 4378 4386 4411 4463 4476 4478 4481 4484 4489 4491 4498
这儿每个文件夹都对应进程4348创立的一个子线程,其名称为线程的 体系thread id, 在 task/[tid]/目录下相同也包括 stat文件 记载该线程资源运用相关的核算信息
2|mars:/proc/4348/task $ cat /proc/4348/task/4357/stat
4357 (perfetto_hprof_) S 845 845 0 0 -1 4194368 9 2670 0 1 0 0 5 21 0 -20 30 0 198399608 7024758784 35104 18446744073709551615 1 1 0 0 0 0 20996 1 1073775868 0 0 0 -1 3 0 0 0 0 0 0 0 0 0 0 0 0 0
一些留意事项
兼容性问题
不同的厂商机型或许存在一些权限问题,因而在进行 cpu运用率监控模块采样线程运转前,应先测验相应文件是否能正常读取,假如不能正常读取则判别为当时设备不支撑。 在APM体系中,一般也是抽样进行监控,因而部分设备不支撑不影响全体衡量目标的有效度。
功能优化
在对文件文本内容进行解析时取值时,尽量不要采用正则匹配的方法 (比方调用 String.split(” “)), 比方运用StringTokenier 比 Stirng.split 分词功能会好几倍, 假如你需求收集一切的线程运用率信息,这个本钱就会被扩大了,由于大型APP 运转时或许包括 几百个线程 (各种三方库内部会创立线程、线程池)。
在android 体系源码中也存在 解析 procstat等文件的代码,咱们能够参阅其完成 如: ProcStatsUtil、ProcTimeInStateReader
最终对APM方向感兴趣的同学能够重视下我的功能监控专栏,会继续共享更多功能监控、优化相关的博文: /column/7107… ,专栏历史文章:
文章 | 地址 |
---|---|
监控Android Looper Message调度的另一种姿势 | /post/713974… |
Android 高版别收集体系CPU运用率的方法 | /post/713503… |
Android 渠道下的 Method Trace 完成及运用 | /post/710713… |
Android 怎么处理运用SharedPreferences 造成的卡顿、ANR问题 | /post/705476… |
基于JVMTI 完成功能监控 | /post/694278… |
参阅资料
- kernel doc :www.kernel.org/doc/html/v4…
- CPU调速器schedutil原理剖析:deepinout.com/android-sys…