Android 高版本采集系统CPU使用率的方式

背景

cpu 信息作为性能监控四大常用指标 (cpu、内存、网络、磁盘)之一,对衡量设备性能,分析、确认一些线上性能问题有着较为重要的作用。

这里举个应用场景: 在得物APM平台的页面慢启动监控中,样本存在慢启动对应的函数火焰图中并未提取到明显耗时函数的情况,但通过分析其启动阶段的CPU使用率信息、CPU频率信息,发现其系统CPU使用率极高,而进程、主线程CPU使用率并不高,启动阶段主要的资源使用是cpu 及 io, 因为未存在IO阻塞函数,因此可以将此类问题归因为系统CPU负载高、主线程未分配到充足时间片来执行启动阶段的函数,因此导致页面慢启动问题。

image.png
image.png

计算系统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}/ 目录下提供的文件,可以获取单个进程的一些统计信息

[图片上传失败...(image-4ad5dc-1661864540228)]

除了procfs,另一个常用的文件系统是 sysfs,其根目录为/sys。 sysfs 是在procfs之后引入的,它将很多原本存在于procfs的信息迁移到sysfs,sysfs被设计用来导出设备树中呈现的信息,这样就不会使得 /procfs文件显得混乱。se有一个procfs sysfs相关区别问题的讨论,可以了解下: https://unix.stackexchange.com/questions/4884/what-is-the-difference-between-procfs-and-sysfs

在本文中,主要关注 /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数。

[图片上传失败...(image-4927d1-1661864540228)]

以下是解析 /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),相关讨论可见 https://issuetracker.google.com/issues/37140047, 普通应用程序已经无法访问/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文档:https://www.kernel.org/doc/Documentation/cpu-freq/cpufreq-stats.txt,

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 文档:https://www.kernel.org/doc/html/v4.14/admin-guide/pm/cpufreq.html。 这里简单描述下 /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状态下运行相关的统计信息,具体信息见文档:https://www.kernel.org/doc/Documentation/cpuidle/sysfs.txt

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的计算方式,在测试过程中 马上就发现了一些问题:

  1. 在采样周期内(如果较短) time并不一定会被更新,有可能会经过几个采样周期才会更新一次。 因此,如果没有注意到这个场景,在采样周期内 因为 cpu的 idle time值不变而认为当前cpu处于100%使用就是错误的,其实 cpu 是 100% 空闲的,因此计算出的cpu使用率可能比实际高。
  2. 同样的问题, 因为time 可能在几个采样周期后才更新,计算出的idle值 可能超过 这个采样周期内对应的cputime,因此如果没有正确处理这个场景的,这个采样周期计算出的idle时间过大,cpu使用率比实际低。

上面的问题,如果采样周期足够长(比如10s采样一次)通常不会有问题,有误差也在可接受范围内,如果是1s采样一次就会偏差很大。下面简单描述下这个问题的简单处理方案:

  1. 每次采样时,同时采集当前cpu的频率,当发现某个cpu在采样间隔内 idle time时间没有变化的时候,判断当前cpu是否处于最高频率下工作,如果是最高频率则无需调整,如果不是则 调整idletime 为采样周期的时间 (理论上也可以通过读取 state[x]/usage 计算cpu 进入idle状态的次数来判断,个人暂未验证)
  2. 针对第二个问题,如果采样时 计算出的 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等文件的代码,我们可以参考其实现 如: ProcStatsUtilProcTimeInStateReader

参考资料

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容