2026-10-13 技术更新:本文保留 2019 年起积累的旧 Systrace 截图,其中也有后补的案例。颜色与快捷键属于 legacy UI;Linux 状态说明参照 v6.6 文档,现代入口核对到 Perfetto UI v58.3(11fbaed83)。原图里的 Self Time、唤醒及调度状态注释有几处不准确,已在相邻正文说明;截图不等于本次设备实测。
本文是 Systrace 系列文章的第二篇,主要是讲解一些分析 Systrace 的预备知识, 有了这些预备知识, 分析 Systrace 才会事半功倍, 更快也更有效率地找到问题点.
本文介绍了如何查看 Systrace 中的线程状态, 如何对线程的唤醒信息进行分析, 如何解读信息区的数据, 以及介绍了常用的快捷键. 通过本篇文章的学习, 相信你可以掌握进程和线程相关的一些信息, 也知道如何查看复杂的 Systrace 中包含的关键信息
本文目录
系列文章目录
- Systrace 简介
- Systrace 基础知识 - Systrace 预备知识
- Systrace 基础知识 - Why 60 fps ?
- Systrace 基础知识 - SystemServer 解读
- Systrace 基础知识 - SurfaceFlinger 解读
- Systrace 基础知识 - Input 解读
- Systrace 基础知识 - Vsync 解读
- Systrace 基础知识 - Vsync-App :基于 Choreographer 的渲染机制详解
- Systrace 基础知识 - MainThread 和 RenderThread 解读
- Systrace 基础知识 - Binder 和锁竞争解读
- Systrace 基础知识 - Triple Buffer 解读
- Systrace 基础知识 - CPU Info 解读
- Systrace 流畅性实战 1 :了解卡顿原理
- Systrace 流畅性实战 2 :案例分析: MIUI 桌面滑动卡顿分析
- Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问
- Systrace 响应速度实战 1 :了解响应速度原理
- Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例
- Systrace 响应速度实战 3 :响应速度延伸知识
- Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇
- Systrace 线程 CPU 运行状态分析技巧 - Running 篇
- Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇
正文
用 Perfetto 阅读本文
本文讲的是分析 Trace 前需要建立的几个基础能力:线程状态、唤醒关系、信息区字段和快捷操作。旧 Systrace UI 和新 Perfetto UI 的界面不一样,但这几个能力仍然是同一套分析基础。
旧流程里,线程状态通过 Systrace HTML 里的颜色直接展示,点击 Slice 后在下方信息区看 Duration、Thread、Process、唤醒关系等字段;新流程里,Perfetto 会把这些信息拆到线程轨道、CPU 调度轨道、选择面板、搜索结果和 SQL 表中。读法要从“看一张 HTML 图”变成“先看轨道,再用搜索/SQL 收敛证据”。
对应到 Perfetto 系列,可以这样读:
| 本文知识点 | Perfetto 中的对应入口 |
|---|---|
| 打开、缩放、搜索、选中 Slice | Perfetto 3:熟悉 Perfetto View |
| Running、Runnable、Sleep 等线程状态 | Perfetto 9:CPU 信息解读 |
| 主线程等待、RenderThread 等待、Binder 等待 | Perfetto 7:MainThread 和 RenderThread / Perfetto 10:Binder 调度与锁竞争 |
线程状态查看
旧 Systrace 在已采集调度数据时,用线程状态条辅助解释 Slice。Slice 是埋点区间,不一定对应一个完整方法;颜色和状态能帮助缩小排查方向,不能单独证明 Binder、锁、I/O 或 CPU 是根因。下面的颜色对应历史截图,实际分析以 State 文本与事件为准。
如果现在用 Perfetto 看同类问题,不要只找和 Systrace 完全一样的颜色。更稳定的做法是同时看三类信息:线程轨道上的 Slice,CPU 轨道上的实际 Running 区间,以及调度状态里的 Runnable/Sleep/Uninterruptible Sleep。这样才能区分“函数自己执行慢”“线程醒了但没抢到 CPU”“线程在等 Binder/锁/IO”这几类完全不同的问题。
线程状态主要有下面几个
绿色 : 运行中(Running)
在查看器的这个区间内,线程被调度到某个逻辑 CPU 上执行;它不是“准备执行但还在排队”。Linux 的 TASK_RUNNING / ps 的 R 则同时包含正在执行与可运行等待两种情况,查看器结合调度事件把二者分开。一个线程不能同时在两个逻辑 CPU 上执行,但可以在不同区间迁移到其他 CPU。
我们经常会拿同一场景的 Running 时间作对比:为什么这边快、那边慢?频率不同吗,跑的核不同吗,还是在 Running、Runnable、Sleep 之间来回切换?这些都要结合 CPU 拓扑和实际调度去看。跑在小核或频率较低本身不等于调度错误;还需确认负载、功耗策略、温度和关键路径。图中 android.bg 在 CPU 0 上的 Running 区间为 41.743 ms,只描述该区间,不代表整个方法或全部进程都执行了这么久。

蓝色 : 可运行(Runnable)
线程具备运行条件,但当前没有在 CPU 上执行。它可能刚被唤醒,也可能刚被抢占;等待多久取决于允许使用的 CPU、优先级、调度类、CPU 配额/限流及其他竞争者。
长 Runnable 是调度延迟线索,不直接证明所有 CPU 都忙,也不能仅凭它认定频率太低或后台应用过多。先看线程允许在哪些 CPU 运行、这些 CPU 当时在做什么,以及是否存在限流、抢占或唤醒迁移。

白色 : 休眠中(Sleep)
通常对应可中断睡眠:线程在等待事件、定时器、Binder 或同步条件,既可能是正常空闲,也可能在关键路径上等待结果。“正在等”不等于“没有工作要完成”。
图中 audioserver 的 Binder:1506_2 显示 Sleeping,阻塞位置是 binder_thread_read,这是等待 Binder 相关事件的线索,不能只凭这一段认定互斥锁竞争或性能故障。

橘色 : 不可中断的睡眠态 (Uninterruptible Sleep - IO Block)
图中 State 是 Uninterruptible Sleep | WakeKill - Block I/O,阻塞位置实际写的是 wait_on_page_bit_killable,并非原文所写的 wait_on_page_locked_killable。这类信息通常由调度事件及额外的阻塞原因/I/O 等待标记组合而来,是否能看到取决于内核、权限和采集配置。
它提示页面相关等待,应继续结合缺页、回收、存储请求和阻塞栈确认原因。冷文件读取、页面锁竞争或内存压力都可能参与,不能由橙色或这一函数名直接得出“低内存”“磁盘一定繁忙”的结论。图中的 WakeKill 还说明不能把“不可中断”理解成所有信号都永远无效;Linux 的 killable 等待包含致命信号唤醒语义。

紫色 : 不可中断的睡眠态(Uninterruptible Sleep)
这张图的 State 明确显示 Uninterruptible Sleep;它不表示“通常就是内存管理”,也不只是“进入了内核态”。正在执行系统调用的线程仍可能处于 Running。不可中断等待可能涉及驱动、同步或其他内核资源,需沿实际阻塞位置查证。
图中线程是 [crtc_commit:127],附近还有显示合成相关轨道,不能将它直接归为内存不足。旧查看器还会为若干其他状态复用相近颜色,所以应读取 State 字段,不能只看紫色。

线程唤醒信息分析
Systrace 会标识出一个非常有用的信息,可以帮助我们进行线程调用等待相关的分析。
唤醒信息给出了调度事件中的一个关联点:谁让目标线程变得可运行。这个线程不一定就是锁持有者、等待结果的生产者或最早的根因;中断上下文、转交唤醒、超时或缺失事件都需要另查。唤醒之后还可能继续等待 CPU,因此要把睡眠时间、唤醒到运行的延迟与实际 Running 时间分开。
例如,App 主线程发出同步 Binder 请求后,可能因服务端执行、锁或调度而等待;这是一种待验证的路径,不能直接当作“跑完 Monkey 或后台进程多必然变慢”的解释。应关联请求/回复、对端线程和持锁者证据。
下面的历史案例中,选区覆盖 Choreographer#doFrame / traversal 的约 152.78 ms 跨度,状态条显示大量非 Running 时间。这个跨度值得排查,但不等于 CPU 执行了 152.78 ms,也不能凭单张图计算完整输入延迟或当前设备掉帧率。

放大后,选中的 Runnable 信息包含 wakeup from tid: 20424;这里的 Running instead 是另一项调度观察,不等于唤醒者或锁持有者。Perfetto 中可结合 thread_state 的 state、waker_utid、io_wait、blocked_function 与 CPU 调度数据查看;这些字段可能为空,IRQ 上下文也不能简单归因于被中断的用户线程。

原案例将 TID 20424 对应到 RenderHeartbeat。下方裁图能看到这个名称,但没有显示 TID,因此应在原始 Trace 的同一进程、同一时间段核对映射,再检查应用逻辑。一次唤醒记录不足以证明 RenderHeartbeat 就是整段长等待的根因。

从睡眠唤醒后,线程会先变为可运行,再被调度执行;但它也可能因抢占从 Running 回到 Runnable,而没有一次新的唤醒事件。短等待可能被缩放隐藏,事件丢失也会使信息不完整。下面另一个历史示例记录了 wakeup from tid: 31388,不要与上面的 20424 案例混为一条调用链。

CPU Slice 中的 stateWhenDescheduled 表示该次运行区间结束、线程被切出 CPU 时的状态。下面图里的 S 是切出时进入睡眠,不是“唤醒前状态”;原图红字在这一点上不准确:

Linux 的状态与 ps 的显示修饰符还需要分开。下面按 procps-ng ps 手册 说明;Android 的 ps 实现/选项可能不同,不要把整列 STAT 原样当作 Perfetto 状态名。
| 主状态 | 含义 |
|---|---|
| R | 正在执行或可运行等待,不能只解释为排队 |
| S | 可中断睡眠,等待条件或事件 |
| D | 不可中断睡眠,常涉及 I/O,但不是磁盘根因结论 |
| I | 空闲内核线程 |
| T / t | T 为作业控制停止;t 为调试器跟踪停止 |
| Z / X | Z 为已退出但尚未回收的僵尸;X 为死亡状态,通常不应看到 |
| W | 旧 paging 状态,Linux 2.6 起已不再有效 |
BSD 风格 STAT 的附加字符包括:< / N 表示较高/较低优先级,L 表示存在锁定内存页,s 是 session leader(不以“有子进程”定义),l 表示多线程,+ 表示前台进程组,不是后台。这里的 + 不应与调度 Trace 中 R+ 的抢占标记混为一谈。Linux v6.6 内核状态另见 proc 文档。
信息区数据解析
线程状态信息解析
图中选中的是 Weibo 主线程的一段 Running:Start 约 1358.039 ms、Duration 5.360 ms、On CPU 为 CPU 4。它描述这段调度状态,不是整个 bindApplication 的耗时。PID 与线程名用于定位对象;CPU 编号只有结合设备拓扑才知道大小核,不能仅凭“CPU 4”猜核心类型。

函数 Slice 信息解析
图中 bindApplication 的 Wall Duration 为 593.686 ms,Self Time 为 116.462 ms。Wall Duration 包含区间内的运行和等待;Self Time 是扣除嵌套子 Slice 后的时间,仍可能包含等待。原图把 Self Time 标成“不包含等待时间”是错误的,它不是 CPU Duration。没有埋点的子调用也不会凭空被扣除;Category 是事件分类,不等于完整调用栈。计算方式可对照 Catapult SliceGroup。

Counter Sample 信息解析
这里选中的是一条 counter 序列在某个时间点的样本,Value 为 3。它是该指标的数值,不是“Counter 的个数”。Counter/Series 用于辨认指标与序列;单位、是否累计、是否会下降以及采样间隔都需看数据源定义,不能把一个样本自动当成区间总量。

Async Slice 信息解析
图中 animator 的 Wall Duration 为 199.579 ms,表示这条异步事件从开始到结束的跨度;不是某一线程持续执行 199.579 ms,也不是 GPU 时间。异步区间需要按事件关联标识理解,不能把跨线程或重叠区间简单相加成 CPU 消耗。

CPU Slice 信息解析
图中 android.bg(TID 1416)在所选 CPU 上的运行区间为 37.654 ms,prio 为 130。prio 是内核调度优先级表示,不是 MHz,也不是 Java Thread 的 1—10 优先级。stateWhenDescheduled: R+ 表示切出后仍可运行,并带抢占标记;不能解释成唤醒前状态或前台进程组。Active slices 是关联的埋点区间,不保证列出所有函数。

User Expectation 信息解析
旧 Systrace 的 Interactions 区域会显示 Rendering Response、Input Response 等由查看器分析生成的区间。图中 Input Response 为 48.993 ms,这是该分析规则的时间跨度,不是人类耐受阈值,也不能直接当作触摸到屏幕发光的实测延迟。要核对其事件边界;现代帧诊断可另采集 Android 12 / API 31+ 的 FrameTimeline,但它也不自动覆盖完整输入链路。

快捷键使用
看 Trace 时经常要选中、平移、缩放,来回点按钮比较费事,熟悉几个快捷键会方便很多。下面是旧 Systrace/Catapult UI 的常用按键,字母按不带 Shift 的小写键理解。它们不能全部照搬到 Perfetto:
| 按键 | legacy UI 用途 |
|---|---|
| w / s | 放大 / 缩小时间轴 |
| a / d | 左移 / 右移时间轴 |
| m | 将当前选区设置为关注时间范围,便于对照边界 |
| 1 | Selection:点击选择事件 |
| 2 | Pan:拖动平移 |
| 3 | Zoom:拖动缩放 |
| 4 | Timing:选起止位置测量时间 |
默认鼠标模式可能随查看器设置保留,可显式按 1 进入 Selection。编辑搜索框等文本输入时,也要注意键盘焦点。
本次核查的 Perfetto UI 为 v58.3(11fbaed83):在侧栏 Support 中打开 Keyboard shortcuts,或用 ? 查看当前帮助。帮助列出的鼠标操作包括点击选事件、拖动选择区域、Shift + 拖动平移;命令面板在 macOS 上为 Cmd + Shift + P,其他平台按界面显示的修饰键操作。旧版的 1—4 鼠标模式和 m 操作不要当作跨版本合同。
当前表字段可查 PerfettoSQL Prelude:调度区间常用 sched_slice,线程状态常用 thread_state。utid 是 Trace 内部唯一线程标识,不等同于可能复用的系统 TID;时间字段通常以纳秒保存,阅读毫秒时要换算。没有对应调度/唤醒数据时,空字段保留为未知,而不是零等待或“没有唤醒者”。
本文知乎地址
由于博客留言交流不方便,点赞或者交流,可以移步本文的知乎或者掘金页面
知乎 - Systrace 基础知识 – 分析 Systrace 预备知识
掘金 - Systrace 基础知识 – 分析 Systrace 预备知识
关于我 && 博客
下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!
- 博主个人介绍 :里面有个人的微信和微信群链接。
- 本博客内容导航 :个人博客内容的一个导航。
- 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
- Android性能优化知识星球:付费社群已于 2026 年停止运营,不再招收新成员;此处保留历史介绍。
一个人可以走的更快 , 一群人可以走的更远
