Android CPU 使用率采集入门:从原理到公式

0 阅读11分钟

做 Android 性能测试,CPU 使用率是最基础的指标。但很多人只会用 top 看一眼,对背后的数据源和计算逻辑并不清楚。 本篇从概念讲起,先给公式、再看数据源、最后用真实数据走一遍完整计算。


一、CPU 使用率到底在度量什么

先明确一个概念:CPU 使用率衡量的是"时间片的分配比例",不是"算力的消耗比例"。

Linux 内核用一个叫 tick 的时间片来调度任务。每个 tick 大约 4~10 毫秒(取决于内核配置 CONFIG_HZ)。每个 tick 结束时,内核记录这个 tick 被分配到了哪个状态:用户态、内核态、空闲、等待 IO……

一句话定义:

CPU 使用率 = 一段时间内,非空闲 tick 占总 tick 的比例


二、核心公式(先看结论)

CPU 统计数据都是从开机开始的累积值,所以必须采两次、取差值才能算出一段时间内的使用率。

2.1 整机 CPU 使用率

第 1 次采样 → total₁, idle₁
     等待 N 秒
第 2 次采样 → total₂, idle₂

Δtotal = total₂ − total₁        ← 这段时间所有核心总共产生了多少 tick
Δidle  = idle₂ − idle₁          ← 其中有多少 tick 是空闲的

整机 CPU% = (Δtotal − Δidle) / Δtotal × 100

其中:

变量含义怎么得到
total所有状态 tick 之和user + nice + system + idle + iowait + irq + softirq + steal
idle空闲 tick/proc/stat 第一行的第 4 个数值字段

2.2 进程 CPU 使用率

第 1 次采样 → process_time₁(同时采整机 total₁)
     等待 N 秒
第 2 次采样 → process_time₂(同时采整机 total₂)

Δprocess = process_time₂ − process_time₁   ← 进程自己消耗的 tick 增量
Δtotal   = total₂ − total₁                 ← 整机总 tick 增量(同上)

进程 CPU% = Δprocess / Δtotal × 100

其中:

变量含义怎么得到
process_time进程累积的 CPU tickutime + stime + cutime + cstime
Δtotal整机总 tick 增量和整机公式用同一个分母

关键点:进程 CPU% 的分母是整机的 Δtotal,不是进程自己的数据。

含义是"这个进程在所有 CPU 资源中占了多少比例"。4 核设备上,单线程进程最高约 25%,4 线程跑满接近 100%。

2.3 公式小结

整机 CPU% = (Δtotal − Δidle) / Δtotal × 100

进程 CPU% = Δprocess / Δtotal × 100

就这两个公式,所有 CPU 采集工具(topdumpsys cpuinfo、各种 APM SDK)底层都是这个逻辑。


三、数据源:数据从哪来

3.1 整机数据:/proc/stat 第一行

Linux 内核提供的 CPU 统计接口,所有 Android 设备都有。读取后第一行格式如下:

cpu  10132153 290696 3084719 46828483 16683 0 25195 0 0 0

跳过开头的 cpu,后面 8 个数值依次是:

位置名称含义
1user用户态时间(App 代码执行)
2nice低优先级用户态时间
3system内核态时间(系统调用、驱动)
4idle空闲时间(CPU 在发呆)
5iowait等待 IO 的空闲时间(等磁盘/网络)
6irq硬中断处理时间
7softirq软中断处理时间
8steal虚拟化被偷走的时间

total = 上面 8 个字段全部相加。

3.2 汇总行 vs 各核:用哪一行

/proc/stat 的完整输出中,第一行下面还有每个核心的单独数据:

cpu  10132153 290696 3084719 46828483 16683 0 25195 0 0 0    汇总行(我们用这行)
cpu0 2503274  72633  771347  11709498 4180  0 6313  0 0 0    核心 0
cpu1 2522508  73222  770877  11707883 4115  0 6239  0 0 0    核心 1
cpu2 2505698  72460  771276  11706498 4198  0 6312  0 0 0    核心 2
cpu3 2514673  72381  771219  11704604 4190  0 6331  0 0 0    核心 3

汇总行 = 所有在线核心的累加。以 idle 字段验证:

cpu0.idle + cpu1.idle + cpu2.idle + cpu3.idle
= 11709498 + 11707883 + 11706498 + 11704604
= 46828483
= 汇总行的 idle ✓

所以:

问题答案
需要自己加各核数据吗?不需要,内核已经帮我们加好了
直接用汇总行就行?是的,只取第一行 cpu
关核了怎么办?关掉的核不出现在输出中,汇总行只含在线核
4 核 total 每秒增加多少?4 × HZ(HZ=100 时约 400 tick/秒)

3.3 进程数据:/proc/{pid}/stat

读取目标进程的 stat 文件,输出是一行很长的数据:

12345 (com.example.app) S 1 12345 12345 0 -1 ... 5000 1200 0 0 ...

我们关心的是第 14~17 个字段(从 1 开始计数):

字段号名称含义
14utime进程在用户态消耗的 tick
15stime进程在内核态消耗的 tick
16cutime已退出子进程的用户态 tick(累积)
17cstime已退出子进程的内核态 tick(累积)
process_time = utime + stime + cutime + cstime

四、完整计算实例

用一组真实格式的数据,从头到尾走一遍。

4.1 第 1 次采样

/proc/stat 第一行:

cpu  10132153 290696 3084719 46828483 16683 0 25195 0 0 0

解析:

user=10132153  nice=290696  system=3084719  idle=46828483
iowait=16683   irq=0        softirq=25195   steal=0

total₁ = 10132153 + 290696 + 3084719 + 46828483 + 16683 + 0 + 25195 + 0
       = 60,377,929

idle₁  = 46,828,483

同时读进程 /proc/{pid}/stat

utime=5000  stime=1200  cutime=0  cstime=0

process_time₁ = 5000 + 1200 + 0 + 0 = 6,200

4.2 等待 10 秒,第 2 次采样

/proc/stat 第一行:

cpu  10132953 290720 3085019 46832083 16690 0 25210 0 0 0

解析:

total₂ = 10132953 + 290720 + 3085019 + 46832083 + 16690 + 0 + 25210 + 0
       = 60,382,675

idle₂  = 46,832,083

同时读进程 /proc/{pid}/stat

utime=5350  stime=1280  cutime=0  cstime=0

process_time₂ = 5350 + 1280 + 0 + 0 = 6,630

4.3 算差值

字段第 1 次第 2 次差值(Δ)
user10,132,15310,132,953800
nice290,696290,72024
system3,084,7193,085,019300
idle46,828,48346,832,0833,600
iowait16,68316,6907
irq000
softirq25,19525,21015
steal000
total60,377,92960,382,6754,746

为什么 10 秒产生了 4746 个 tick?因为 4 核设备,每个核每秒约产生 HZ 个 tick(HZ≈100~120),4 × ~119 × 10 ≈ 4746

4.4 代入公式

整机 CPU%

Δtotal = 4,746
Δidle  = 3,600

整机 CPU% = (Δtotal − Δidle) / Δtotal × 100
          = (4746 − 3600) / 4746 × 100
          = 1146 / 4746 × 100
          ≈ 24.1%

→ 过去 10 秒,4 个核心总共有 24.1% 在干活,75.9% 在空闲

细分一下各状态占比:

user   占比 = 800  / 4746 × 100 = 16.9%   ← 用户态(App 代码)
system 占比 = 300  / 4746 × 100 = 6.3%    ← 内核态(系统调用)
iowait 占比 = 7    / 4746 × 100 = 0.1%    ← 等 IO(几乎没有)
nice   占比 = 24   / 4746 × 100 = 0.5%
softirq占比 = 15   / 4746 × 100 = 0.3%

进程 CPU%

Δprocess = 66306200 = 430

进程 CPU% = Δprocess / Δtotal × 100
          = 430 / 4746 × 100
          ≈ 9.1%

→ 这个进程在过去 10 秒,消耗了整机 CPU 资源的 9.1%

4.5 一张图看全貌

Δtotal = 4,746 tick(10 秒 × 4 核)
┌─────────────────────────────────────────────────┐
│                                                 │
│  ┌── user    = 800  ─┐                          │
│  ├── nice    = 24    │                          │
│  ├── system  = 300   ├─ 忙碌 = 1,146 (24.1%)   │
│  ├── iowait  = 7     │                          │
│  ├── softirq = 15    │                          │
│  ├── irq     = 0     ┘                          │
│  │                                              │
│  └── idle    = 3,600 ── 空闲 (75.9%)            │
│                                                 │
│  其中进程 com.example.app 消耗 430 tick = 9.1%  │
└─────────────────────────────────────────────────┘

五、常见采集方式对比

除了直接读 /proc/stat,还有几种常见方式,它们底层都在用同样的公式:

维度直接读 /proc/stattop -n 1dumpsys cpuinfo
底层原理就是原始数据源内部读 /proc/stat 帮你算通过 Binder 向 system_server 取
数据精度最高(原始 tick)中(top 自己算的)低(系统统计窗口不精确)
采集开销最低(2~3ms)中(50~200ms)最高(100~500ms)
需要自己算
输出格式稳定(内核标准格式)差(各版本不同)
适合高频采集不推荐
适合手动看一眼否(原始数字)

建议:写采集工具 → 直接读 /proc/stat;手动快速看 → 用 topdumpsys cpuinfo


六、采集间隔与精度

6.1 间隔选择

间隔适用场景注意事项
1~2 秒短时间精细分析(启动、页面切换)ADB 采集开销占比高,建议设备端脚本
5~10 秒长时间压测(推荐)平衡精度和开销
30~60 秒粗粒度监控可能漏掉短暂的 CPU 飙升

常用选择是 10 秒——24 小时压测产生 8640 个数据点,够做趋势分析,又不会干扰设备。

6.2 tick 精度:CONFIG_HZ

/proc/stat 里的数值单位是 tick。每个 tick 的时长取决于内核编译时的 CONFIG_HZ

CONFIG_HZ每 tick 时长常见平台
10010ms部分旧内核
2504msMTK 平台常见
3003.3ms部分高通平台
10001ms服务器/桌面 Linux

可通过 adb shell getconf CLK_TCK 查看设备的 HZ 值。

HZ=100 时,一个运行了 5ms 的短任务可能不会被记录(不到 1 tick)。对大部分性能测试场景(间隔 ≥ 5 秒),这个精度限制可以忽略。


七、完整采集流程

把前面的内容串起来,一次完整的 CPU 采集分三步:

┌───────────────────────────────────────────────────────┐
│  Step 1:第 1 次采样                                    │
│                                                       │
│  · 读 /proc/stat → 取第一行                            │
│    → 解析 8 个字段                                     │
│    → total₁ = 8 个字段之和                             │
│    → idle₁  = 第 4 个字段                              │
│                                                       │
│  · 读 /proc/{pid}/stat                                │
│    → 取第 14~17 字段                                   │
│    → process_time₁ = utime + stime + cutime + cstime  │
└───────────────────────────────────────────────────────┘
                        │
                        │  等待 N 秒(推荐 10 秒)
                        ▼
┌───────────────────────────────────────────────────────┐
│  Step 2:第 2 次采样(同样步骤)                         │
│                                                       │
│    → total₂, idle₂, process_time₂                     │
└───────────────────────────────────────────────────────┘
                        │
                        ▼
┌───────────────────────────────────────────────────────┐
│  Step 3:代入公式                                      │
│                                                       │
│  Δtotal   = total₂ − total₁                          │
│  Δidle    = idle₂ − idle₁                             │
│  Δprocess = process_time₂ − process_time₁             │
│                                                       │
│  整机 CPU% = (Δtotal − Δidle) / Δtotal × 100         │
│  进程 CPU% = Δprocess / Δtotal × 100                  │
└───────────────────────────────────────────────────────┘

小结

知识点一句话
CPU% 的本质非空闲 tick 占总 tick 的比例
核心公式整机 (Δtotal−Δidle)/Δtotal,进程 Δprocess/Δtotal
整机数据源/proc/stat 第一行(汇总行,不需要手动加各核)
进程数据源/proc/{pid}/stat 第 14~17 字段
total 怎么算汇总行 8 个数值字段全部相加
推荐采集方式直接读 /proc/stat,不用 top / dumpsys
推荐采集间隔10 秒(长时间压测)
精度上限CONFIG_HZ 决定,通常 4~10ms

掌握了这些基础,你就能看懂任何 CPU 采集工具的原理了。下一篇我们讲这个"简单公式"里藏着的 8 个坑——每个都可能让你的数据严重失真。


系列目录

  • 第 1 篇:内存泄漏自动检测(上)——采集层设计
  • 第 2 篇:内存泄漏自动检测(中)——检测层设计
  • 第 3 篇:内存泄漏自动检测(下)——响应层设计
  • 第 4 篇:Android 内存采集避坑指南
  • 第 5 篇(本篇):Android CPU 使用率采集入门
  • 第 6 篇(下一篇):CPU 采集的 8 个坑

我是测试工坊,专注 Android 系统级性能工程。 如果你也在做 CPU 相关的性能测试,欢迎评论区交流 👇 关注我,后续更新不迷路。