资讯动态

从 Perfetto Android Trace 定位最耗 CPU 的线程:PerfettoSQL 查询方法与 Agent 评测案例实战

发布时间:2026/9/17 13:48:11 来源:尧图企业网站定制
从 Perfetto Android Trace 定位最耗 CPU 的线程PerfettoSQL 查询方法与 Agent 评测案例实战【免费下载链接】perfettoProduction-grade client-side tracing, profiling, and analysis for complex software systems.项目地址: https://gitcode.com/GitHub_Trending/pe/perfetto拿到一份 Android 手机上的 Perfetto Trace如何用trace_processor准确回答整段 trace 里哪个线程消耗的 CPU 时间最多、大约是多少、它属于哪个进程这是 Perfetto 官方为 AI 编码 Agent 设计的一组评测eval案例中最具代表性的一道题。本文以仓库中ai/evals/cases/cpu-top-thread/prompt.md这一评测用例为核心完整还原问题定义、Ground Truth 的计算方式sched_slice按utid求和、可直接复现的 PerfettoSQL 查询语句并结合sched.with_context标准库模块的源码讲清楚底层sched表的数据结构最后说明这套评测框架如何自动化判定 Agent 回答的对错。读完你既能独立完成这类 CPU 热点分析也能理解 Perfetto 官方如何用评测驱动的方式验证 Agent 技能的有效性。一、案例是什么一个标准的 Android Trace CPU 分析问题ai/evals/cases/cpu-top-thread/prompt.md是一个评测案例的完整定义文件采用YAML frontmatter 提示词正文的结构。其核心是一道面向 AI Agent 的开放式问题但它的解题思路对人工分析同样成立。1.1 frontmatter案例的元信息--- name: Top CPU thread in an Android system trace tags: [android, cpu, adhoc] runs: 3 files: - src: {repo}/test/data/example_android_trace_30s.pb dst: trace.pb ---各字段的含义如下字段值含义nameTop CPU thread in an Android system trace案例名称Android 系统 trace 中的 CPU 占用最高线程tags[android, cpu, adhoc]按 ai/evals/README.md 的约定android/cpu是领域标签问题关于什么adhoc是路线标签该问题主要靠针对核心表的裸 SQL 解答而非某个标准库模块或现成 runbook 全权接管runs3每个条件condition下重复运行 3 次。原因在 README 中有明确说明非确定性的 Agent 单次运行只是噪声必须多次取聚合files将{repo}变量展开为仓库路径把测试 trace 复制到工作区并命名为trace.pb每次评测都会在系统临时目录下创建全新工作区只复制案例声明的输入文件注意{repo}是一个会被 run_evals.py 中的expand()函数替换的模板变量。所用的输入 trace 是test/data/example_android_trace_30s.pb对应的校验文件为 test/data/example_android_trace_30s.pb.sha256另有.gz压缩版即一段 30 秒的 Android 系统级 trace通过 Linux ftrace 采集了调度信息。1.2 提示词正文一句话的开放式问题I have a Perfetto trace from an Android phone at ./trace.pb. Which thread used the most CPU time over the whole trace, and roughly how much? Give me the thread name and the process it belongs to.问题拆解成三个必须回答的子问题整段 trace约 30 秒内哪个线程消耗的 CPU 时间最多大约多少对数量级有要求但允许roughly该线程的名称以及它所属的进程名称。注意提示词刻意没有出现 Perfetto 字样以外的任何提示与no-mention类陷阱案例不同本题明确给出了 trace 类型但它属于adhoc路线——标准库的sched.with_context模块可以大幅简化查询却并非唯一解法Agent 完全可以只对核心表sched写裸 SQL 得到正确答案。二、Ground Truth评测如何定义正确回答评测的价值在于有一个可自动验证的标准答案。ai/evals/cases/cpu-top-thread/graders/目录下的四个 grader 文件共同定义了本题的判分规则其中也记录了标准答案的计算方式。2.1 标准答案来自 grader 文件注释thread-name.md的说明直接给出了 Ground Truth 的完整描述Ground truth (sched_slice summed per utid):CrRendererMainincom.android.chrome:sandboxed_process0with~2273 msof CPU time, ahead of traced_probes (~1496 ms) and .android.chrome (~1442 ms).即在example_android_trace_30s.pb中CPU 时间排名前三的线程是排名线程名所属进程CPU 时间1CrRendererMaincom.android.chrome:sandboxed_process0~2273 ms (2.27 s)2traced_probestraced_probes~1496 ms3.android.chromecom.android.chrome~1442 ms值得注意的细节这是一个渲染进程Chrome 的sandboxed_process0而非主进程说明真实世界的 CPU 热点常常不在名字最显眼的进程里——这正是该案例想考验 Agent 是否真正逐线程统计而不是凭直觉猜一个进程。2.2 判分方式四个 gradergrader 文件type判定内容cpu-time.mdregex答案中报告的 CPU 时间须落在 ~2273 ms 的 1% 容差内正则同时接受2.2x s、22xx ms、2.27等不同书写形式兼容逗号/点号小数点process-name.mdregex答案必须包含进程名sandboxed_process0thread-name.mdregex答案必须包含线程名CrRendererMainused-trace-processor.mdbashscored: false过程指标Agent 是否真的驱动了trace_processor而不是用 Python API、UI 或凭空猜测不计入分数这套设计体现了 ai/evals/README.md 中反复强调的原则Outcomes first, process second——regex 判分器只检查答案中的事实线程名、进程名、数值Agent 无论走什么路径裸 SQL、标准库模块、多条查询组合只要答对就算过是否使用了trace_processor只作为过程指标单独报告不掺入正确性分数。而 CPU 时间正则专门设计了 ~1% 的容差并兼容多种单位书写是因为roughly how much本身就允许近似不同 Agent 的舍入习惯也不一致。三、手把手复现用 trace_processor 和 PerfettoSQL 求解无论你是人类分析师还是被评测的 Agent标准解法都一样把 trace 加载进trace_processor对每个线程的调度切片sched slice的dur求和取最大值。下面是完整的可复现流程。3.1 加载 trace 并启动查询会话按 ai/skills/perfetto/infra-references/querying.md 的推荐用一次加载、多次查询的模式trace_processor server unix --name topcpu --daemonize ./trace.pb trace_processor query --remote topcpu SELECT 1 # 验证会话可用解析loading/parsing是 trace_processor 最耗时的阶段大型 trace 可能耗时数秒到数分钟因此复用会话是效率关键会话状态跨调用保持一次INCLUDE PERFETTO MODULE或CREATE PERFETTO TABLE之后后续查询都能看到空闲会话 30 分钟后会被回收用完记得trace_processor server kill topcpu若只做一次查询也可以trace_processor query ./trace.pb ...但每次都会重新解析整个 trace。3.2 方案 A使用标准库模块推荐INCLUDE PERFETTO MODULE sched.with_context; SELECT thread_name, process_name, SUM(dur) / 1e6 AS cpu_ms FROM sched_with_thread_process GROUP BY utid ORDER BY cpu_ms DESC LIMIT 10;sched_with_thread_process视图已经帮我们把线程名、进程名 join 到每一条调度切片上了查询一行都不用写 join。预期输出第一名即CrRendererMain / com.android.chrome:sandboxed_process0 / 2273左右。3.3 方案 B直接对核心表写裸 SQLadhoc 路线SELECT utid, SUM(dur) / 1e6 AS cpu_ms FROM sched GROUP BY utid ORDER BY cpu_ms DESC LIMIT 5;得到utid后再查thread、process表还原名称SELECT t.name AS thread_name, p.name AS process_name FROM thread t JOIN process p USING (upid) WHERE t.utid top_utid;utid是 trace 内的唯一线程标识跨进程时不会像 OS 的tid那样被回收复用因此是可靠的 join 键。这也是 querying.md 中 PerfettoSQL 规则之一join 用utid/upid报告给用户时用名称。3.4 验证与坑位dur以纳秒为单位除以1e6得到毫秒dur -1表示切片到 trace 结束时仍未关闭若需要边界内求和应使用IIF(dur -1, trace_end() - ts, dur)答案中的约 2273 ms来自对sched_slice即sched表按utid求和这是评测认定的 Ground Truth 口径该 trace 通过 ftrace 的sched/switch与sched/wakeup*事件采集调度信息因此sched表覆盖完整若 trace 未开启这些事件sched表为空则需改用thread_stateRunning 状态区间统计——但本案例的 trace 是完整的系统级 trace不存在此问题。四、源码级原理sched表与sched_with_thread_process视图要理解为什么按sched表求和就是CPU 时间需要看sched表的数据来源。Linux ftrace 的sched_switch事件记录的是每一次 CPU 上的线程切换谁在哪个 CPU 上从什么时候运行到什么时候。Perfetto 的 trace 处理器把这些事件还原为sched表每一行是一条运行区间Running period字段包括ts开始时间戳、dur持续时间、utid运行的线程、cpu、end_state切片结束时的内核调度状态、priority等。因此对sched.dur按线程求和得到的就是该线程在整个 trace 期间占用的真实 CPU 时间这与thread_state表中状态为Running的区间一一对应。标准库视图定义在 src/trace_processor/perfetto_sql/stdlib/sched/with_context.sqlINCLUDE PERFETTO MODULE std.thread.with_context; CREATE PERFETTO VIEW sched_with_thread_process( id ID(sched.id), ts TIMESTAMP, dur DURATION, utid JOINID(thread.id), thread_name STRING, upid JOINID(process.id), process_name STRING, cpu LONG, end_state STRING, priority LONG ) AS SELECT sched.id, sched.ts, sched.dur, utid, upid, _thread_with_process.thread_name, _thread_with_process.process_name, sched.cpu, sched.end_state, sched.priority FROM _thread_with_process JOIN sched USING (utid);从源码可以读出几个关键设计线程/进程上下文被预 join 进_thread_with_process来自std.thread.with_context模块使外层所有 join 都变成 INNER JOIN。注释解释了原因SQLite 不会把虚拟表跨 LEFT JOIN 重排若直接用thread LEFT JOIN process再 joinsched查询规划器就无法从sched.id驱动 id-keyed join维度表在前、大事实表在后的 join 顺序_thread_with_process在前、sched在后让规划器可以从任一被过滤的一侧驱动保证大规模 trace 上的性能视图的end_state字段用单个字符编码线程结束时的调度状态R 可运行、S 等待唤醒、D 不可中断睡眠、Z 僵尸等配合priority字段可以进一步分析线程为什么没在跑。这段源码既是本题 Ground Truth 的实现基础也印证了评测 README 中Ground truth from trace_processor itself的原则每个 grader 的答案都来自用trace_processor跑真实查询得到的结果而不是人工编造。五、评测如何运行把这个问题放进 Agent 评测框架本题只是 ai/evals 评测集共 11 个案例覆盖 ANR、binder、jank、内存、GPU 等主题中的一个。它的运行机制决定了这道题的结论是否可信。5.1 条件conditions与对照实验conditions.json 定义了评测的对照变量核心是有技能 vs 无技能条件内容baseline什么都不装、PATH 上什么都没有——Agent 第一次接触时的默认行为baseline-tp仅把trace_processor加入 PATH不装技能skill-published装载用户实际安装的已发布技能skill-local装载本仓库 ai/skills 的开发中版本skill-local-tp本地技能 PATH 上的 trace_processor只有同一任务、装与不装技能的对照才有意义——这正是 ai/skills/README.md 中结论的依据单有二进制在 PATH 上Opus 这类强模型几乎能答对所有问题技能的价值不在于从头教 SQL而在于提升效率一次加载多次查询、诚实性报告 trace 里的真实数字和在引导式工作流上的可靠性。5.2 运行与判定流程按 ai/evals/README.md 与 run_evals.py 的说明一次 trial 的流程是在系统临时目录创建全新工作区仅复制案例声明的输入文件如trace.pb应用条件是否装技能、PATH 里有什么、额外环境变量Agent 以非交互模式通过自身 CLI 运行完整事件流被记录转录被归一化为与 harness 无关的形态然后依次用 regex / bash / tool_used 等确定性判分器评分只有是否基于证据、是否编造这类正则查不了的标准才交给 LLM judge同时计算过程指标是否调用技能、是否跨查询保持 trace 加载、多少次 trace_processor 调用、多少 SQL 错误、成本与耗时、是否作弊找到本地构建等。隔离是这套评测的前提工作区放在仓库之外repo、主 checkout 和~/.local/share/perfetto对 Agent 隐藏Linux 用 bubblewrap 以空 tmpfs 覆盖防止 Agent 直接翻仓库找到构建好的二进制——否则对照实验就没有意义。5.3 动手运行# 准备资源构建 trace_processor_shell、准备测试 trace、装配外部资产 ai/evals/setup_assets.py --out ~/perfetto-eval-assets \ --tp-binary out/mac_release/trace_processor_shell export EVAL_ASSETS~/perfetto-eval-assets # 带技能 vs 不带技能每个案例跑 3 次5 个并行 ai/evals/run_evals.py run --conditions baseline-tp,skill-local --runs 3 --jobs 5 # 只看 CPU 相关案例、换用 Codex 或更便宜的模型 ai/evals/run_evals.py run --agent codex --tag cpu --conditions baseline-tp,skill-local ai/evals/run_evals.py run --model sonnet --budget 2 --conditions baseline-tp,skill-local # 修改判分器后重新判分保留已有 LLM 判定 ai/evals/run_evals.py grade ai/evals/results/name --skip-llm # 多目录结果横向对比 ai/evals/run_evals.py compare ai/evals/results/a ai/evals/results/b \ --conditions baseline-tp,skill-local六、从案例到方法论这类 CPU 分析的通用套路cpu-top-thread案例虽然只有一句话但它代表的是一整类CPU 热点定位问题的分析范式可直接推广到更复杂的场景先查标准库再写裸 SQL。sched.with_context这类模块把最常用的 join 都封装好了不确定有哪些可用对象时可查询__intrinsic_stdlib_objects表搜索WHERE regexp(cpu|sched, summary, i)按utid聚合而不是按tid。OS 的tid会被回收复用跨进程 join 会串数据区分CPU 时间与墙钟时间。本题问的是 CPU 时间sched.dur求和如果一个线程长时间挂起但几乎不占 CPU它不会出现在榜首报告名称而非 ID。utid/upid在一次运行内稳定但跨运行不稳定向用户报告thread.name/process.name留意容器/子进程。本题的答案藏在 Chrome 的sandboxed_process0渲染进程里——排名靠前的往往是后台渲染、媒体或 GC 线程而不是名字最响亮的进程。如果你想扩展这套评测ai/evals/README.md 的 Adding a case 一节给出了明确准则选一个真实用户会问的问题 一段能回答它的test/datatrace用trace_processor_shell算出答案并把查询写进 grader 正文每个 regex grader 只查一个事实先在 baseline 条件下跑 3 次验证案例的有效性——一个所有条件都能过的案例说明不了任何问题baseline 过不了、改动后能过的案例才有区分度。本文事实依据汇总问题定义来自 ai/evals/cases/cpu-top-thread/prompt.md标准答案与判分规则来自 ai/evals/cases/cpu-top-thread/graders/ 下的四个 grader 文件评测框架与运行命令来自 ai/evals/README.md 和 run_evals.py条件定义来自 conditions.json查询方法来自 querying.md视图实现来自 with_context.sql输入 trace 位于 test/data 目录。【免费下载链接】perfettoProduction-grade client-side tracing, profiling, and analysis for complex software systems.项目地址: https://gitcode.com/GitHub_Trending/pe/perfetto创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价