Android | 十分钟写一个ebpf 可视化观测工具

866 阅读4分钟

故事的背景:
我们在分析性能问题时,常常需要排查关键线程(包括 CFS 和 RT)为何会频繁跑到小核,导致running 耗时长。

以往的传统分析流程如下:

  1. 抓取 Systrace + Ftrace,确保启用 sched 等相关节点;
  2. 在 Systrace 中定位线程落小核的片段,再到 Ftrace 中逐一查找对应原因;
  3. 对照源码理解每个选核 reason 的含义并进行归纳。

这一套流程下来通常耗时半小时左右,若由业务方协助抓取,来回沟通加上指导操作,耗时会更长。

在上周的一个下午,打算分析一个RT 线程不时落小核的问题时,突然想到:

  • 首先这是一个高频且重复的分析场景;
  • 因为分析步骤固定,本质是个体力活;
  • 选核原因虽多,但在特定版本中是固定的,为何不能可视化展现呢(比如以饼状图展现)。

在ChatGPT 加持下,花了10 分钟调通了这个观测程序的初版,后续又花了点时间完善信息(比如加了这个线程是否有绑核,绑核范围等信息)。

成果如下图所示:

20250524-165153.png

注:图中这个线程cpuset 是后台,所以cpu 允许范围是0~3,图中的reason 只针对mtk 平台kernel 6.1

操作步骤
执行该Python 脚本,指定要抓取的线程号,按ctrl+C 停止后,会自动打开网页呈现出这段时间内,该线程的选核原因分布。

有一点可能稍微注意下,比如我这里追踪的tp是在单独的ko 中,引用vmlinux.h 后,实际是找不到这个函数的,需要cat 下这个tp 的format,将其定义在内核态的程序中。

举个例子:

TRACE_EVENT(sched_select_task_rq,

	TP_PROTO(struct task_struct *tsk, bool in_irq,
		int select_reason, int backup_reason, int prev_cpu, int target_cpu,
		int task_util, int task_util_est, int boost, bool prefer,
		int sync_flag, struct cpumask *effective_softmask),

	TP_ARGS(tsk, in_irq, select_reason, backup_reason, prev_cpu, target_cpu, task_util, task_util_est,
		boost, prefer, sync_flag, effective_softmask),

	TP_STRUCT__entry(
		__field(pid_t, pid)
		__field(int, compat_thread)
		__field(bool, in_irq)
		__field(int, select_reason)
		__field(int, backup_reason)
		__field(int, prev_cpu)
		__field(int, target_cpu)
		__field(int, task_util)
		__field(int, task_util_est)
		__field(int, boost)
		__field(long, task_mask)
		__field(long, effective_softmask)
		__field(bool, prefer)
		__field(int, sync_flag)
		__field(int, cpuctl_grp_id)
		__field(int, cpuset_grp_id)
		),

将TP_STRUCT__entry 内的参数整合到内核态程序中即可。

20250526144236.png

上层读取程序的实现很简单

20250526144532.png

接下来,获取到数据之后,就借助python 进行可视化到网页中,这个脚本完全借助AI 实现,比较就不展开了

借助工具,原本至少半小时的分析归纳时间,可以缩短为一分钟即可自动呈现,同时还可以给测试或者业务方使用。

顺着这个思路,很自然会想到:
这类自动化观测和分析手段,其实可以扩展到更多场景。举两个例子:

  1. 内存观测
    统计一段时间的内存分配延迟、识别后台频繁申请内存的线程、关键线程上内存分配失败情况等。
  2. 调度延迟分析
    以往我们调整调度策略后,缺乏精细的量化工具,现在可以借助 eBPF 测量。

这种机制不仅适用于内核空间,用户空间同样适用。

Google 在 V 版本中进一步增强了 Uprobestats,这一设计值得借鉴。

我们可以构建一个通用方案:

  1. eBPF 模板化
    eBPF 上手门槛略高,但代码高度重复,通过模板封装可大大降低使用成本。
  2. 配置驱动
    任何需要追踪的函数,只需提供配置项,反序列化后即可动态注入追踪逻辑,常用项甚至可预置到设备中。
  3. 数据可视化
    这至关重要,若无清晰展示,再有价值的数据也难以快速定位。可考虑输出到 Perfetto(可参考 AOSP 中 StatD 的实现),或像笔者这样以网页形式展示。

接下来计划再搞点内存和调度方面的 eBPF 观测工具,后续计划开源至 GitHub。

最后想到一点:
现在很多手机厂商,还有一些车商,都在研究通过ebpf 捕获终端用户数据后利用AI 大模型进行训练,不断精细化用户的画像,这是一个不错的思路。