移动/游戏
用 ETTrace 对 iOS…
当你需要用 ETTrace 对一段有界的 iOS 启动或运行时流程做采样分析,并希望结果可符号化、可复现、可对比时使用。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15
适用场景
用 ETTrace 对 iOS 启动或运特定
解决思路
- 在采集前写下采集契约:一个确切的用户流程及其起止边界;是启动采集还是已运行时的运行时采集;app commit、构建配置、优化设置和架构;ETTrace runner 与 framework 版本,以及单线程或多线程模式;模拟器/设备型号与 OS build;启动方式、数据状态、缓存状态和重复次数。任何一项有实质差异就拒绝前后对比结论,应在同一契约下重新构建并重复。
- 先做分诊再插桩:确认 ETTrace 的采样火焰图是合适的下一步工具。当慢的区间未知、跨越多个子系统,或需要系统级阻塞、I/O、卡顿、并发上下文时,优先先用 Instruments。
- 选择一个足够小、可重复的流程。启动、首屏构建、打开一个文档或应用一次编辑都是有用的边界;“使用 app”不是。
- 固定 ETTrace 版本:从官方 tap 安装 runner,并把 ETTrace package product 链接到被测量的 app target。分别记录 runner 版本和 framework revision 或 tag,并在对比采集之间保持两者固定。内置分析器支持已验证的 v1.1.1 processed 输出格式;使用不同格式前先重新检查上游和解析器。
- 运行命令:brew install emergetools/homebrew-tap/ettrace
- 运行一次插桩后的 app,确认出现 `Starting ETTrace` 日志。没有这条消息意味着采集证据不可信。除非项目有意承担该取舍,否则不要把该接线留在发布配置中。
- 先构建,再收集匹配的 dSYM:采集和收集 dSYM 都使用最终插桩后的构建。绝不要按文件名、修改时间或 Derived Data 邻近性来选择 dSYM。
- 运行命令:mkdir -p /tmp/myapp-ettrace;python3 scripts/collect_dsyms.py --app /path/to/Build/Products/Release-iphonesimulator/MyApp.app --search-root /path/to/Build/Products --search-root /path/to/Archives --output /tmp/myapp-ettrace/dsyms --pretty > /tmp/myapp-ettrace/dsym-report.json
- 该辅助脚本会比较 app 可执行文件和内嵌二进制的 `dwarfdump --uuid` 输出。缺失或有歧义的 UUID 匹配默认会停止运行。读取并保留 `dsym-report.json`,不要假设每个复制的符号文件都相关。当 UUID 匹配无法复制到 ETTrace 1.1.1 能发现的唯一扁平目标名时,该辅助脚本会失败。
- 从干净的产物目录采集:在空目录中运行 ETTrace,因为 processed 文件会写入当前工作目录。模拟器用 `--simulator`,精确 dSYM 目录用 `--dsyms`。仅对启动工作添加 `--launch`,并严格遵循 runner 的两次启动提示。
- 运行命令:mkdir -p /tmp/myapp-ettrace;mkdir /tmp/myapp-ettrace/run-01;cd /tmp/myapp-ettrace/run-01;ettrace --simulator --dsyms /tmp/myapp-ettrace/dsyms
- 第二个 `mkdir` 在该 per-run 目录已存在时必须失败。选择新的 run 名称,不要把重试的 processed 采集混在一起。
- 当 runner 提示时,从 Home Screen 点击 app 启动。从 Xcode 启动可能改变启动路径和时序。设备采集时省略 `--simulator`;保持所有其他实验字段稳定。
- 在有界流程结束后立即停止,并把每个新的 `output_<threadId>.json` 连同采集契约一起保留。ETTrace 1.1.1 在符号化后创建这些 processed 文件。其内部 raw runner `output.json` 是不同产物,下面的分析器不接受。
- 校验并汇总 processed 输出:运行命令:python3 scripts/analyze_ettrace.py /tmp/myapp-ettrace/run-01/output_*.json --top 25 --pretty > /tmp/myapp-ettrace/run-01/summary.json
- 该辅助脚本会校验 v1.1.1 processed 节点形状,拒绝重复输入或混合的 `osBuild`/`device`/`isSimulator` 元数据,处理序列化器的 object-or-array `children` 字段,并输出确定性 JSON。它不会重写采集文件。把这些原始文件保留在 summary 旁边。
- 当重要 app 帧是 `<unknown>`、原始地址或归属到错误二进制时,停止并修复符号化。未符号化的热点地址是证据缺口,不是代码建议。ETTrace 1.1.1 中带地址的节点列在 `unresolved_frames` 下,并被排除在普通热点之外。
- 只改变一个原因并重复:根据热点 app 自有路径做最小的代码或配置改动。重新构建、重新收集 UUID 匹配的 dSYM,并对同一流程至少采集两次。报告方差和完整采集契约。
- 解读报告时:Exclusive seconds 近似表示减去直接子节点时长后直接计入某符号的采样时间,从高 exclusive 的 app 自有工作开始看。
- 解读报告时:Inclusive seconds 表示整个调用子树的权重,用来找昂贵的入口路径,而不是用来归咎每个父帧。
- 解读报告时:聚合 inclusive 时间可能超过根时长,因为递归或嵌套出现会重复计入每个帧。Exclusive 百分比使用根时长。
- 解读报告时:`<unattributed>` 单独报告。该值很大时会削弱关于缺失区间内发生了什么的结论。
- 解读报告时:framework 热点仍可能由 app 调用频率、数据形状或配置引起。向上走到第一个可控的 app 自有调用者。
- 解读报告时:多线程文件汇总的是线程时间,不是墙钟延迟。不要把它们合并后的根时长描述为经过时间。
- 避免常见错误:分析无界会话后再猜测哪些样本匹配 bug。
- 避免常见错误:采集一个构建却提供另一个构建或架构的 dSYM。
- 避免常见错误:把 ETTrace 的 raw runner JSON 当作 processed 火焰图。
- 避免常见错误:把启动采集与运行时采集比较,或把单线程与多线程采集比较。
- 避免常见错误:不检查 exclusive 工作和子路径就优化 top inclusive 父节点。
- 避免常见错误:仅凭一次噪声较大的运行就声称有改进。
- 避免常见错误:意外把 ETTrace 插桩留在生产环境。
- 对照审查清单:记录了一个可复现流程及其起止边界。
- 对照审查清单:启动/运行时采集模式、单/多线程模式、runner 版本和 framework revision 匹配。
- 对照审查清单:构建配置、架构、目标/OS、启动方式以及 app 数据/缓存状态匹配。
- 对照审查清单:每次采集都从新的空 per-run 产物目录开始。
- 对照审查清单:app 和内嵌二进制 UUID 与提供的 dSYM 匹配。
- 对照审查清单:dSYM 收集 JSON 报告连同 source/destination 以及缺失、歧义、冲突或不兼容证据一起保留。
- 对照审查清单:processed `output_<threadId>.json` 文件原样保留。
- 对照审查清单:重要 app 帧已符号化。
- 对照审查清单:exclusive、inclusive、unattributed 和多线程语义清晰。
- 对照审查清单:建议指明 app 可控路径及支持证据。
- 对照审查清单:验证重复同一契约并报告不止一次运行。
- 对照审查清单:临时插桩已移除或有意限定范围。
适用边界 · 注意事项
- 当慢的区间未知、跨越多个子系统,或需要系统级阻塞、I/O、卡顿、并发上下文时,不要用 ETTrace,优先用 Instruments。
- 当需要视图 identity、失效和布局修复时,不要用 ETTrace,改用 SwiftUI 性能指导。
- 不要把 ETTrace 的采样时长当作生产墙钟计时,也不要与配置不同的 profiler 直接比较。