CPU 调度事件

在 Android 和 Linux 上,Perfetto 可以通过 Linux 内核 ftrace 基础架构收集调度器 trace。

这允许获取细粒度的调度事件,例如:

UI

UI 将各个调度事件表示为 Slice:

单击 CPU Slice 会在详细信息面板中显示相关信息:

向下滚动时,展开各个进程时,调度事件还为每个线程创建一个 track,允许跟踪单个线程状态的演变:

SQL

在 SQL 级别,调度数据在 sched_slice 表中公开。

select ts, dur, cpu, end_state, priority, process.name, thread.name from sched_slice left join thread using(utid) left join process using(upid)
ts dur cpu end_state priority process.name, thread.name
261187012170995 247188 2 S 130 /system/bin/logd logd.klogd
261187012418183 12812 2 D 120 /system/bin/traced_probes traced_probes0
261187012421099 220000 4 D 120 kthreadd kworker/u16:2
261187012430995 72396 2 D 120 /system/bin/traced_probes traced_probes1
261187012454537 13958 0 D 120 /system/bin/traced_probes traced_probes0
261187012460318 46354 3 S 120 /system/bin/traced_probes traced_probes2
261187012468495 10625 0 R 120 [NULL] swapper/0
261187012479120 6459 0 D 120 /system/bin/traced_probes traced_probes0
261187012485579 7760 0 R 120 [NULL] swapper/0
261187012493339 34896 0 D 120 /system/bin/traced_probes traced_probes0

TraceConfig

要收集此数据,请包括以下数据源:

# 来自内核的调度数据。 data_sources: { config { name: "linux.ftrace" ftrace_config { compact_sched: { enabled: true } ftrace_events: "sched/sched_switch" # 可选:精确的线程生命周期跟踪: ftrace_events: "sched/sched_process_exit" ftrace_events: "sched/sched_process_free" ftrace_events: "task/task_newtask" ftrace_events: "task/task_rename" } } } # 添加完整的进程名称和线程<>进程关系: data_sources: { config { name: "linux.process_stats" } }

调度唤醒和延迟分析

通过在 TraceConfig 中进一步启用以下内容,ftrace 数据源还将记录调度唤醒事件:

ftrace_events: "sched/sched_wakeup_new" ftrace_events: "sched/sched_waking"

虽然 sched_switch 事件仅在线程处于 R(unnable) 状态并且在 CPU 运行队列上运行时发出,但在任何事件导致线程状态更改时都会发出 sched_waking 事件。

考虑以下示例:

线程 A condition_variable.wait() 线程 B condition_variable.notify()

当线程 A 在 wait() 上挂起时,它将进入状态 S(sleeping) 并从 CPU 运行队列中移除。当线程 B 通知变量时,内核将线程 A 转换为 R(unnable) 状态。此时线程 A 有资格放回运行队列。但是,这可能不会立即发生,因为,例如:

除非使用实时线程优先级,否则大多数 Linux 内核调度器配置都不是严格的工作守恒的。例如,调度器可能更喜欢等待一段时间,希望当前 CPU 上运行的线程进入空闲,避免跨 CPU 迁移,这在开销和功耗方面可能更昂贵。

NOTE: sched_wakingsched_wakeup 提供几乎相同的信息。区别在于跨 CPU 的唤醒事件,这涉及处理器间中断。前者始终在源(唤醒)CPU 上发出,后者可能在源或目标(被唤醒)CPU 上执行,具体取决于几个因素。sched_waking 通常足以进行延迟分析,除非你正在细分由于调度器唤醒路径导致的延迟,例如处理器间信令。

当启用 sched_waking 事件时,在选择 CPU Slice 时,UI 中将显示以下内容:

解码 end_state

sched_slice 表包含有关系统调度活动的信息:

> select * from sched_slice limit 1 id type ts dur cpu utid end_state priority 0 sched_slice 70730062200 125364 0 1 S 130

表的每一行显示给定线程(utid)何时开始运行(ts)、在哪个核心上运行(cpu)、运行了多长时间(dur)以及它为什么停止运行:end_state

end_state 编码为一个或多个 ASCII 字符。UI 使用以下转换将 end_state 转换为人类可读的文本:

end_state Translation
R Runnable
R+ Runnable (Preempted)
S Sleeping
D Uninterruptible Sleep
T Stopped
t Traced
X Exit (Dead)
Z Exit (Zombie)
x Task Dead
I Idle
K Wake Kill
W Waking
P Parked
N No Load

并非所有字符组合都是有意义的。

如果我们不知道调度何时结束(例如,因为在线程仍在运行时 trace 结束),end_state 将为 NULL,并且 dur 将为 -1。