iOS Trace 数据解析

iOS Trace 数据解析

目录


概览

本文档介绍如何解析和分析 Xcode Instruments 生成的 Trace 文件。Trace 文件包含了应用运行时的性能数据,包括 CPU 使用情况、函数调用堆栈、线程信息等。通过解析 Trace 文件,可以深入了解应用的性能特征,定位性能瓶颈。

主要内容

  • 基本概念:理解 Trace 文件中的关键概念,如 all_weightself_weight
  • 符号化:将内存地址转换为可读的函数名和代码位置
  • 文件格式:了解 Trace 文件的内部结构
  • 解析流程:掌握解析 Trace 文件的完整流程
  • 数据统计:学习如何正确统计和分析性能数据

一、基本概念

1.1 all_weight 和 self_weight

在 Instruments 的性能数据中,每个函数都有两个重要的时间指标:

  • all_weight:包含子函数调用的总执行时间
  • 表示函数及其所有子函数的总耗时
  • 用于了解函数在整个调用链中的总开销

  • self_weight:函数自身执行时间(不包含子函数调用)

  • 表示函数本身的执行时间,不包括调用其他函数的时间
  • 用于了解函数本身的性能开销

关系公式

all_weight = self_weight + sum(子函数的 all_weight)

示例

假设函数 A() 调用了 B()C()

A() {
    // self_weight = 10ms
    B();  // all_weight = 20ms
    C();  // all_weight = 30ms
}

A 的 all_weight = 10 + 20 + 30 = 60ms
A 的 self_weight = 10ms

使用场景

  • all_weight:用于分析函数在整个调用链中的总开销,适合定位性能瓶颈
  • self_weight:用于分析函数本身的性能,适合优化函数内部实现

1.2 Trace 文件结构

Trace 文件是 Instruments 生成的性能数据文件,包含以下核心元素:

  • id:所有元素都有一个全局唯一的 id
  • weight:表示执行时间或权重
  • text-address:函数调用地址
  • backtrace:调用堆栈信息
  • thread:线程信息

二、符号化

符号化是将内存地址转换为可读的函数名和代码位置的过程。这是解析 Trace 文件的关键步骤。

2.1 符号化流程

完整流程

  1. 生成模块列表文件:获取所有模块的 UUID 和基本信息
  2. 使用 atos 命令符号化地址:将内存地址转换为函数名和行号
  3. 生成 Symbols 文件:为后续解析准备符号信息

2.2 查看模块 UUID

每个模块(可执行文件、动态库等)都有一个唯一的 UUID,用于标识和匹配符号文件。

方法一:使用 symbols 命令

'/Applications/Xcode.app/Contents/Developer/usr/bin/symbols' -uuid 'WeMeetApp.app.dSYM/Contents/Resources/DWARF/WeMeetApp'

方法二:使用 dwarfdump 命令

xcrun dwarfdump --uuid *.dSYM

输出示例

UUID: 7C6E3623-FD50-3603-82E3-7F4F29B5DF76 (arm64) WeMeetApp.app.dSYM/Contents/Resources/DWARF/WeMeetApp

2.3 获取模块信息

获取模块的基地址和大小等信息,用于地址计算。

'/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool' -arch arm64 -l '/Users/mjzheng/Library/Developer/Xcode/iOS DeviceSupport/14.1 (18A8395)/Symbols/usr/lib/system/libsystem_pthread.dylib'

关键信息

  • 基地址(Load Address):模块在内存中的起始地址
  • 大小(Size):模块的大小
  • 架构(Architecture):arm64、armv7 等

2.4 生成 Symbols 文件

生成符号文件包,用于后续的符号化过程。

xcrun symbols -arch all -symbolsPackageDir symbols WeMeetApp.app.dSYM/Contents/Resources/DWARF/WeMeetApp

参数说明

  • -arch all:生成所有架构的符号文件
  • -symbolsPackageDir symbols:指定符号文件输出目录
  • 最后一个参数:dSYM 文件的路径

输出

会在 symbols 目录下生成以 UUID 命名的 .symbols 文件,例如:

symbols/7C6E3623-FD50-3603-82E3-7F4F29B5DF76.symbols

参考:Google 搜索关键词:xcrun symbols

2.5 使用 atos 符号化

使用 atos 命令将内存地址转换为函数名和行号。

# 基本用法
atos -o <binary> -l <load_address> <address>

# 示例
atos -o WeMeetApp.app/WeMeetApp -l 0x100000000 0x100001234

参数说明

  • -o:指定可执行文件或 dSYM 文件
  • -l:指定模块的加载地址(Load Address)
  • 最后一个参数:要符号化的内存地址

输出示例

-[ViewController viewDidLoad] (in WeMeetApp) (ViewController.m:45)

三、Trace 文件格式

3.1 Trace 文件结构

Trace 文件(.trace)是 Instruments 生成的二进制文件,可以通过 xctrace 命令导出为 XML 格式进行分析。

导出 XML

xcrun xctrace export --input 34.trace --xpath '/trace-toc/run[@number="1"]/data/table[@schema="time-profile"]' --output 34.xml

3.2 Symbols 文件格式

Symbols 文件以模块的 UUID 命名,格式为:

<UUID>.symbols

示例

7C6E3623-FD50-3603-82E3-7F4F29B5DF76.symbols

前缀为 UUID,用于标识模块。

3.3 XML 格式说明

导出的 XML 文件包含以下核心元素:

基本元素

  • 所有元素都有一个全局唯一 id:用于建立元素之间的关联关系

weight 元素

  • weight fmt:表示执行时间
  • 单位通常是微秒(microseconds)
  • 用于计算函数或堆栈的执行时间

text-address 元素

  • 表示函数调用链:一个地址对应一个函数调用
  • 根函数在最末尾:调用链的顺序是从调用者到被调用者
  • 多个 text-address 组成调用堆栈

示例

<text-address id="123">0x100001234</text-address>
<text-address id="124">0x100002345</text-address>

backtrace 元素

  • 一个 backtrace 表示一个调用堆栈
  • 包含多个 text-address:按照从上到下的顺序组成调用堆栈
  • 关联一个 weight id:表示该堆栈的执行时间

结构示例

<backtrace id="456">
  <text-address id="123"/>
  <text-address id="124"/>
  <weight id="789"/>
</backtrace>

调用堆栈顺序

函数A (顶层,调用者)
  └─ 函数B
      └─ 函数C (底层,被调用者)

四、解析流程

4.1 解析步骤

Trace 文件 id 是唯一的,text-addresses 顶层地址在最后面,一个线程有多个 backtrace id

完整解析步骤

  1. 获取 id->item 的字典
  2. 建立所有元素的映射关系
  3. 通过 id 快速查找对应的元素

  4. 建立 thread_id->backtrace id 数组的映射

  5. 将每个线程的所有 backtrace 组织起来
  6. 相同 backtrace id 的权重相加,得到 backtrace 的总权重

  7. 解析 backtrace id 的调用地址

  8. 遍历 backtrace 中的 text-address
  9. 将地址转换为函数名(使用符号化工具)

  10. 获取线程树

  11. 遍历线程的所有 backtrace
  12. 所有 backtrace 的权重相加得到线程的总权重
  13. 层次遍历 backtrace 里面的调用地址,同一层相同地址进行合并

4.2 数据结构映射

关键数据结构

# id -> item 映射
id_to_item = {
    "123": text_address_element,
    "456": backtrace_element,
    "789": weight_element,
    ...
}

# thread_id -> backtrace_ids 映射
thread_to_backtraces = {
    "thread_1": ["backtrace_1", "backtrace_2", ...],
    "thread_2": ["backtrace_3", "backtrace_4", ...],
    ...
}

# backtrace_id -> weight 映射
backtrace_weights = {
    "backtrace_1": 1000,  # 微秒
    "backtrace_2": 2000,
    ...
}

4.3 线程树构建

构建过程

  1. 收集线程的所有 backtrace
    python
    thread_backtraces = thread_to_backtraces[thread_id]

  2. 计算线程总权重
    python
    thread_total_weight = sum(backtrace_weights[bt_id] for bt_id in thread_backtraces)

  3. 构建调用树

  4. 遍历每个 backtracetext-address 序列
  5. 从顶层(最后一个地址)到底层(第一个地址)构建树结构
  6. 同一层的相同地址进行合并,权重累加

示例

假设有两个 backtrace:

Backtrace 1:
  A -> B -> C (weight: 100)

Backtrace 2:
  A -> B -> D (weight: 200)

构建的调用树:

A (weight: 300)
└─ B (weight: 300)
    ├─ C (weight: 100)
    └─ D (weight: 200)

五、数据统计方法

5.1 平均统计 vs 加权平均

在性能数据分析中,选择合适的统计方法非常重要。

平均统计

公式平均值 = sum(所有值) / 数量

特点
– 简单直观
– 易受极端值(bad case)影响
– 可能掩盖性能问题

示例

假设 99 个用户 CPU 使用率为 1%,1 个用户 CPU 使用率为 99%:

平均统计 = (99 × 1 + 1 × 99) / 100 = 1.98%

结果看起来正常,但实际上有一个用户出现了严重的性能问题。

加权平均

公式加权平均 = sum(值 × 权重) / sum(权重)

特点
– 可以根据重要性分配权重
– 仍然可能受极端值影响
– 需要合理设置权重

示例

加权平均 = (1 × 0.99 + 99 × 0.01) = 1.98%

如果权重设置不合理,仍然可能掩盖问题。

5.2 分位数统计

分位数统计是性能监控中更可靠的方法。

常用分位数

  • P50(中位数):50% 的数据小于等于该值
  • P95:95% 的数据小于等于该值
  • P99:99% 的数据小于等于该值
  • P99.9:99.9% 的数据小于等于该值

优势

  • 不受极端值影响:P95、P99 等分位数可以过滤掉极端情况
  • 反映真实用户体验:关注大多数用户的性能表现
  • 便于设置告警阈值:可以基于分位数设置合理的告警

示例

CPU 使用率分布:
P50: 1%
P95: 5%
P99: 50%
P99.9: 99%
Max: 99%

通过分位数可以看出:
– 大多数用户(P95)CPU 使用率正常
– 但仍有少量用户(P99)出现性能问题
– 极少数用户(P99.9)出现严重问题

5.3 统计方法选择建议

关键点:平均统计法,易受 bad case 的影响

建议

  1. 对于性能监控,应该关注 P95、P99 等分位数指标
  2. 了解大多数用户的性能表现
  3. 及时发现性能问题

  4. 避免单一平均值掩盖问题

  5. 平均值可能看起来正常,但实际存在性能问题
  6. 结合分位数和分布图分析

  7. 结合分布图分析性能数据

  8. 直方图:了解数据分布情况
  9. 箱线图:查看数据的分散程度和异常值
  10. 时间序列图:观察性能趋势

  11. 设置合理的告警阈值

  12. 基于 P95 或 P99 设置告警
  13. 避免基于平均值设置告警(容易被极端值触发)

  14. 关注异常值

  15. 虽然分位数可以过滤异常值,但仍需要关注
  16. 分析异常值的原因,可能是 bug 或性能问题

统计方法对比

统计方法 优点 缺点 适用场景
平均值 简单直观 易受极端值影响 数据分布均匀时
加权平均 可设置权重 权重设置困难 有明确权重需求时
中位数(P50) 不受极端值影响 可能忽略问题 了解典型情况
P95/P99 反映真实体验 计算稍复杂 性能监控推荐
最大值 发现最坏情况 可能过于敏感 关注极端情况

总结

解析 Instruments Trace 文件是 iOS 性能分析的重要技能。通过理解 Trace 文件的结构、掌握符号化方法、正确解析数据并使用合适的统计方法,可以深入分析应用性能,定位性能瓶颈。

关键要点

  1. 理解基本概念all_weightself_weight 的区别和用途
  2. 掌握符号化流程:从 UUID 获取到地址转换的完整流程
  3. 了解文件格式:Trace 文件和 XML 格式的结构
  4. 正确解析数据:按照步骤解析并构建调用树
  5. 选择合适的统计方法:使用分位数而非平均值进行性能监控

留下评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注

Index