heaptrack - Linux系统的堆内存分析器
Heaptrack跟踪所有内存分配,并用堆栈跟踪注释这些事件。 专用分析工具随后允许您解释堆内存配置文件,以便:
- 找到需要优化以减少应用程序内存占用的热点
- 找到内存泄漏,即分配后从未释放的内存位置
- 找到分配热点,即触发大量内存分配调用的代码位置
- 找到临时分配,即分配后立即释放的内存
使用heaptrack
推荐的方法是启动应用程序并从一开始就开始跟踪:
heaptrack <你的应用程序及其参数>
heaptrack输出将写入"/tmp/heaptrack.APP.PID.gz"
正在启动应用程序,这可能需要一些时间...
...
heaptrack统计:
分配: 65
泄漏分配: 60
临时分配: 1
Heaptrack完成!现在运行以下命令来调查数据:
heaptrack --analyze "/tmp/heaptrack.APP.PID.gz"
或者,您可以附加到已经运行的进程:
heaptrack --pid $(pidof <你的应用程序>)
heaptrack输出将写入"/tmp/heaptrack.APP.PID.gz"
通过GDB将heaptrack注入应用程序,这可能需要一些时间...
注入完成
...
Heaptrack完成!现在运行以下命令来调查数据:
heaptrack --analyze "/tmp/heaptrack.APP.PID.gz"
嵌入式机器上的分析
当您试图分析一个没有直接访问调试符号的系统时,这在嵌入式系统上很常见,上述步骤不会给您有用的数据。在这种情况下,您首先需要记录原始跟踪文件,然后在有权访问所需调试符号的SDK或sysroot的系统上解释它。
在嵌入式系统上记录原始跟踪文件:
heaptrack --raw <你的应用程序及其参数>
然后将原始跟踪文件下载到您的开发机器并解释数据:
heaptrack --interpret "/path/heaptrack.test_c.8911.raw.zst" --sysroot "/path/to/sysroot"
然后,您可以分析它:
heaptrack --analyze "/tmp/heaptrack.test_c.8911.zst"
如果您在sysroot之外有其他包含调试信息的文件夹,可以通过--debug-paths
传递给heaptrack --interpret
,另请参阅:https://sourceware.org/gdb/current/onlinedocs/gdb.html/Separate-Debug-Files.html
此外,如果您有自定义代码侧载并希望从相应的构建文件夹加载调试信息,请使用--extra-paths
,例如:
heaptrack --interpret "/path/heaptrack.test_c.8911.raw.zst" --sysroot "/path/to/sysroot"
构建heaptrack
Heaptrack分为两部分:数据收集器,即heaptrack
本身,以及名为heaptrack_gui
的分析器GUI。以下总结了这两部分的依赖关系,因为它们可以独立构建。您可以在所有主要发行版上找到这些依赖项的相应开发包。
在嵌入式设备或较旧的Linux发行版上,您只需要构建heaptrack
。然后可以在具有更现代Linux发行版的不同机器上分析数据,该机器可以访问所需的GUI依赖项。
如果您在构建、部署或使用heaptrack时需要帮助,可以联系KDAB获得商业支持:https://www.kdab.com/software-services/workshops/profiling-workshops/
共享依赖项
两个部分都需要以下工具和库:
- cmake 2.8.9或更高版本
- 支持C++11的编译器,如g++或clang++
- zlib
- 可选:zstd用于更快的(解)压缩
- elfutils
- libdl
- pthread
- libc
heaptrack
依赖项
heaptrack数据收集器和简单的heaptrack_print
分析器依赖于以下库:
- boost 1.41或更高版本:iostreams, program_options
- libunwind
对于运行时附加,您需要安装gdb
。
heaptrack_gui
依赖项
用于解释和分析heaptrack收集的数据的图形用户界面依赖于Qt 5和一些KDE库:
- extra-cmake-modules
- Qt 5.2或更高版本:Core, Widgets
- KDE Frameworks 5:CoreAddons, I18n, ItemModels, ThreadWeaver, ConfigWidgets, KIO, IconThemes
当缺少任何这些依赖项时,heaptrack_gui
将不会被构建。
可选地,安装以下依赖项以在GUI中获得额外功能:
- KDiagram:KChart(用于图表可视化)
编译
运行以下命令来编译heaptrack。请注意CMake命令的输出,因为它会告诉您缺少的依赖项!
cd heaptrack # 即源文件夹
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release .. # 查看有关缺少依赖项的消息!
make -j$(nproc)
在macOS上使用homebrew编译heaptrack_gui
可以使用上述依赖项在Linux以外的平台上构建heaptrack_print
和heaptrack_gui
。
在macOS上,可以使用homebrew和KDE homebrew tap轻松安装依赖项。
brew install qt@5
# 准备tap
brew tap kde-mac/kde https://invent.kde.org/packaging/homebrew-kde.git
"$(brew --repo kde-mac/kde)/tools/do-caveats.sh"
# 安装依赖项
brew install kde-mac/kde/kf5-kcoreaddons kde-mac/kde/kf5-kitemmodels kde-mac/kde/kf5-kconfigwidgets \
kde-mac/kde/kf5-kio kde-mac/kde/kdiagram \
extra-cmake-modules ki18n threadweaver \
boost zstd gettext
# 运行brew打印的手动步骤
ln -sfv "$(brew --prefix)/share/kf5" "$HOME/Library/Application Support"
ln -sfv "$(brew --prefix)/share/knotifications5" "$HOME/Library/Application Support"
ln -sfv "$(brew --prefix)/share/kservices5" "$HOME/Library/Application Support"
ln -sfv "$(brew --prefix)/share/kservicetypes5" "$HOME/Library/Application Support"
要编译,请确保使用homebrew的Qt并将gettext添加到路径中:
cd heaptrack # 即源文件夹
mkdir build
cd build
CMAKE_PREFIX_PATH=/opt/homebrew/opt/qt@5 PATH=$PATH:/opt/homebrew/opt/gettext/bin cmake ..
cmake -DCMAKE_BUILD_TYPE=Release .. # 查看有关缺少依赖项的消息!
make heaptrack_gui heaptrack_print
解释堆配置文件
Heaptrack生成的数据文件对人类来说是不可能分析的。相反,您需要使用heaptrack_print
或heaptrack_gui
来解释结果。
heaptrack_gui
分析堆配置文件的强烈推荐方法是使用heaptrack_gui
工具。
它依赖于Qt 5和KF 5来图形化可视化记录的数据。它具有以下特点:
- 数据摘要页面
- 分配内存的代码位置的自下而上和自上而下的树视图, 包括它们的聚合成本和堆栈跟踪
- 火焰图可视化
- 随时间变化的分配成本图表
heaptrack_print
heaptrack_print
工具是一个具有最小依赖项的命令行应用程序。它获取
堆配置文件,对其进行分析,并以ASCII格式将结果打印到命令行。
最简单的形式,您可以这样使用它:
heaptrack_print heaptrack.APP.PID.gz | less
默认情况下,报告将包含三个部分:
对分配函数的最多调用
峰值内存消耗者
最多临时分配
然后每个部分列出前十个热点,即触发例如 最多内存分配的代码位置。
查看heaptrack_print --help
以更改输出格式和其他选项。
请注意,您可以使用此工具将heaptrack数据文件转换为Massif数据格式。
您可以生成折叠的堆栈报告以供flamegraph.pl
使用。
与Valgrind的massif的比较
构建 heaptrack 的想法源于使用 Valgrind 的 massif 时遇到的困难。Valgrind 在内存和时间方面都带来了巨大的开销,有时会阻止你在较大的实际应用程序上运行它。Valgrind 所做的大部分工作对于一个简单的堆分析器来说是不必要的。
heaptrack 相比 massif 的优势
-
速度和内存开销
使用 heaptrack 跟踪多线程应用程序时,它们不会被序列化,即使对于单线程应用程序,时间和内存的开销也显著降低。最值得注意的是,你只在分配内存时付出代价——与 Valgrind 不同,耗时的 CPU 计算完全不会被减慢。
-
更多数据
Valgrind 的 massif 在写入报告之前会聚合数据。这一步骤会丢失大量有用信息。最显著的是,你无法再知道内存被分配的频率,或者临时分配是在哪里触发的。Heaptrack 在你解释数据之前不会聚合数据,这允许对你的分配模式有更有用的洞察。
massif 相比 heaptrack 的优势
-
能够将页面分配作为堆进行分析
这允许你对使用绕过 malloc 及其相关函数的池分配器的应用程序进行堆分析。原则上,heaptrack 也可以分析这类应用程序,但需要修改代码以注释内存池实现。
-
能够分析栈分配
据我所知,这在 heaptrack 中本质上无法高效实现。
Heaptrack 与 Rust
总的来说,Heaptrack 基本上可以开箱即用地处理 Rust 二进制文件,因为 Rust 程序包含相应的调试符号。 虽然还不支持符号解码,但大多数符号名称仍然可以辨认。
还有一些其他细节需要注意。
启用调试符号
如果在发布模式下构建,请确保启用了调试符号。
在你的 Cargo.toml 中添加以下内容:
[profile.release]
debug = true
⚠️ 注意,如果你的项目是工作空间的一部分,这必须添加到工作空间的 Cargo.toml 中,而不是单个 crate 中。
在 Rust 二进制文件上运行 Heaptrack
运行 heaptrack cargo run
不会按预期工作,因为这会分析 Cargo 的内存使用情况,而不是你的应用程序。
相反,你可以:
- 使用
cargo build --release
正常编译你的二进制文件,然后在target/release/
目录中的结果二进制文件上运行 heaptrack。- 例如:
heaptrack ./target/release/my-rust-app
- 例如:
- 安装 cargo-heaptrack(非官方)并运行
cargo heaptrack
。- ⚠️ 在打开 GUI 之前,确保按照命令行输出提示运行分析命令
通过 Heaptrack 打开 Rust 源文件
在 Heaptrack 的 GUI 中,在调用者/被调用者图下,你可以双击一个位置来在那里打开编辑器。
文件路径是相对的,所以确保在你的项目根目录打开 heaptrack GUI,以便这能正确工作。
⚠️ 如果你的 crate 是工作空间的一部分,你需要在工作空间的根目录打开 heaptrack,以使路径正确。
为 heaptrack 做贡献
作为一个自由开源软件项目,我们欢迎任何形式的贡献。你可以通过以下方式帮助改进项目:
- 在 https://bugs.kde.org/enter_bug.cgi?product=Heaptrack 提交错误报告
- 通过 https://invent.kde.org/sdk/heaptrack 贡献补丁
- 在 https://l10n.kde.org/ 的帮助下翻译 GUI
- 在 https://userbase.kde.org/Heaptrack 上编写文档
提交错误报告时,你可以使用 tools/anonymize
脚本匿名化你的数据:
tools/anonymize heaptrack.APP.PID.gz heaptrack.bug_report_data.gz
已知的错误和限制
旧版 gold 链接器的问题
当从使用旧版 gold 链接器链接的代码中展开时,Libunwind 可能会产生虚假的回溯。 在这种情况下,使用 heaptrack 记录似乎可以正常工作并产生数据文件。但是使用 heaptrack_gui 解析这些数据文件常常会导致内存耗尽崩溃。使用 heaptrack_print 查看数据时,会看到完全损坏的垃圾回溯。
如果你遇到这样的问题,请尝试使用 ld.bfd
而不是 ld.gold
重新链接你的应用程序和 libunwind。
你可以通过运行 libunwind 单元测试(使用 make check
)来查看是否受到影响。但请注意,你需要重新链接你的应用程序,而不仅仅是 libunwind。
使用 ASAN(地址sanitizer)构建的可执行文件
如果你在使用 ASAN 构建的应用程序上运行 heaptrack,你可能会在启动时遇到这个致命错误:
ASan runtime does not come first in initial library list [...]
解决方法是向 heaptrack 传递 --asan
标志。
注意:这只适用于用 gcc
构建的二进制文件(即那些链接到 libasan.so
的文件)。
目前不支持使用 clang
的 ASAN 构建的二进制文件。