Perfetto 之内存优化

🥦 Perfetto 工具的来源

想详细了解 Perfetto 的话, 可以跳转 谷歌开发者文档 查阅, 这里只是简单引用几句话说明一下

The System tracing utility is an Android tool that saves device activity to a trace file. On a device running Android 10 (API level 29) or higher, trace files are saved in Perfetto format, as shown later in this document. On a device running an earlier version of Android, trace files are saved in the Systrace format.

从上面一段的描述可以看出, Android 系统是内置跟踪程序的, 此程序可以将设备的活动保存至跟踪文件中, 在 Android 10 及以上版本中, 跟踪文件会以 Perfetto 格式保存, 在 Android 9 及以下版本中, 跟踪文件会以 Systrace 格式保存

Systrace is a legacy platform-provided command-line tool that records device activity over a short period of time in a compressed text file. The tool produces a report that combines data from the Android kernel, such as the CPU scheduler, disk activity, and app threads. Systrace works on all Android platform versions, but we recommend Perfetto for devices running Android 10 and higher.

Perfetto is the platform-wide tracing tool introduced in Android 10. It is a sophisticated open source tracing project for Android, Linux, and Chrome. It offers a superset of data sources compared to Systrace and lets you record arbitrarily long traces in a protocol buffer binary stream. You can open these traces in the Perfetto UI.

从上面两段的描述可以看出, Perfetto 本质就是 Systrace 的上位替代品, 是谷歌新推出的一款跟踪工具, 已于 Android 系统中 内置

🥝 Perfetto 的组成

Perfetto 是一套开源的多平台性能分析工具集, 它可以跟踪多种性能数据, 可以看下面的截图, 以 CPU GPU Memory 为例, 当然远不止这些

perfetto-1

perfetto-2

perfetto-3

这里我们就专注于看内存分析中的 Native Heap Profiling 部分

👻 内存基础知识

先来补充一下基础知识, 对基础很了解的可以直接跳到这里: 堆内存分析的原理

🦄 内存分类

内存可以按照 堆内存 (Heap) 和 栈内存 (Stack) 的方式分类, 栈内存不会有内存泄漏问题, 而且通常不纳入分析, 因此我们之后分析的都是堆内存

而堆内存又分为 托管内存 (managed memory) 和 原生内存 (native memory), 原生内存也叫做非托管内存, 下文都称为 非托管内存

🐔 非托管 (堆) 内存

在学校学习时期编写的 C 语言控制台程序就是纯粹的非托管内存程序, 那时候无论申请还是释放堆内存, 都要自己手动调用 malloc / calloc / realloc / free

用一句话总结的话, 完全由开发者手动申请手动释放的堆内存就是非托管内存

🐤 托管 (堆) 内存

那么什么是托管内存呢 ? 要说明托管内存, 就要先说明一下 "托管运行时", 在开发 C/C++ 程序时, 有下面一些问题:

  1. 指针 引发的内存安全问题, 为了避免 野指针, 内存越界 等内存安全问题, 需要一个统一的管理层

  2. "跨平台" 问题, 如何做到只编写一份代码就可以在各个平台上正确运行, 也需要一个统一的管理层

  3. 系统安全问题, 使用 C/C++ 可以做内存病毒, 因为是直接在操作系统上裸跑, 所以为了限制程序访问敏感资源, 防止损坏系统, 也需要一个统一的管理层

基于上面种种问题, "托管运行时" 被提出, 一个 负责执行程序代码, 并统一管理内存, 类型安全, 线程, 异常, GC 等功能的中间层, 由 "托管运行时" 帮我们申请的内存, 就是由这个 "托管运行时" 来管理的, 它会帮我们管理, 帮我们释放, 不过释放时有卡顿风险

这样我们的程序和最初的 C/C++ 程序相比, 可以简单理解为, 不再直接和操作系统打交道, 而是交给一个托管商, 这个托管商帮助我们管理资源, 调度执行, 处理异常, 回收垃圾内存等, 这部分被托管商管理的内存就是托管内存啦

🎄 内存的组成

虽说托管运行时接管了部分内存的申请和释放, 但托管运行时并没有硬性限制我们只能用托管内存, 我们也可以跳过托管, 直接向系统申请非托管内存, 不要想当然地理解成有了托管运行时后, 所有内存就都走托管了, 所以现代软件中, 几乎全部程序的堆内存都是由 非托管内存托管内存 共同组成的

举一个例子, 比如 Android NDK, 全称是 Android Native Development Kit, 允许 Android 应用直接编写 C/C++ 代码, 既然都可以写 C/C++ 代码了, 自然就可以直接申请非托管堆内存, 以及调用底层系统 API

再举一个例子, 我们都知道, Java 程序运行在 JVM 上, JVM 的全称是 Java Virtual Machine, 翻译过来就是 Java 虚拟机, 但是也不要想当然认为 Java 代码申请的内存就全是托管内存, 比如 JNI, 全称是 Java Native Interface, 其中像 ByteBuffer.allocateDirect() 这样的 API 就可以实现在 JVM 堆外申请内存, 这种内存是不归 JVM 管的, 也是非托管内存

所以再次说明, 现代应用程序的堆内存是由 非托管内存托管内存 共同组成的

🦴 常见的托管运行时

下面是一些常见的托管运行时, 看看有没有你所熟知的

托管运行时 主要编程语言 代表平台 / 应用
.NET CLR / CoreCLR / Mono / IL2CPP C#, F#, VB.NET Windows 应用, Unity 游戏的 C# 层, Xamarin
JVM (Java Virtual Machine) Java, Kotlin, Scala, Groovy Android 应用, 企业服务器端
V8 / SpiderMonkey / JavaScriptCore JavaScript, TypeScript Chrome / Node.js / Safari / Web 前端
Python Interpreter (CPython, PyPy) Python AI 脚本, 数据分析, 自动化
Ruby MRI / YARV Ruby Web 开发 (Rails)
BEAM VM Erlang, Elixir 电信系统, 高并发后台
Lua VM Lua 游戏脚本, 嵌入式脚本系统

⏳ Perfetto 堆内存分析的原理

基础知识说明完毕, 接下来开始说明如何使用 Perfetto 进行堆内存分析, Perfetto 提供了两种互补的技术来专项负责调试上述两种内存

第一种 : heap profiling for native code

非托管代码 的堆内存分析, 基于 malloc/free 时点, 对调用堆栈进行采样 (此次的研究内容)

像 C/C++/Rust 这样的 Native Languages, 通常使用 libc 系列的 malloc / free 函数在底层分配和释放内存

Perfetto 分析这部分非托管内存的工作原理是拦截对这些函数的调用, 并注入代码来跟踪已分配但未释放的内存的调用栈, 还可以实现追踪每次分配的代码来源

注意:

使用 Perfetto 进行非托管堆内存分析仅适用于 Android 和 Linux, 因为 Perfetto 所开发的拦截 malloc / free 的技术, 仅适用于这些操作系统

另外此堆分析不具有追溯力, 它只能报告跟踪开始后发生的分配, 无法提供任何有关跟踪开始前的信息

如果需要分析进程启动时的内存使用情况, 则必须在进程启动前便开始跟踪

第二种 : heap dumps for Java/managed code

托管代码 的堆内存转储, 以创建堆保留图的形式, 来显示对象之间的保留依赖关系 (目前不做研究)

🍖 Perfetto 之 UI 模式

Perfetto 有两种使用方式, 第一种使用谷歌所提供的 Perfetto UI 网站来使用 Perfetto

分析准备

  1. 目标设备必须是 Android 10 及以上
  2. 应用程序是 可分析可调试
  3. 找到待分析的进程名称
  4. 安装 android-tools, 即 ADB

在 adb 中执行 adb shell ps -A 可以打印出进程信息, 通过筛选缩小数量, 最后一列就是需要填写的内容

Windows 操作系统可以使用 Select-String 进行筛选, Linux 则是使用 grep : adb shell ps -A | Select-String funny

连接设备

  1. 打开 Perfetto UI 网站 https://ui.perfetto.dev/
  2. 选择 Record new trace
  3. 选择 Target device
  4. 选择 Android
  5. 选择 WebUSB (FireFox 不支持 WebUSB, 请使用 Chrome 或 Edge)
  6. 点击 Connect new device

成功连接设备后如下图

perfetto-4

配置跟踪

  1. 选择左侧 Probes 中的 Memory
  2. 开启 Native heap profiling
  3. 填写 进程名称, 转储延时, 连续转储间隔 等
  4. 选择左侧 Record settings 中的 Buffers and duration, 设置时长和大小限制

跟踪内存

  1. 选择 Target device
  2. 点击最下方的 Start tracing

🍉 Perfetto 之 CLI 模式

前面说了 Perfetto 的第一种使用方式, 直接使用 Web UI, 但是这种方式无法对堆栈信息进行符号化, 而 CLI 模式可以, 所以我们选择 CLI 模式, CLI 模式也有两种使用方式

第一种 : 直接使用 perfetto 命令

使用 perfetto 命令, 这种使用方式灵活, 通过不同的配置文件可以进行不同的数据跟踪, 指定堆内存分析配置, 就可以进行堆内存分析了, 指定 CPU 分析配置, 就可以跟踪 CPU 数据, 但是使用难度很高, 而且进行非托管内存跟踪时无法自动符号化, 关于此命令的详细说明, 请参考 注解1

第二种 : 使用官方的 heap_profile 脚本

这个脚本是专门用于堆内存分析的, 只会跟踪堆内存数据, 无法进行其他的数据跟踪, 但对我们而言, 已经足够了, 另外 heap_profile 脚本支持自动符号化, 还能直接将跟踪数据从手机导出到 Linux 中, 不再需要手动 pull, 因此后续会讲解 heap_profile 脚本进行非托管内存分析

现在可以先将这个脚本下载下来, 后面会用: https://raw.githubusercontent.com/google/perfetto/main/tools/heap_profile

🍑 WSL 的说明

是不是觉得很奇怪, 前面不是在讲 CLI 模式嘛, 怎么只讲了一点概述就跳转到 WSL 了 ? WSL 又是什么 ?

先说一下什么是 WSL, WSL 全称 Windows Subsystem for Linux, 是一个可以让开发人员在 Windows 计算机上同时访问 Windows 和 Linux 的强大功能, 通过 WSL, 我们就可以直接安装各种 Linux 发行版, 例如 Ubuntu, Debian, Arch Linux 等, 并直接在 Windows 上使用 Linux 程序和 Bash 命令行工具, 而不用进行任何修改, 也无需运行虚拟机或配置双启动, 是不是很方便呢 ?

那么为什么要提到 Linux 呢 ? 前面有提到 "堆栈信息符号化", 目前这个功能只能在 Linux 或者 MacOS 上实现, 虽然其他的功能 Windows 都能实现, 但对于堆内存分析而言, "堆栈信息符号化" 是必须的一环, 不能跳过, 否则跟踪出来的数据就是一堆垃圾, 毫无用处, 所以接下来我们要在 Linux 上使用 Perfetto, 不过在这之前, 先说明一下如何使用 WSL

🍒 开启 WSL

WSL 是 Windows 操作系统内置的, 不需要手动安装, 但是默认是关闭的, 想要启用 WSL, 需要开启以下设置

  1. 启用 CPU 虚拟化, 这个需要在主板设置中开启, 不同主板的设置方法不用, 请自行搜索
  2. 开启 虚拟机平台 功能, 在 "启用或关闭 Windows 功能" 中开启
  3. 开启 适用于 Linux 的 Windows 子系统 功能, 在 "启用或关闭 Windows 功能" 中开启

perfetto-5

设置完毕后, 记得重启计算机, 这样 WSL 就成功启用了

如果担心自己电脑上的 WSL 不是最新版, 可以通过 wsl --update --web-download 更新 WSL

如果通过命令更新失败, 也可以直接去 github 下载 .msixbundle 文件, 手动进行更新 (https://github.com/microsoft/WSL/releases)

可以通过 wsl --version 查看当前已安装 WSL 的版本信息

WSL 版本: 2.6.1.0

内核版本: 6.6.87.2-1

WSLg 版本: 1.0.66

MSRDC 版本: 1.2.6353

Direct3D 版本: 1.611.1-81528511

DXCore 版本: 10.0.26100.1-240331-1435.ge-release

Windows: 10.0.19045.6216

🍅 安装 Linux : Ubuntu

wsl --list --online : 列出当前可安装的 Linux 发行版本

wsl --list --verbose : 列出当前已安装的 Linux 发行版本

wsl --install <name> : 例如 Ubuntu-24.04, 安装 Ubuntu-24.04 子系统

wsl --unregister <name> : 例如 Ubuntu, 删除已安装的 Ubuntu 子系统

先通过 list 命令得到想要安装的发行版名称, 再使用 install 命令安装即可, 我安装的是 Ubuntu 发行版, 所以下面说明一下 Ubuntu 的一些命令

🍐 更新 Ubuntu

上一步安装完 Linux 之后, 开始菜单中就可以看到 Ubuntu 了, 可以直接启动, 启动后就是一个控制台, 是的, 就只是一个控制台, 因为我们并没有安装桌面系统, 所以这里的 Ubuntu 是没有桌面的, 当然你可以自己安装一个桌面系统, 就可以使用带桌面的 Linux 了, 不过我们这里并不需要, 因为我们之后使用 Perfetto 也是以命令行的方式使用

cat /etc/os-release

通过此命令可以查看系统信息, 其中 "cat" 全称 "concatenate"

sudo apt update

apt 就是 Ubuntu 系列的包管理器命令, 不同的 Linux 系列使用不同的包管理器命令

Debian 系列, 使用 apt, dpkg

Red Hat 系列, 使用 rpm, yum, dnf

Arch Linux 系列, 使用 pacman, yay, paru

使用此命令可以更新本地的软件包索引, 以熟悉的 git 对比的话, 这就是 "fetch" 命令, 执行后通常会提示有多少包需要更新

sudo apt upgrade

前面查询出需要更新的包, 现在就可以使用此命令执行更新, 可以使用 "-y" 来取消交互, 一路自动 "yes"

🍎 在 Linux 上安装 Perfetto 所需的环境

sudo apt install adb

我们的目标是跟踪 Android 程序, 因此必须安装 ADB

sudo apt install llvm

这里的 llvm 是什么呢 ? 我们为什么要安装它呢 ?

llvm 的全称是 Low Level Virtual Machine, 但是现代的 llvm 早已不再是虚拟机, 而是一整套编译器基础设施, 包含一系列模块化的编译器组件和工具链, 主要用于开发编译器以及优化编译, 而我们想要让堆栈信息符号化, 就必须安装其中的 llvm-symbolizer, 这是 llvm 工具集的一部分, 所以我们直接安装 llvm 即可

安装完成后, 可以使用 llvm-symbolizer --version 验证是否安装成功

🍊 Windows 与 Linux 共享数据总线

这是什么意思呢 ? 虽然 WSL 实现了 Windows 和 Linux 几乎一体化的操作体验, 但是它们俩本质还是两个操作系统

我们的安卓手机通过数据线只能连接到 Windows, 而无法连接到 Linux, 所以接下来我们要想办法让 Linux 可以连接我们的安卓手机

那么怎么做到呢 ? 就是标题了 : Windows 与 Linux 共享数据总线

  1. 安装 USBIPD, 去 usbipd 这里下载 usbipd-win_5.2.0_x64.msi 并安装

  2. 执行 adb kill-server : 目的是让 Windows 端停止 ADB 服务, 防止 ADB 占用总线, 导致无法共享

  3. 执行 usbipd list : 列出连接到 Windows 的所有 USB 设备, 找到要共享到 WSL 的总线 ID, 我的是 2-8

  4. 执行 usbipd bind --busid 2-8 --force : 共享总线, 之后可以通过执行 usbipd list 验证是否正确开启共享

  5. 执行 usbipd attach --wsl --busid 2-8 : 将总线附加到 WSL 中, 这样 WSL 就可以访问这条数据总线了

🍈 Linux 通过 ADB 连接 Android 设备

切换到 Linux 这边, 也不是说现在就可以连接 Android 了, 还需要先安装一个工具

  1. 执行 sudo apt install usbutils 安装 USB 助手: usbutils

  2. 执行 lsusb 查看连接的 USB 设备, 此时应该就可以看到安卓手机了 (MTP + ADB)

  3. 执行 adb devices 就可以进行 USB 调试了

🌈 Perfetto 之 CLI 模式

做完前面的步骤, 我们的 Linux 就准备好了, 接下来正式讲解 Perfetto 命令行模式的使用步骤

分析前提

先来看下分析的前提

  1. 目标设备必须是 Android 10 及以上
  2. 应用程序是 可分析可调试
  3. 找到待分析的进程名称
  4. linux 安装 android-tools, 即 ADB
  5. linux 安装 llvm-symbolizer
  6. linux 安装 python3

前面的步骤中, 我们已经安装好了 adb 以及 llvm-symbolizer, 看要求还要安装 python3 ? 为什么要用 python3 呢 ? 因为前面 这里 不是说了要用 heap_profile 脚本嘛, 而这个脚本就是一个 python 脚本 😂 哈哈哈

那为什么安装 Linux 那里没有让你安装呢, 是因为 Ubuntu 中已经自带了 python3, 所以不需要再次安装, 可以使用 python3 --version 命令查看当前版本

连接手机

adb devices

使用命令检查手机, 确保 Linux 已通过 ADB 连接手机

List of devices attached

9dcb88d1 device

将准备文件放置到 Linux

在 Windows 文件管理器中输入 \\wsl$ 即可访问安装的 Linux 发行版

将之前下载好的 heap_profile 文件, 以及需要解析的符号表 (*.so) 放置到 home/kuroha/Documents 文件夹中

其中 kuroha 是我设置的 Linux 用户名, Documents 需要自己新建

符号表文件解压后直接放进文件夹即可, 后面我们会设置为 index 模式, 此模式下会自动递归搜索文件夹, 所以不需要什么特殊路径配置

perfetto-6

给予 heap_profile 脚本执行权限

chmod +x ~/Documents/heap_profile

这里的 ~ 就是用户文件夹, 即: home/kuroha

给予 heap_profile 脚本执行权限, 这样 heap_profile 就可以在命令行中执行了, 权限设置好之后, 我们开始使用 heap_profile 命令进行堆内存跟踪

关于这个脚本的具体使用方法可以执行 Documents/heap_profile -h 进行查看, 也可以在 cli 文档中查看帮助信息

捕获非托管内存快照

如果直接使用 heap_profile -n <name> 命令, 可以执行默认配置的内存捕获

但是我们一般都是使用参数来进行自定义的内存捕获, 下面是常用的参数, 具体请参考 注解2

-n 用于指定需要跟踪的进程的名称

-c 可以设置数据保存的间隔

-d 可以设置总的跟踪时长

命令示例 : Documents/heap_profile -n com.Kuroha.SwordRequiem -c 5000 -d 60000

符号化

通过环境变量 PERFETTO_SYMBOLIZER_MODEPERFETTO_BINARY_PATH 可以指定符号文件的位置

将 PERFETTO_SYMBOLIZER_MODE 设置为 index 就可以递归搜索文件夹, 寻找符号表文件, 这样就不需要关注路径问题了

PERFETTO_BINARY_PATH 的值就是符号表文件所在的根目录, 下面是最终的完整命令

1
PERFETTO_SYMBOLIZER_MODE="index" PERFETTO_BINARY_PATH="/home/kuroha/Documents/symbols_sword/" Documents/heap_profile -n com.Kuroha.SwordRequiem -c 5000 -d 60000

执行此命令后, 会立即进行一次数据保存, 并且接下来的 1 分钟内, 每过 5 秒会再进行一次数据保存, 并生成跟踪文件, 此时生成的文件在 Android 手机中

1 分钟结束后, 会自动拉取跟踪数据到本机, 即从 Android 手机拉取到 Linux 上, 并自动进行符号化

最终生成 13 个 .pb.gz 跟踪数据文件, raw-trace 文件, symbolized-trace 文件, symbols 文件

此时打开 Perfetto UI 网站, 点击 Open trace file, 选择 symbolized-trace 文件即可查看最终数据

perfetto-7

🍀 参考

以下是一些参考资料

⚡ 注解1

Perfetto 命令的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
adb shell perfetto -h

Usage: perfetto
--background -d : Exits immediately and continues tracing in background
--config -c : /path/to/trace/config/file or - for stdin
--out -o : /path/to/out/trace/file or - for stdout
--upload : Upload field trace
(Android only)
--dropbox TAG : DEPRECATED: Use --upload instead TAG should always be set to 'perfetto'
--no-guardrails : Ignore guardrails triggered when using --dropbox
(for testing).
--txt : Parse config as pbtxt. Not for production use. Not a stable API.
--reset-guardrails : Resets the state of the guardails and exits all_heaps
(for testing).
--query : Queries the service state and prints it as human-readable text.
--query-raw : Like --query, but prints raw proto-encoded bytes of tracing_service_state.proto.
--help -h

light configuration flags: (only when NOT using -c/--config)
--time -t : Trace duration N[s,m,h] (default: 10s)
--buffer -b : Ring buffer size N[mb,gb] (default: 32mb)
--size -s : Max file size N[mb,gb] (default: in-memory ring-buffer only)
ATRACE_CAT : Record ATRACE_CAT (e.g. wm)
FTRACE_GROUP/FTRACE_NAME : Record ftrace event (e.g. sched/sched_switch)
FTRACE_GROUP/* : Record all events in group (e.g. sched/*)

statsd-specific flags:
--alert-id : ID of the alert that triggered this trace.
--config-id : ID of the triggering config.
--config-uid : UID of app which registered the config.
--subscription-id : ID of the subscription that triggered this trace.

Detach mode. DISCOURAGED, read https://docs.perfetto.dev/#/detached-mode :
--detach=key : Detach from the tracing session with the given key.
--attach=key [--stop] : Re-attach to the session (optionally stop tracing once reattached).
--is_detached=key : Check if the session can be re-attached (0:Yes, 2:No, 1:Error).

⚡ 注解2

Heap_Profile 脚本的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/home/kuroha/Documents/heap_profile -h

usage: heap_profile [-h] [-i INTERVAL] [-d DURATION] [--no-start] [-p PIDS] [-n NAMES] [-c CONTINUOUS_DUMP]
[--heaps HEAPS] [--all-heaps] [--no-android-tree-symbolization] [--disable-selinux]
[--no-versions] [--no-running] [--no-startup] [--shmem-size SHMEM_SIZE] [--block-client]
[--block-client-timeout BLOCK_CLIENT_TIMEOUT] [--no-block-client] [--idle-allocations]
[--dump-at-max] [--disable-fork-teardown] [--simpleperf] [--traceconv-binary TRACECONV_BINARY]
[--no-annotations] [--print-config] [-o DIRECTORY]

Collect a heap profile

The PERFETTO_PROGUARD_MAP=packagename=map_filename.txt[:packagename=map_filename.txt...] environment variable can be used to pass proguard deobfuscation maps for different packages

options:
-h, --help show this help message and exit
-i, --interval Sampling interval. Default 4096 (4KiB)
-d, --duration Duration of profile (ms). 0 to run until interrupted. Default: until interrupted by user.
--no-start Do not start heapprofd.
-p, --pid Comma-separated list of PIDs to profile.
-n, --name Comma-separated list of process names to profile.
-c, --continuous-dump Dump interval in ms. 0 to disable continuous dump.
--heaps Comma-separated list of heaps to collect, e.g: libc.malloc,com.android.art. Requires Android 12.
--all-heaps Collect allocations from all heaps registered by target.
--no-android-tree-symbolization Do not symbolize using currently lunched target in the Android tree.
--disable-selinux Disable SELinux enforcement for duration of profile.
--no-versions Do not get version information about APKs.
--no-running Do not target already running processes. Requires Android 11.
--no-startup Do not target processes that start during the profile. Requires Android 11.
--shmem-size Size of buffer between client and heapprofd. Default 8MiB. Needs to be a power of two multiple of 4096, at least 8192.
--block-client When buffer is full, block the client to wait for buffer space. Use with caution as this can significantly slow down the client. This is the default
--block-client-timeout If --block-client is given, do not block any allocation for longer than this timeout (us).
--no-block-client When buffer is full, stop the profile early.
--idle-allocations Keep track of how many bytes were unused since the last dump, per callstack
--dump-at-max Dump the maximum memory usage rather than at the time of the dump.
--disable-fork-teardown Do not tear down client in forks. This can be useful for programs that use vfork. Android 11+ only.
--simpleperf Get simpleperf profile of heapprofd. This is only for heapprofd development.
--traceconv-binary Path to local trace to text. For debugging.
--no-annotations Do not suffix the pprof function names with Android ART mode annotations such as [jit].
--print-config Print config instead of running. For debugging.
-o, --output Output directory.

🔥 参考链接

download heap_profile : https://raw.githubusercontent.com/google/perfetto/main/tools/heap_profile

download record_android_trace : https://raw.githubusercontent.com/google/perfetto/master/tools/record_android_trace

使用 Perfetto 调试内存 其中的 "Analyzing the Native Heap" 段

关于 Heap profiler 的更多信息

Perfetto Trace 的抓取

🔥 如何判断安卓设备架构

adb shell getprop ro.product.cpu.abi

这将输出设备的主 CPU 架构类型

arm64-v8a : 表示 64-bit ARM 架构, 通常是 arm64

armeabi-v7a : 表示 32-bit ARM 架构, 通常是 armv7

x86_64 : 表示 64-bit x86 架构

x86 : 表示 32-bit x86 架构

🔥 命令示例

1
2
3
4
5
PERFETTO_SYMBOLIZER_MODE="index" PERFETTO_BINARY_PATH="/home/kuroha/Documents/symbols_sword/" Documents/heap_profile -n com.Kuroha.SwordRequiem -c 5000 -d 60000

PERFETTO_SYMBOLIZER_MODE="index" PERFETTO_BINARY_PATH="/home/kuroha/Documents/symbols_test/" Documents/heap_profile -n com.DefaultCompany.Perfetto -c 5000 -d 60000

PERFETTO_SYMBOLIZER_MODE="index" PERFETTO_BINARY_PATH="/home/kuroha/Documents/symbols_funny/" Documents/heap_profile -n com.sofunny.Sausage -c 5000 -d 60000

🌴 Perfetto SQL

stack_profile_mapping

stack_profile_mapping 表存储调用堆栈的进程地址空间映射信息 (a mapping (binary / library) in a process), 是由堆栈分析器 heapprofd 和 traced_perf 生成的

1
select id, build_id, exact_offset, start_offset, start, end, load_bias, name from stack_profile_mapping
  • id: 唯一标识符
  • build_id: 符号表文件 *.so 的 Build ID
  • start: 映射在进程地址空间中的开始位置
  • end: 映射在进程地址空间中的结束位置
  • name: 符号表文件 *.so 的名字

stack_profile_frame

stack_profile_frame 表存储的是调用堆栈的帧信息 (a frame on the callstack), 也是由堆栈分析器 heapprofd 和 traced_perf 生成的

1
select id, name, mapping, rel_pc, symbol_set_id, deobfuscated_name from stack_profile_frame
  • id: 唯一标识符
  • name: 此位置所在的函数的名称
  • mapping: 此位置所在的映射
  • rel_pc: 相对于映射开始的程序计数器
  • symbol_set_id: 此帧的离线符号信息
  • deobfuscated_name: 此位置所在的函数的去混淆名称

stack_profile_callsite

stack_profile_callsite 表存储的是堆栈上的帧列表 (a callsite, callsite is a list of frames that were on the stack), 也是由堆栈分析器 heapprofd 和 traced_perf 生成的

1
select id, depth, parent_id, frame_id from stack_profile_callsite

heap_profile_allocation

heap_profile_allocation 表存储的是一个 callsite 上发生的分配, 这是由 heapprofd 生成的

1
select id, ts, upid, heap_name, callsite_id, count, size from heap_profile_allocation

stack_profile_symbol

stack_profile_symbol 表存储帧的符号化数据

1
select id, symbol_set_id, name, source_file, line_number from stack_profile_symbol

示例 SQL

1
2
3
4
5
6
select heap.callsite_id, frame.name, frame.symbol_set_id, mapping.name as mapping_name
from heap_profile_allocation heap
join stack_profile_callsite callsite on (heap.callsite_id = callsite.id)
join stack_profile_frame frame on (callsite.frame_id = frame.id)
join stack_profile_mapping mapping on (frame.mapping = mapping.id)
where