性能优化工具-perf实现火焰图

安装依赖

确保已安装 perf 和 FlameGraph 工具。

FlameGraph 通过以下命令下载

git clone https://github.com/brendangregg/FlameGraph.git

原理

相关格式

调用栈文本格式

使用 sudo perf script > out.perf 生成的格式如下所示


perf-exec 376271 140211.225788: 1 cycles:P:
ffffffff8b268a13 perf_lock_task_context+0x3 ([kernel.kallsyms])
ffffffff8b271ee9 perf_event_exec+0x1a9 ([kernel.kallsyms])
ffffffff8b330c60 begin_new_exec+0x5c0 ([kernel.kallsyms])
ffffffff8b39b2df load_elf_binary+0x2bf ([kernel.kallsyms])
ffffffff8b32fa7e bprm_execve+0x23e ([kernel.kallsyms])
ffffffff8b3302ed do_execveat_common.isra.0+0x1ad ([kernel.kallsyms])
ffffffff8b3311c3 __x64_sys_execve+0x33 ([kernel.kallsyms])
ffffffff8bf22e55 do_syscall_64+0x35 ([kernel.kallsyms])
ffffffff8c000124 entry_SYSCALL_64_after_hwframe+0x6c ([kernel.kallsyms])
7f76332aef3b [unknown] ([unknown])

perf-exec 376271 140211.225798: 1 cycles:P:
ffffffff8b268a13 perf_lock_task_context+0x3 ([kernel.kallsyms])
ffffffff8b271ee9 perf_event_exec+0x1a9 ([kernel.kallsyms])
ffffffff8b330c60 begin_new_exec+0x5c0 ([kernel.kallsyms])
ffffffff8b39b2df load_elf_binary+0x2bf ([kernel.kallsyms])
ffffffff8b32fa7e bprm_execve+0x23e ([kernel.kallsyms])
ffffffff8b3302ed do_execveat_common.isra.0+0x1ad ([kernel.kallsyms])
ffffffff8b3311c3 __x64_sys_execve+0x33 ([kernel.kallsyms])
ffffffff8bf22e55 do_syscall_64+0x35 ([kernel.kallsyms])
ffffffff8c000124 entry_SYSCALL_64_after_hwframe+0x6c ([kernel.kallsyms])
7f76332aef3b [unknown] ([unknown])

文件包含多个块

每个块第一行表示采样执行的函数名,事件,计数,事件

第二行到块末尾表示的是调用栈,里面包含函数的名字,地址,是否内核函数

调用栈折叠文本格式

将采样数据中的调用栈折叠为一行一行的格式,每一行表示一个调用栈及其采样次数。使用 stackcollapse-perf.pl 脚本完成这一步骤:

./FlameGraph/stackcollapse-perf.pl out.perf > out.folded

折叠后的文件格式如下:

perf-exec;[unknown];entry_SYSCALL_64_after_hwframe;do_syscall_64;__x64_sys_execve;do_execveat_common.isra.0;bprm_execve;load_elf_binary;begin_new_exec;perf_event_exec;perf_lock_task_context 339
sleep;[unknown];entry_SYSCALL_64_after_hwframe;do_syscall_64;__x64_sys_execve;do_execveat_common.isra.0;bprm_execve;load_elf_binary;setup_arg_pages;shift_arg_pages;__vma_adjust 46635
  • 每一行表示一个调用栈。
  • 调用栈中的函数用分号 ; 分隔。
  • 最后一个数字表示该调用栈的采样次数。

调用栈回溯

默认情况下,perf 使用 帧指针(Frame Pointer, FP) 进行调用栈回溯(Call-Graph)。帧指针是一种简单且高效的调用栈回溯方法,但它依赖于程序编译时保留帧指针信息。如果程序没有保留帧指针(例如使用了 -fomit-frame-pointer 编译选项),perf 可能无法正确获取调用栈。

为了应对这种情况,perf 提供了多种调用栈回溯方法,可以通过 --call-graph 参数进行配置。以下是常见的调用栈回溯方法及其配置方式:

帧指针(Frame Pointer, FP)

  • 原理:通过帧指针寄存器(如 x86 架构的 RBP)遍历调用栈。

  • 优点:简单、高效。

  • 缺点:依赖程序编译时保留帧指针信息,未使用 fomit-frame-pointer

  • 配置

    perf record --call-graph fp -p <PID> -- sleep 10

调试信息(DWARF)

  • 原理:通过调试信息(如 .eh_frame 或 .debug_frame 段)进行调用栈回溯。(编译时使用-g参数)

  • 优点:不依赖帧指针,适用于没有帧指针的程序。

  • 缺点:性能开销较大,生成的采样数据较多。

  • 配置

    perf record --call-graph dwarf -p <PID> -- sleep 10

最后分支记录(Last Branch Record, LBR)

  • 原理:利用 CPU 的 LBR 功能记录最近的函数调用路径。

  • 优点:高效、精确。

  • 缺点:仅支持部分 CPU 架构(如 Intel x86)。

  • 配置

    perf record --call-graph lbr -p <PID> -- sleep 10

选择哪种方式

  • 当程序保留栈指针的时候,可以用栈指针进行回溯
  • 当编译时没有保留栈指针fomit-frame-pointer,则可以选择DWARF和LBR进行回溯
  • 当调用栈层级发生了截断,可以用形如—call-graph fp,1024的方式指定深度

数据采集

使用 perf record 采集性能数据

sudo perf record -F 99 -g -p <PID> -- sleep 60
  • F 99:采样频率为 99 Hz。
  • g:记录调用栈。
  • p <PID>:指定进程 ID。
  • - sleep 60:采集 60 秒。

生成报告

使用 perf script 生成报告。

sudo perf script > out.perf

生成火焰图

使用 FlameGraph 工具生成火焰图。

./FlameGraph/stackcollapse-perf.pl out.perf > out.folded
./FlameGraph/flamegraph.pl out.folded > flamegraph.svg
作者

deepwzh

发布于

2024-10-10

更新于

2025-03-17

许可协议

评论