Zephyr性能分析工具 Perf

概述 链接到标题

Zephyr 项目中实现了Perf用于性能分析的工具,通过栈回溯实现轻量级的性能分析。该工具配置简单,使用方便,适合对代码性能进行快速分析和优化。

工作原理 链接到标题

Perf 工具的工作原理如下:

  • 使用 perf record 命令启动定时器,定时器通过中断触发 Perf 跟踪函数
  • Zephyr 内核在调用中断处理程序之前,会将返回地址和帧指针保存在中断栈或 callee_saved 结构中
  • Perf 跟踪函数利用这些返回地址和帧指针生成栈回溯
  • 可通过 scripts/profiling/stackcollapse.py 脚本将栈回溯中的返回地址转换为函数名,并以 FlameGraph 所需格式输出

配置选项 链接到标题

主要配置项 链接到标题

  • CONFIG_PROFILING: 启用 PROFILING
  • CONFIG_PROFILING_PERF:启用该模块,并在 shell 中添加 perf 命令
  • CONFIG_PROFILING_PERF_BUFFER_SIZE:设置 Perf 缓冲区大小,用于存储采样数据,不配置时默认是2048,该值比较小,需要根据要perf的数据量进行修改

后端配置(根据架构自动选择) 链接到标题

  • CONFIG_PROFILING_PERF_BACKEND_RISCV
  • CONFIG_PROFILING_PERF_BACKEND_X86
  • CONFIG_PROFILING_PERF_BACKEND_X86_64

使用方法 链接到标题

配置步骤 链接到标题

在项目配置文件中添加以下配置:

CONFIG_PROFILING=y
CONFIG_PROFILING_PERF=y
CONFIG_FRAME_POINTER=y
CONFIG_THREAD_STACK_INFO=y
CONFIG_PROFILING_PERF_BUFFER_SIZE=10240
CONFIG_SHELL=y

注意:由于 Zephyr 的 Perf 被集成到 shell 中,因此需要开启 shell。

数据采集 链接到标题

编译完成后,在 shell 中执行 perf 命令进行数据采集:

perf record <duration> <frequency>

参数说明:

  • <duration>:数据收集的持续时间(毫秒),该时间要覆盖你要分析的性能问题的时间点。
  • <frequency>:采样频率(Hz),频率约高约精确

等待完成消息 perf done!,如果 perf 缓冲区大小小于所需大小,则会出现 perf buf override!

需要注意的是duration/frequency越大,perf buffer就需要越大,另外也受到调用堆栈深度的影响。

数据处理 链接到标题

  1. 打印数据:收集完成后,使用以下命令打印 Perf 缓冲区中的数据:
perf printbuf

输出示例:

Perf buf length 6191
0000000000000002
0000000042018a22
0000000042018cb2
0000000000000002
....
  1. 保存数据:将输出内容复制下来,以文本格式保存(例如文件名为 perf_data

  2. 下载火焰图工具

git clone https://github.com/brendangregg/FlameGraph.git
  1. 生成火焰图
python zephyr/scripts/profiling/stackcollapse.py perf_data build/zephyr/zephyr.elf | ~/workspace/tools/FlameGraph/flamegraph.pl > graph2.svg

perf.png 这个演示结果可以看到采样期间sys_clock_cycle_get_32调用的次数最多,而其呼叫的两个函数占用次数相当。如果这段出现了性能问题,可能是这两个函数的执行时间过长。

实现原理 链接到标题

核心机制 链接到标题

Perf利用中断机制对执行过程进行“采样”,在采样时刻捕获返回ra和sp,和当时的栈回溯信息。使用脚本将其转换为函数名,以通过火焰图将其以可视化形式展示。 perf_arch.png

采样起始时间(end-start = duration),决定了要Perf的内容,例如你发现在特定时间点出现性能问题,可以将起始时间放在出性能问题的时间点附近。采样频率决定了你收集数据的完整性,频率越高,数据越完整。从上图可以看到频率不够时会漏采样线程A中FA1()调用FB1()。因此Perf是统计学意义上的性能分析,并不是精确的性能分析。

代码结构 链接到标题

zephyr/subsys/profiling/perf/perf.c 提供了框架代码,以 shell 命令的形式进行调用:

  • cmd_perf_record

    • 按照 frequency 创建 k_timer,周期性地进入中断
    • 在中断中执行 perf_tracer,perf_tracer 调用 arch_perf_current_stack_trace 获取当前线程的 backtrace,并存放到 perf_data.buf 内
    • perf_tracer 发现 perf_data.buf 满后停止 timer
    • 按照 duration 创建 k_work,k_work 到期后调用 perf_dwork_handler 停止 timer
  • cmd_perf_clear

    • 抓取数据过程中不能清除,perf 结束后将 perf_data.buf 清空,perf_data.idx = 0
  • cmd_perf_info

    • 显示 perf_data.buf 的信息:总计多少,已经有多少数据
  • cmd_perf_print

    • 将记录的数据打印出来,调用序列是依次打印fp地址,每个调用序列之间用0000000000000002分隔

数据转换 链接到标题

zephyr/scripts/profiling/stackcollapse.py 脚本将 perf 记录的fp数据从elf查找出符号并转换为 FlameGraph 所需格式。 火焰图脚本flamegraph.pl将文本格式转换成svg,通过svg可以可视化展示调用序列和其频繁度。

架构相关实现 链接到标题

不同的架构 arch_perf_current_stack_trace 实现会不同,都集中在 zephyr/subsys/profiling/perf/backends 下:

  • perf_riscv.c
  • perf_x86_64.c
  • perf_x86.c

参考 链接到标题

https://docs.zephyrproject.org/latest/services/profiling/perf.html https://lgl88911.github.io/2020/03/19/Perf%E5%92%8C%E7%81%AB%E7%84%B0%E5%9B%BE/