Android性能测试自动化:Perfetto+AI诊断方案实践
2026/9/9 2:39:04 网站建设 项目流程

最近我把 Perfetto 和 AI 放在一起,在 Android 性能测试上搭了一套自动化诊断方案。过去要花半天才能看完的 trace 文件,现在通过自动化采集、SQL 特征提取和 AI 归因,半小时内能出一份相对靠谱的问题报告。这套方案解决的核心问题,就是性能测试中“采集靠人工、分析靠经验、报告靠手写”的效率瓶颈。如果你也在做 Android 应用或者系统级性能测试,或者正被启动慢、掉帧、周期性卡顿这类问题反复折磨,这篇内容应该能给你一条能照着搭的路线。

一个很现实的情况是,很多团队并不是没有性能数据,而是数据拿到之后不知道怎么高效使用。尤其是 Perfetto 抓出来的 trace 动辄几百 MB,打开界面要加载半天,火焰图点开一层又一层,没有足够经验的人根本看不出门道。我搭这套自动化诊断方案,本质上就是把“trace 采集、特征提取、归因建议”这条链路固化下来,让 AI 先做一轮初筛,人工再去验证和深挖。下面我把整个方案的选型思路、落地细节和踩坑过程都摊开讲。

1. 为什么做这套方案:性能测试的日常,其实比想象中更磨人

1.1 手动抓 Trace 的典型一天:重复劳动占了大头

先还原一个性能测试最典型的场景。测试人员拿到一台待测设备,安装好版本,连上 adb,然后开始手动执行一系列操作:打开应用、滑动列表、进入二级页面,再退出。为了抓一帧掉帧问题,往往要提前打开 systrace 或者 Perfetto,录个 10 到 20 秒,操作完再保存 trace。问题偶现的时候更痛苦,一个场景要反复跑好几轮,每个 trace 都要重新分析。这样一套流程下来,真正用于思考“为什么卡”的时间可能不到三分之一,大量时间都耗在采集、保存、打开、拖动时间轴这些事情上。

如果只是偶尔做一两次性能专项,手动流程还能接受。但性能问题一旦进入迭代回归阶段,比如每次发版前都要把所有核心场景跑一遍,手动方案的短板就非常明显。团队里真正能熟练看火焰图、能分清主线程忙碌和 Binder 等待的人本来就不多,让他们每天对着几十个 trace 做重复劳动,既浪费人力,也很难保证分析标准的一致。不同人看同一份 trace,给出的结论甚至可能完全不同。

我最早想做的自动化,其实就是把采集这一步先固定下来:测试脚本启动,Perfetto 就开始录;测试脚本结束,trace 自动保存并拉回服务端。这个事听起来简单,但真正落地时牵扯到 TraceConfig 怎么写、什么时候采集、怎么保证文件不丢失、怎么跟用例执行时间对齐,细节非常多。等采集的问题解决完,我又发现新的瓶颈转移到了“分析”这步——trace 还是得有人打开看。

1.2 为什么选 Perfetto:它不只是新一代 systrace

我用过挺长一段时间的 systrace,后来切到 Perfetto 之后,最明显的感受是它不只是一个升级版工具,而是把性能数据的处理方式彻底换了一套。systrace 输出的是传统 HTML 报告,能看,但很难程序化处理。Perfetto 的核心理念是“数据源插件化 + 统一 trace 格式 + SQL 化分析”,其中 SQL 化分析这一点对我来说是关键中的关键。

Perfetto 采集到的.perfetto-trace文件经过 trace_processor 解析后,会变成一个 SQLite 数据库。进程表、线程表、slice 表、调度表、计数器表,全都结构化管理。也就是说,我可以不用再靠肉眼去界面里找某一段调用栈,而是直接用 SQL 查询:主线程最长耗时函数 top 20 是什么、某一帧为什么超过了 100 毫秒、哪个进程在持续抢占 CPU。这种能力天然适合自动化,因为查询逻辑一旦写死,每次拿到新 trace 都能用同一套规则去提取指标。

这也是我选择 Perfetto 而不是自己开发埋点方案的原因。自定义埋点当然能拿到业务层面的精准信息,但需要业务方配合打点,而且很难拿到内核调度、CPU 频率这些底层数据。Perfetto 覆盖了从内核 ftrace 事件到用户态 slice 的完整链路,拿到的是系统级视角,很多问题不用一开始就猜是不是业务代码的问题,而是先看系统资源分配和调度,再定位到具体进程或方法。作为自动化诊断的数据底座,Perfetto 的完整度和可编程性目前没有更好的替代方案。

2. 方案整体设计与关键选型

2.1 三层结构:采集层、分析层、诊断报告层

整个方案我拆成三层:采集层、分析层、诊断报告层。采集层负责在设备上跑 Perfetto,产出原始 trace;分析层负责用 trace_processor 把 trace 转成可计算的 SQL 表,再用一组预置查询提取关键指标;诊断报告层则把分析层输出的结构化数据喂给 AI,由 AI 给出归因判断和优化建议,最后生成一份可读的报告。这样分层的好处是每一层都能独立替换和演进。

比如说,采集层今天用 Perfetto,明天如果想把 trace 的采集能力扩展到云真机集群,只需要改这一层的调度逻辑;分析层如果发现某些 SQL 指标不够准确,可以直接调整查询语句,不影响前后两层;诊断报告层如果换一个更强的模型,或者要接入本地私有化部署的模型,也只涉及这一层的接口调用。层与层之间通过标准的数据格式衔接,这个边界必须拉清楚,否则后面迭代会很痛苦,谁都不敢动代码。

我在最初的版本里其实把分析层和诊断报告层混在一起写过,AI 那边既负责提指标又负责给结论,结果模型的输出很不稳定。后来把“指标提取”这个动作完全交给确定性的 SQL 规则,AI 只做归因和解释,稳定性才上来。这个经验我觉得值得提前说:AI 适合做综合判断,但不适合做精确计算。

2.2 为什么把 AI 放在“归因”环节,而不是让它直接看图

AI 在这个方案里到底承担什么角色,我反复调整过几次。最开始我试过直接把.perfetto-trace文件的内容塞给大模型,让模型告诉我问题在哪,效果非常糟糕。原因有两个层面:一是 token 限制,一份 trace 解压成文本后可能有几百万行,模型根本吃不进去;二是 trace 里的原始事件非常琐碎,绝大多数内容对定位某一个具体问题来说是噪声,如果缺少特征提取这个步骤,模型很容易被无关信息带偏。

所以我把 AI 放在归因和解释这一环。也就是说,先通过一组可解释的指标,把“现象”明确下来:主线程出现了一个 800 毫秒的耗时切片,期间进程的 CPU 占用不高,有一个很长的 Binder 等待。这些指标是我手工总结出的模式,逻辑上是清楚的。AI 拿到这些指标组合后,去判断它更符合哪种典型问题,比如是主线程被 I/O 阻塞,还是在等待远端进程响应,还是因为内存抖动导致频繁 GC。这种用法把 AI 的长处放在“综合上下文做判断”上,而不是让它去从原始数据里捞针。

这样做还有一个附带好处:AI 的输出是可以被人工复核的。因为输入的是结构化指标,模型给出的根因能够对应到具体的 evidence。如果发现模型结论不对,我可以回头检查是指标提取错了,还是 prompt 引导有问题,而不是面对一个黑盒输出无从下手。

2.3 关于数据流的一点思考:从 Trace 到指标再到结论

整个诊断过程可以看成一条数据流水线:原始 trace、关键指标、诊断结论,每一层都在做降维和抽象。原始 trace 信息最全但噪声最大;关键指标保留的是与问题强相关的信息片段;诊断结论则是基于这些片段给出的可能性判断。每一层都应该保留向下追溯的能力,否则 AI 说“主线程有长时间阻塞”,但报告里看不到对应的数据和原始轨迹,这份结论的可信度就大打折扣。

我最终的实现里,每份诊断报告都会附带三个东西:执行场景名、AI 判断的根因、对应的指标证据列表。指标证据里会带上这条数据是从哪张 SQL 表查出来的,原始值是多少。这样如果测试同学对结论有疑问,可以顺着证据列表回到 trace 里做人工二次确认。我自己的体会是,这种“可追溯”的设计,比模型本身的准确率还重要。因为自动化诊断最大的风险不是模型偶尔说错,而是错误结论被直接拿去拦截版本,造成误杀。

3. 采集层落地:在自动化流程里稳定拿到高质量 Trace

3.1 ADB 启动 Perfetto:先写一份能跑的 TraceConfig

Perfetto 的采集靠 TraceConfig 驱动,它是一个 protobuf text 格式的配置文件,描述要采集哪些数据源、 buffer 开多大、采多久。很多第一次用的人容易直接在命令行里敲perfetto -t 10s -o /data/misc/perfetto-traces/trace.perfetto-trace,这样确实能抓到东西,但抓到的事件类型很有限,无法支撑后续的分析。我建议从一开始就维护一份自己的 TraceConfig,按需开启数据源。

下面这份是我在自动化测试场景里常用的配置,核心思路是:开启 ftrace 调度事件、进程内存统计、CPU 频率变化,以及 Power 相关的事件。其中sched_switch用于看线程切换和阻塞原因,sched_wakeup用于看唤醒源,cpu_frequency用于判断是否降频。buffer 大小这里给了两个,一个给主 buffer,一个给内存统计用的辅 buffer。

buffers { size_kb: 262144 fill_policy: RING_BUFFER } buffers { size_kb: 40960 fill_policy: RING_BUFFER } data_sources { config { name: "linux.ftrace" ftrace_config { ftrace_events: "sched/sched_switch" ftrace_events: "sched/sched_wakeup" ftrace_events: "power/cpu_frequency" ftrace_events: "power/cpu_idle" ftrace_events: "sched/sched_process_exit" } } } data_sources { config { name: "android.process_stats" } } data_sources { config { name: "android.incremental_state" } } duration_ms: 30000 write_into_file: true

启动命令我习惯用 stdin 传配置的方式,这样不需要先 push 文件到设备上:

adb shell perfetto -c - -o - --txt <<'EOF' > trace.perfetto-trace buffers { size_kb: 262144 fill_policy: RING_BUFFER } data_sources { config { name: "linux.ftrace" ftrace_config { ftrace_events: "sched/sched_switch" ftrace_events: "sched/sched_wakeup" ftrace_events: "power/cpu_frequency" } } } duration_ms: 30000 write_into_file: true EOF

-c -表示从标准输入读配置,-o -表示把 trace 输出到标准输出,然后在 PC 端重定向成文件。这种方式在 CI 环境里比较友好,不用在设备上额外创建配置文件。需要注意,Perfetto 的版本和 Android 系统版本相关,不同版本支持的 TraceConfig 字段有差异,配置写好之后先在小范围试跑一次,看有没有报错或者字段不识别的问题。

3.2 配合自动化用例:什么时候开始采,什么时候停

自动化采集最容易犯的错,是直接用固定duration_ms来控制采集时长。如果用例执行了 40 秒,而配置里只写了 30 秒,那么最后 10 秒的问题数据就完全没录到;反过来,如果用例只跑了 8 秒,后面 22 秒录到的都是空数据,会白白增加文件体积。所以我后来改成由测试框架主动控制 Perfetto 的启停:用例开始前 5 秒启动采集,用例结束并留出必要的余量后再停止。

一个典型的时序是这样的:先执行adb shell perfetto -c - -o /data/misc/perfetto-traces/trace.perfetto-trace --txt并让它后台运行,等待 2 到 3 秒确认采集真的启动了,然后开始跑 UI 用例,用例结束之后等 1 秒让残留事件写完,再执行adb shell killall perfetto触发正常终止,最后用adb pull把 trace 拉到本地。整个流程可以用一个脚本包起来,避免手动敲命令的环节。

adb shell "perfetto -c /data/local/tmp/trace_config.pbtxt -o /data/misc/perfetto-traces/trace.perfetto-trace --txt &" sleep 3 # 这里执行你的 UI 自动化测试脚本 python3 run_ui_test.py # 测试结束后停止采集并拉回文件 adb shell "killall perfetto" adb pull /data/misc/perfetto-traces/trace.perfetto-trace ./traces/scene_001.perfetto-trace

这里有个小坑:如果直接通过adb shell启动后台进程,adb 断开时进程可能被一并杀掉。我试过几种方式,比较稳的是在设备端加nohup或者通过setsid启动,或者用adb shell "perfetto ... &"后不要让 adb 立即退出。再有就是测试机如果开了多个监控工具,比如同时跑着 Perfetto、内存 profiler、GPU 监控,很容易互相抢占资源,导致 trace 里出现假阳性。我的建议是性能测试机上不要同时开多套采集工具,一批用例跑一类监控。

3.3 用 trace_processor 把 .perfetto-trace 变成 SQL 表

拿到 trace 文件之后,如果只会用官方界面看,自动化还是没有闭环。Perfetto 提供了 trace_processor,可以在本地执行trace_processor_shell trace.perfetto-trace进入一个 SQLite 交互环境,但我更常用的是 Python API,直接在脚本里执行查询并拿结果做进一步处理。安装方式很简单,pip install perfetto即可,然后用 TraceProcessor 加载 trace 文件。

from perfetto.trace_processor import TraceProcessor tp = TraceProcessor(file_path='trace.perfetto-trace') res = tp.query(''' SELECT t.name AS thread_name, s.name AS slice_name, CAST(s.dur AS REAL) / 1e6 AS dur_ms FROM slice s LEFT JOIN thread t USING (utid) WHERE s.dur > 50_000_000 ORDER BY s.dur DESC LIMIT 20; ''') for row in res: print(row)

这段 SQL 的意思是,在所有 slice 里找出时长大于 50 毫秒的切片,并关联线程名按耗时排序。slice 表里存的是用户态的事件切片,可以理解为函数或方法的执行区间。需要注意 ts 和 dur 的默认单位都是纳秒,所以示例里把 dur 除以 1e6 转成了毫秒,方便阅读。另一个常用表是processthread,分别存进程和线程的元信息;sched表则记录线程在 CPU 上的调度区间,用它可以分析线程是否有过长时间等待。

我刚接触这套 SQL 表结构时最不适应的一点,是表之间靠utidupidtsdur这些 ID 和时间列关联,不像业务数据库里直接用主键和业务名称关联。但只要把slicethreadprocess这三张表的关系记住,大部分查询都能写出来。分析层做指标提取,本质就是写一堆这种带过滤条件的 SQL,然后定时回归校验这些查询得到的数值是否符合人工观察。

4. 分析层与 AI 诊断:让模型看懂 Trace 数据

4.1 为什么不能直接把原文件丢给大模型

把整份 trace 直接丢给大模型这个念头,我相信很多人在一开始都会动。毕竟现在大模型上下文窗口越来越大,好像什么都能接。但实际试过就知道不现实,一份 30 秒的 trace 展开成文本能达到几十万甚至上百万行,而且里面的原始事件绝大多数是调度切换、频率更新这类底层日志,模型如果要从中自己找问题,等于让它在草垛里找针,还要保证不找错。就算模型说出了一个看起来合理的结论,你也很难判断它依据的是哪一段数据,因为输入数据本身已经超出了人类的核查能力。

所以我坚持先做特征提取,让模型只看到一小部分高价值的信息。比如我不关心过去 30 秒里每毫秒的线程状态,我只关心“主线程是否有一个超过 200 毫秒的 slice”“这个 slice 执行期间,该县城是否长时间处于 Running 状态”“当时 Binder 线程是否有堆积”。这些特征由 SQL 查询直接产出,数值准确、逻辑清晰,模型拿到这些压缩后的信息,才能给出有依据的分析。

4.2 构建诊断摘要:关键指标与事件文本的生产方法

诊断摘要我一般分成几个模块:环境信息、场景信息、核心性能指标、异常事件列表。环境信息包括设备型号、系统版本、应用版本;场景信息就是当前测试在做什么,比如“冷启动进入首页并滑动列表 30 秒”;核心性能指标包括主线程慢函数 top N、Jank 次数、慢 Binder 调用 top N、进程内存占用等;异常事件列表则是那些超过阈值、值得重点关注的切片和调度片段。

下面这段代码展示了如何把 SQL 查询结果拼成一段适合 AI 阅读的文本。关键点在于,每个指标块前面都加上一个明确的标签,比如[SLOW_FUNCTIONS][JANK_INFO][BINDER_WAIT]。这样模型能快速定位不同信息的含义,输出结构也更容易保持稳定。

def build_ai_summary(tp, fname, scene): lines = [] lines.append(f"设备: {device_model}") lines.append(f"系统: {android_version}") lines.append(f"测试场景: {scene}") lines.append(f"trace 文件: {fname}") slowness = tp.query(''' SELECT t.name AS thread_name, s.name AS slice_name, CAST(s.dur AS REAL) / 1e6 AS dur_ms FROM slice s LEFT JOIN thread t USING (utid) WHERE s.dur > 100_000_000 ORDER BY s.dur DESC LIMIT 10; ''') lines.append("[SLOW_FUNCTIONS]") for row in slowness: lines.append(f"- {row.thread_name}: {row.slice_name} {row.dur_ms:.1f}ms") binder = tp.query(''' SELECT p.name AS process_name, s.name AS slice_name, CAST(s.dur AS REAL) / 1e6 AS dur_ms FROM slice s JOIN thread t USING (utid) JOIN process p USING (upid) WHERE s.name LIKE '%binder%' ORDER BY s.dur DESC LIMIT 10; ''') lines.append("[BINDER_WAIT]") for row in binder: lines.append(f"- {row.process_name}: {row.slice_name} {row.dur_ms:.1f}ms") jank = tp.query(''' SELECT COUNT(*) AS frames, SUM(CASE WHEN dur > 100_000_000 THEN 1 ELSE 0 END) AS jank_frames FROM slice WHERE name = 'Choreographer#doFrame'; ''') for row in jank: lines.append(f"[JANK_INFO] total_frames={row.frames}, jank_frames={row.jank_frames}") return "\n".join(lines)

用文本摘要而不是 JSON 喂给模型,我自己试下来效果更好。因为模型在解读自然语言段落时更顺手,而 JSON 里有太多括号和引号,模型反而容易在格式上出错。但要注意,摘要里的每个数值来源必须是可复现的 SQL 查询,而不是模型自己生成的东西。分析层的角色是“确定性计算”,这一点不能动摇。

4.3 Prompt 设计与结构化返回:从“泛泛而谈”到可落地的建议

Prompt 写得好不好,直接影响 AI 诊断结果的质量。我最早用的 prompt 只是简单说“请分析这份性能数据并给出优化建议”,结果模型给了一堆正确的废话,比如“建议优化主线程耗时”“建议减少内存分配”,看了等于没看。后来我改成强约束模式:给模型一个明确的角色任务、输入字段说明、输出 JSON 格式约定,并且要求它必须引用证据。

我现在的 prompt 大致长这样:

你是一名 Android 性能优化工程师,请根据以下 Perfetto trace 摘要信息定位性能问题根因。 要求: 1. 只基于提供的字段做判断,不要编造数据。 2. 输出 JSON,不要输出其他内容。 3. JSON 结构: { "is_problem": true/false, "severity": "low|medium|high", "category": "UI_JANK|IO_BLOCK|BINDER_WAIT|MEMORY|OTHER", "root_cause": "一句话根因描述", "evidence": ["证据1", "证据2"], "suggestion": "具体可执行的优化建议" } 输入信息: <SUMMARY> {summary} </SUMMARY>

加上固定的 JSON schema 之后,模型的输出至少是可解析的,后续代码可以直接把结果映射成报告字段。如果能再配一个 few-shot 示例,比如“如果 SLOW_FUNCTIONS 中主线程出现 800ms 的 view 绘制切片,且 BINDER_WAIT 中没有明显慢调用,则判断为 UI 层耗时问题”,输出的专业度会再上一个台阶。

还有一点要提醒,性能数据可能涉及用户隐私或业务敏感信息,尤其 trace 里可能包含应用内部类名、包名、甚至一些路径信息。如果模型走的是外部 API,必须处理好脱敏,或者干脆用私有化部署的模型。我们最后选型时考虑到数据合规和稳定性,用的是本地部署的开源模型,效果上对我来说完全够用。

5. 自动化诊断的 CI 接入与落地效果

5.1 串起整条链路:一个可落地的执行脚本

所有模块准备完之后,最后要做的就是把它们串成一条流水线。我写了一个 Python 脚本,接收场景名和测试用例名作为参数,内部按顺序执行:启动 Perfetto、跑自动化用例、停止采集、拉取 trace、用 trace_processor 解析提取摘要、调用 AI 接口、生成报告。

def run_diagnose(serial, scene_name, cases): start_trace(serial) run_ui_test(cases) stop_and_pull_trace(serial, scene_name) tp = TraceProcessor(file_path=f"traces/{scene_name}.perfetto-trace") summary = build_ai_summary(tp, f"{scene_name}.perfetto-trace", scene_name) diagnosis = call_ai_model(summary) report = render_report(scene_name, diagnosis, summary) save_report(report, f"reports/{scene_name}.md") return diagnosis

这个脚本最开始的版本跑得很不顺,问题主要出在时序。比如 Perfetto 还没真正启动,用例就开始跑了,导致前几秒的数据丢失;或者用例执行完毕但 trace 文件还没有刷新完成,直接 pull 回来一个损坏文件。后来我在几个关键节点都加了等待和重试逻辑,宁可慢一两秒,也不能漏数据。尤其是start_trace这一步,加了一个检查逻辑:先执行adb shell perfetto --version确认设备端可执行文件存在,再执行启动命令,启动后主动 sleep 2 秒,然后检查设备上的输出文件是否在增长,确认没问题才继续跑用例。

5.2 接入 CI 门禁:什么情况下拦截版本

流水线跑通之后,下一步就是把诊断结果接进 CI 门禁。但不是每次提交都跑全套性能诊断,那样成本太高,也没必要。我目前的策略是:在重要的集成分支上,每逢关键性能场景的自动化用例跑完后,自动触发诊断;诊断结果里如果出现severity=highis_problem=true,则把这条构建标记为失败,并在报告里附上根因和证据。这样相当于把性能问题当成和功能用例失败同等重要的拦截条件。

为了防止偶发问题造成误杀,我加了一个“连续三次才失败”的策略。也就是说,单次诊断出现 high 级问题只记录告警,连续三次同一场景同一类型的问题才真正拦截合并。这个策略来自我踩过的一个坑:有一次设备后台正好在跑系统应用更新,trace 里出现了明显的主线程阻塞,AI 也准确判断出来了,但问题并不是这次版本引入的,如果直接拦截就会误伤。加上连续多次确认的机制之后,稳定性好了很多。

报告输出我用的是一份 Markdown 文件,包含摘要信息和 AI 诊断结论,并统一归档到流水线产物里。测试同学和研发同学在 MR 页面上点开就能看到,不需要额外登录一套系统。

5.3 跑了一段时间后的实际效果:到底能省多少时间

这套方案上线后,我统计过一个小周期内的数据。之前一个性能专项,从开始抓 trace 到人工分析完 5 个核心场景,大约需要一个有经验的工程师半天时间;现在自动化链路跑完,单场景从采集到产出诊断报告只需要 10 分钟左右,而且大部分时间是在等设备执行用例和分析模型返回,人的参与时间大概只有 3 到 5 分钟,主要用来确认报告结论是不是合理。

必须承认,AI 的结论不是每次都能一把定位到根因。我的体感是,对于常见的 UI Jank、Binder 等待、主线程 I/O 阻塞这类问题,准确率已经比较高;但对于比较隐蔽的内存碎片化、底层驱动相关问题,AI 给出的建议常常还需要人工去追。但就算这样,它也已经把排查范围从“整份 trace”缩小到“某一类问题”,这个收益已经很可观了。后续随着样本积累和 prompt 迭代,准确率应该还能继续往上走。

6. 踩坑清单与经验速查

6.1 我遇到的典型问题与排查方法

症状常见原因解决办法
trace 文件只有 0 字节或明显过小TraceConfig 语法错误、设备权限不足、采集未真正启动先执行adb shell perfetto --version确认命令可用;用--txt配置时检查字段拼写;非 root 设备建议用 userdebug 版本
trace 中前段数据缺失Perfetto 还没启动完成,用例就开始执行启动后主动 sleep 2 到 3 秒,并检查输出文件大小是否持续增长
buffer 溢出,中间数据被覆盖单次采集时间过长或 buffer 太小调大size_kb,或者缩短单段采集时长,改为分段录制
SQL 查不到预期的 slice 数据TraceConfig 中未开启对应数据源检查是否有linux.ftrace数据源,以及ftrace_events里是否包含目标事件
AI 输出不稳定,时好时坏摘要信息结构混乱或 prompt 约束不足固定摘要模块顺序,增加 JSON schema 和 few-shot 示例;必要时增加重试逻辑
模型编造不存在的证据输入摘要中噪声太多,模型“脑补”了因果关系强制要求模型只基于输入字段输出,并明确“未命中不要猜测”
同一问题被重复告警设备后台环境不稳定,偶发因素被当成版本问题增加连续多次确认机制,或设置问题排除名单

这些坑里最隐蔽的是第二个。Perfetto 启动本身需要一点时间,如果自动化脚本刚发出启动命令就立刻跑 UI 用例,前几秒几乎必然是空的,而且从 trace 表面上不容易看出来,因为后面数据看起来都很正常。后来我在启动步骤做了文件增长检查,这个问题才彻底解决。

6.2 一些我从第一天起就建议养成的习惯

第一个习惯是“先校准,再自动化”。不要写完 SQL 提取逻辑就立刻全部交给 AI,而是先拿过去已经人工分析过的 trace 样本跑一遍,对比 AI 结论和人工结论是否一致,找到偏差后调整指标口径和 prompt。我大概用了两周时间来回校准,之后产出的报告才比较能看。

第二个习惯是“trace 文件一定要归档”。自动化诊断跑多了之后,你会发现 trace 文件本身才是最有价值的资产。AI 的结论可以重新生成,SQL 查询也可以反复执行,但原始 trace 如果被覆盖了,后面想再挖新特征就完全没有依据。我在 CI 产物里设置了按日期归档的规则,trace 文件默认保留 30 天,关键版本的 trace 单独标记长期保留。

第三个习惯是“保持环境干净”。性能测试对设备状态特别敏感,后台有 App 在更新、系统在做 dex2oat、充电电流不稳,都会直接影响结果。我们专门准备了两台测试机,一台连着充电器但电量稳定在 80% 以上,一台用独立电源,平时不装任何无关应用。这样采集到的 trace 噪声小很多,AI 分析的准确率也相应提高。

最后说一点我自己的体会。这套方案最花时间的不是写 prompt,也不是调模型,而是把 Perfetto 采集和对齐自动化测试场景这个基础做稳。AI 在这里其实更像一个读数据很快的初级分析师,它能给出什么质量的结论,取决于你喂给它的输入结构化程度。别指望第一天就全自动拦截所有性能劣化,先让链路跑起来,把每次 AI 判断和人工结论做对比,攒一批样本,指标口径和提示词自然会越来越准。这个方向后续还能继续扩展,比如增加更多 trace 类型、做版本间性能趋势对比,但底层逻辑始终是同一句话:采集标准化、分析特征化、结论可追溯。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询