利用 Agent 实现冻屏问题的自动化定位
本原创文章帖发布在华为开发者联盟社区,欢迎开发者前往访问评论交流,更多与该内容相关讨论,请点击原帖查看:
点了一下按钮,页面不动了。等打开 Profiler 准备录制,它又恢复了。再点一次,一切正常。
排查偶发卡死时,最容易错过的就是这几秒。写代码、操作应用时,如果有监控任务持续监听卡死信号,触发后自动采集日志,就可以事后再查看主线程到底忙在哪儿。
appfreeze-dev-analysis 提供了这样的功能:在 CodeAgent 中启动一个持续观察的子任务,检测到卡死信号后抓取 trace 和 perf,接着调用分析技能,结合工程代码返回报告。该任务在后台执行,不会阻塞主 Agent 的正常开发测试任务。

图 1|监听就绪后再操作应用。采集结束会进入分析,观察任务继续等待下一次故障。
如何使用该 Skill
从官方技能仓安装完整的 developtools_dfx_skills,按仓库说明将技能加载到 CodeAgent,并配置分析依赖。安装后新建会话,让 Agent 检查依赖。
连接并解锁手机,运行待测的调试应用,在 CodeAgent 中输入:
持续监控当前 HarmonyOS 工程的应用是否发生卡死问题,卡死时抓日志并结合代码分析。观察 30 分钟。监听 ready 后告诉我,我来操作应用;每次采集后主动返回分析报告,继续监听,直到我要求停止。
也可以简短地说:“帮我盯着当前应用,卡死时抓日志分析。”多台设备连接时,补上目标设备;工具或工程不在默认位置时,再提供路径。
首次配置时,下面这些条件需要一起满足。日常使用不必每次手工重配:
| 准备项 | 要确认的内容 |
| CodeAgent | 技能已加载,支持持续运行的并行子任务 |
| 采集工具 | Python 3.9+、SDK 的 hdc、原生 trace_streamer |
| 分析依赖 | appfreeze-analysis、perf-analysis-skill 及其后端可用 |
| 手机与应用 | 设备已连接、解锁;应用运行中且可调试;hiprofiler_cmd 双插件可用 |
| 代码材料 | 对应源码,以及需要反解位置时使用的同构建 SourceMap 和符号 |
耗时分析报告
监控任务在检测到触发事件后,主动采集约 10 秒的 trace 和 perf,并返回耗时分析报告。收到 Agent 提示后,先看 timeout-analysis.md,再打开 capture.htrace。报告会包含采集时间范围、主线程状态,以及建议检查的代码位置。
下面是一份报告摘要。它来自 GLM-5 对历史 CPU 繁忙事件的离线分析,数据和表述经过人工核对,用来展示报告能提供的线索。
点击后主线程持续计算
诊断: 点击处理链同步执行 runBusyCpu,主线程持续计算,无法及时处理后续消息。
| 记录 | 分析依据 |
| trace 窗口约 9.688 s | 主线程 Running 合计 7710.368 ms |
| 主线程 perf 共 7582 个样本 | 业务调用链包含 runBusyCpu |
| hilog | AppMainThread not alive;MMITask 持续 15005 ms;业务标记 cpu-busy |
| 源码 | menuAction 同步调用 runBusyCpu,后者循环计算直到指定时间 |
采样给出的调用链是:
anonymous Index.ts:545:33
→ menuAction Index.ts:306:34
→ runBusyCpu Index.ts:327:21
本地源码中,Index.ets 第 135 行调用 runBusyCpu,第 157—159 行是计算循环:
const summary = this.runBusyCpu();
while (Date.now() < end) {
acc += Math.sqrt(i * 1.000001)
+ Math.sin(i % 360) * Math.cos(i % 180);
i++;
// 其余循环控制省略
}
修改建议: 移除或限制这个用于制造故障的测试循环。真实业务遇到同类问题,先减少重复计算,再考虑任务池或 Worker,完成后回主线程更新 UI。给函数加 async,不会自动把同步计算移到其他线程。
复测方法: 用相同输入核对输出,主动录制修改后的操作,检查耗时和交互。本事件尚无修复后的测量数据。
报告也会如实说明日志未能获取的信息:这份记录只覆盖约 9.7 秒,缺少完整系统资源信息和故障快照栈;perf 样本数不能换算成某行代码的毫秒数,15005 ms 的任务时长也不能这样分摊。历史安装包与 SourceMap 的构建身份未核验,源码行号应作为候选位置使用。
拿到这样的报告,就有了一条可以动手核对的业务调用链。下一步打开采样记录,看看它是否支持这个判断。
在采样里找到报告说的函数
SmartPerf Host 可以本地查看合并 htrace。本次截图使用 6.0.0.36 发布包,一次导入就能看到调度轨道和 HiPerf 采样。
选择 Open trace file,打开记录,展开 HiPerf → 目标进程 → 主线程,框选区间后打开 Bottom Up。点击 runBusyCpu,右侧 Call Stack 可以继续看到 menuAction 和 anonymous。

图 2|同一份合并记录中的调度轨道和 perf 热点。先选中目标主线程,再查看调用栈。

图 3|右侧展开 runBusyCpu、menuAction、anonymous。所选约 7.089 s 区间有 7119 个主线程样本;该 runBusyCpu 采样位置占 1881 个、26.4%。
这里的 26.4% 属于选中的采样位置,不代表整个函数的耗时占比,也不能与报告全窗口的统计直接对比。SmartPerf 显示的 TS 位置还要通过同构建 SourceMap 反解为 ETS 位置。
如果习惯在 IDE 里查代码,也可以使用 DevEco Studio Profiler。下面是同一历史事件单独导入 perf 的操作截图:展开 Callstack,框选主线程采样,打开 Bottom Up,再双击 Index.ts 的业务帧。这组截图展示查栈和跳转,未验证 IDE 同时显示合并文件的两类轨道。

图 4|Bottom Up 中可以找到业务帧与源文件,完整函数名还需结合报告和符号解析。

图 5|双击后跳到 Index.ets:135:28,即 this.runBusyCpu();下方可见计算循环。
这样就能把“主线程很忙”落到具体的调用和循环上。换成自己的工程时,仍要确认源码、SourceMap 和安装包属于同一构建;能跳转到某一行,只说明找到了可查看的位置。
注意事项
开发态通常使用 aa attach,避免进程过早被系统结束。若 IDE 已持有调试会话,观察任务需要识别并复用,结束时不能解除其他任务的调试状态。
检测命中后,hiprofiler_cmd 在同一会话中使用 ftrace-plugin 和 hiperf-plugin 录制约 10 秒。perf 参数为 1000 Hz、hw-cpu-cycles、fp,保留 offcpu 和 cpu-limit 25,不另起 hiperf。
这意味着,采集从检测信号之后开始,无法追回此前的 trace。分析上一份报告时可以继续等待下一轮采集,但录制或封存期间的新信号不会排队补录。需要看完整操作时,应协调停止监听,提前开始一次主动录制,避免两个 trace 任务争用设备。
遇到锁等待等阻塞,主线程可能没有 CPU 样本。只有确认采集有效、同一进程仍卡死且确实没有主线程样本,才在停止采集、确认调试归属后考虑 LLDB 补栈。如果已有样本只是缺业务符号,先补 SourceMap 或符号。
开发结束时,记得告诉 Agent:
停止观察,分析完已经采集的事件并汇总,确认采集退出且清理完成。
事件处理完后再看 session-report.md。completed 表示适用分析流程和证据检查完成,partial 表示仍有缺口;根因和修复建议都需要结合证据判断。如果只拿到文件,没有报告,就让 Agent 检查观察子任务、导出状态和分析队列。采集脚本还活着,不代表 Agent 仍在处理事件。
第一次试用,可以先选一个已知的卡死操作:等 ready,再复现,确认能主动收到报告,并沿报告里的调用链找到代码。这个过程跑通后,再把它留在后台,陪你做日常调试。
更多推荐




所有评论(0)