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

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


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

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

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

一句话定义:

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


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

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

2.1 整机 CPU 使用率

ini 复制代码
第 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 使用率

ini 复制代码
第 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 tick utime + stime + cutime + cstime
Δtotal 整机总 tick 增量 和整机公式用同一个分母

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

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

2.3 公式小结

erlang 复制代码
整机 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 个数值依次是:

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

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

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

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

yaml 复制代码
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 字段验证:

ini 复制代码
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 文件,输出是一行很长的数据:

yaml 复制代码
12345 (com.example.app) S 1 12345 12345 0 -1 ... 5000 1200 0 0 ...

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

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

四、完整计算实例

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

4.1 第 1 次采样

/proc/stat 第一行:

复制代码
cpu  10132153 290696 3084719 46828483 16683 0 25195 0 0 0

解析:

ini 复制代码
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

ini 复制代码
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

解析:

ini 复制代码
total₂ = 10132953 + 290720 + 3085019 + 46832083 + 16690 + 0 + 25210 + 0
       = 60,382,675

idle₂  = 46,832,083

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

ini 复制代码
utime=5350  stime=1280  cutime=0  cstime=0

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

4.3 算差值

字段 第 1 次 第 2 次 差值(Δ)
user 10,132,153 10,132,953 800
nice 290,696 290,720 24
system 3,084,719 3,085,019 300
idle 46,828,483 46,832,083 3,600
iowait 16,683 16,690 7
irq 0 0 0
softirq 25,195 25,210 15
steal 0 0 0
total 60,377,929 60,382,675 4,746

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

4.4 代入公式

整机 CPU%

ini 复制代码
Δ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% 在空闲

细分一下各状态占比:

ini 复制代码
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%

ini 复制代码
Δprocess = 6630 − 6200 = 430

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

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

4.5 一张图看全貌

ini 复制代码
Δ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/stat top -n 1 dumpsys 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 时长 常见平台
100 10ms 部分旧内核
250 4ms MTK 平台常见
300 3.3ms 部分高通平台
1000 1ms 服务器/桌面 Linux

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

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


七、完整采集流程

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

ini 复制代码
┌───────────────────────────────────────────────────────┐
│  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 相关的性能测试,欢迎评论区交流 👇 关注我,后续更新不迷路。

相关推荐
Lstone736424 分钟前
Bitmap深入分析(一)
android
一起搞IT吧1 小时前
Android功耗系列专题理论之十四:Sensor功耗问题分析方法
android·c++·智能手机·性能优化
ByNotD0g2 小时前
Doris 学习笔记
android·笔记·学习
修炼者2 小时前
【Android进阶】 RenderEffect的底层实现
android
bropro2 小时前
MySQL不使用子查询的原因
android·数据库·mysql
执笔论英雄3 小时前
【cuda】 pinpaged
android·java·数据库
新青年.3 小时前
Android(Compose)使用 LibVLC 播放 RTSP 视频流
android
一见4 小时前
WorkBuddy安装Skill的方法
android·java·javascript
毛骗导演4 小时前
万字解析 OpenClaw 源码架构-跨平台应用之Android 应用
android·前端·架构