- 嵌入式
- 物联网
- 机器人
- 自动驾驶
- 智能硬件
【免费下载链接】PX4-Autopilot
PX4 Autopilot Software
本文介绍 PX4-Autopilot 内置的Poor Man's Sampling Profiler(PMSP)采样剖析方案:它通过周期性地中断固件执行并采样当前调用栈,经"折叠(folding)"与 FlameGraph 的采样—折叠—绘图三级流水线原理。
方法概述(Approach)
PMSP 的本质是一个shell 脚本,它实现了一种经典的低成本采样剖析方法(该方法最初由 Mark Callaghan 与 Domas Mituzas 提出,即"poor man's profiler")。其工作流程分为三个阶段:
- 采样(Sampling):脚本周期性地通过 GDB 中断(halt)目标固件的执行,抓取当前时刻的调用栈(stack trace),并把每次抓到的栈信息追加写入一个文本文件。由于采样是"随机打点",采样次数越多、采样间隔越均匀,统计结果就越接近真实的 CPU 时间分布。
- 折叠(Folding):采样结束后(通常需要一个小时甚至更久),脚本把收集到的所有调用栈"折叠"成另一份文本文件——所有相同的调用栈路径被合并,并记录其出现次数。折叠结果等价于"每个调用路径被采样命中多少次",这正是火焰图绘制的输入数据。
- 可视化(Graphing):折叠后的栈数据被送入FlameGraph(Brendan Gregg 开源的调用栈可视化工具)生成交互式 SVG 火焰图。
整个三级流水线的入口是仓库中的 poor-mans-profiler.sh,它位于platforms/nuttx/Debug/目录下,与Nuttx.py、gdbinit.in等调试辅助文件同处一个目录。
基本使用(Basic Usage)
前置条件(Prerequisites)
由于剖析依赖 GDB 在嵌入式目标上运行 PX4,开始之前需要准备齐以下三样东西:
- 待剖析的硬件:例如一块 Pixhawk 4 / Pixhawk 6X 等飞控板;
- 已编译并烧录的固件:先在目标硬件上编译并上传 PX4 固件(见 boards 下各板卡目录的构建配置);
- 调试探针(debug probe):用于运行 GDB server 并与板卡交互。官方文档推荐 Zubax BugFace BF1 这类基于 Black Magic Probe(BMP)的调试器。更多探针选择与接线说明可参考 SWD 调试文档。
确定调试器设备号(Determine the Debugger Device)
当使用Zubax BugFace BF1(Black Magic Probe)时,poor-mans-profiler.sh 会自动检测并使用正确的 USB 设备。从脚本源码(第 59 行)可以看到其默认逻辑:
gdbdev=`ls /dev/serial/by-id/*Black_Magic_Probe*-if00`即脚本自动枚举/dev/serial/by-id/下匹配*Black_Magic_Probe*-if00的设备。如果使用其他类型的探针,则需要通过--gdbdev参数显式指定调试器所在的设备节点。
在 Ubuntu 上,可用如下命令枚举可能的设备:
ls -alh /dev/serial/by-id/下面示例展示了同时连接 Pixhawk 4 与 Zubax BugFace BF1 时的枚举结果:
user@ubuntu:~/PX4-Autopilot$ ls -alh /dev/serial/by-id/ total 0 drwxr-xr-x 2 root root 100 Apr 23 18:57 . drwxr-xr-x 4 root root 80 Apr 23 18:48 .. lrwxrwxrwx 1 root root 13 Apr 23 18:48 usb-3D_Robotics_PX4_FMU_v5.x_0-if00 -> ../../ttyACM0 lrwxrwxrwx 1 root root 13 Apr 23 18:57 usb-Black_Sphere_Technologies_Black_Magic_Probe_BFCCB401-if00 -> ../../ttyACM1 lrwxrwxrwx 1 root root 13 Apr 23 18:57 usb-Black_Sphere_Technologies_Black_Magic_Probe_BFCCB401-if02 -> ../../ttyACM2在此例中,脚本会自动选中名为*Black_Magic_Probe*-if00的设备(即ttyACM1)。若你使用的是其他调试器,可从上面的列表中找出对应的 id,并通过--gdbdev传入:
./poor-mans-profiler.sh --elf=build/px4_fmu-v4_default/px4_fmu-v4_default.elf --nsamples=30000 --gdbdev=/dev/ttyACM2一键运行(Running)
PMSP 最便捷的入口是 PX4 的构建系统。下面这条命令会构建px4_fmu-v4pro目标并执行 10000 次采样(脚本会自动获取FlameGraph并加入PATH,如需自定义采样次数等更细粒度控制,见下文"实现原理(Implementation)"一节):
make px4_fmu-v4pro_default profile脚本源码头部注释也给出了完整的使用路径(以 Pixhawk 4 / fmu-v5 为例):
# 0) 准备 flamegraph.pl(见下文 PATH 要求) # 1) 连接设备,例如 Pixhawk 4 # 2) 构建 PX4: make px4_fmu-v5_default # 3) 烧录 PX4: make px4_fmu-v5_default upload # 4) 连接 JTAG/SWD 调试器,例如 DroneCode Probe # 5) 执行剖析: make px4_fmu-v5_default profile理解输出结果(Understanding the Output)
采样与折叠完成后,PMSP 调用flamegraph.pl生成火焰图(FlameGraph)。下图是官方文档给出的输出示例(注意静态图片不具备交互性,实际产物为可交互的 SVG):
火焰图的阅读规则如下:
- 水平方向:每一层由多个矩形条组成,矩形的宽度与该函数被采样命中的次数成正比。命中次数越多,说明该函数在采样周期内的执行时间(时长 × 频率)占比越高,是潜在的 CPU 热点。
- 垂直方向:自下而上为调用栈的层次——底部是根函数(如任务入口、
px4_main、工作队列调度),顶部是叶子函数(被层层调用的具体实现)。栈越深,代表调用链越长。 - 颜色:通常用于区分不同函数或不同栈帧,颜色本身不携带数值语义(默认配色下红色系表示高频热区、黄色系为相对低频的深层调用,可结合具体图例判断)。
因此,最宽、最长的"平顶"区域就是你需要重点审视的热点函数——它往往对应某个繁忙的任务循环或高开销的系统调用。
已知问题与注意事项(Possible Issues)
PMSP 定位为"ad-hoc"(临时应急)方案,脚本本身存在一些已知缺陷,使用时应特别留意:
- GDB 故障可能不被察觉:如果 GDB 工作异常,脚本可能无法检测到并继续空跑,结果自然是毫无可用的栈数据。为避免浪费数小时,应周期性检查
/tmp/pmpn-gdberr.log——该文件保存了最近一次 GDB 调用的 stderr 输出。脚本源码(第 103 行)将 GDB 的错误输出重定向到该文件。未来版本应改为以安静模式调用 GDB 并通过退出码判断成败。 - GDB 偶发永久挂起:采样栈时 GDB 可能卡死,导致目标被无限期 halt。解决办法是手动中止脚本,再带
--append选项重新启动(追加而非覆盖旧数据)。未来版本应给每次 GDB 调用强制加上超时。 - 不支持多线程环境:脚本一次采样只记录单条栈。这对单核嵌入式目标没有影响(始终只有一个执行线程),但限制了它在多线程应用中的适用性。未来版本应让栈折叠器支持每次采样包含多条栈。
实现原理(Implementation)
脚本本体位于 platforms/nuttx/Debug/poor-mans-profiler.sh,其执行流程与全部可调参数都直接体现在源码中。
执行流水线
- 启动自检:脚本首先检查
flamegraph.pl是否位于PATH中(第 43 行),否则直接报错退出——FlameGraph 脚本必须存在于PATH,否则 PMSP 拒绝启动。 - 采样:按指定的采样次数与时间间隔执行采样,原始栈数据写入系统临时目录的文本文件。脚本默认的临时文件路径(第 100~103 行)为:
/tmp/pmpn-stacks.log:原始采样栈;/tmp/pmpn-folded.txt:折叠后的栈;/tmp/pmpn-flamegraph.svg:最终火焰图 SVG;/tmp/pmpn-gdberr.log:GDB 的 stderr 输出。
- 折叠:采样结束后自动调用栈折叠器,输出写入临时目录中相邻的折叠文件。折叠器是脚本内嵌生成的 Python 程序
/tmp/pmpn-folder.py(第 157 行起),它专门处理 C++ 类型——包括operator<、operator<<、operator>、operator->等运算符重载符号,以及括号/尖括号/引号内的函数签名解析,避免 C++ 模板与运算符导致栈解析错乱。折叠器还会向 stderr 输出统计信息:总栈帧数(Total stack frames)以及栈顶分布 Top 300 消费函数占比(Top consumers),可直接在终端看到热点排行。 - 绘图:折叠成功后调用
flamegraph.pl --fontsize=$fgfontsize --width=$fgwidth生成交互式 SVG并自动用浏览器打开。注意并非所有图片查看器都支持交互式 SVG,建议用 Web 浏览器打开生成的 SVG(脚本会优先尝试 firefox、google-chrome,其次才是 xdg-open)。
参数详解
脚本通过--key=value形式解析参数(第 61~93 行),完整参数如下表:
| 参数 | 默认值 | 作用 |
|---|---|---|
--nsamples=<n> | 0(跳过采样) | 采样次数。0表示仅基于已有栈文件重新生成折叠与火焰图,不访问目标 |
--sleeptime=<sec> | 0.1 | 两次采样之间的间隔秒数,决定总剖析时长 |
--taskname=<name> | 空(全局采样) | 指定 NuttX 任务名,仅采样该任务(依赖带 Python 支持的 GDB 与Nuttx.py) |
--elf=<file> | 无(必填) | 指向当前运行 ELF 的路径(相对仓库根目录),用于把内存地址映射为符号 |
--append | 关 | 追加到旧栈文件而非覆盖 |
--gdbdev=<dev> | 自动探测 BMP | GDB 连接的目标设备节点 |
--fgfontsize=<px> | 10 | 火焰图字体大小 |
--fgwidth=<px> | 1900 | 火焰图宽度 |
采样背后的 GDB 调用
PMSP 使用arm-none-eabi-gdb采集栈(当前仅支持该工具链,未来可能扩展)。每次采样执行一次批处理式 GDB 调用(第 120~128 行):
arm-none-eabi-gdb $elf --nx --quiet --batch \ -ex "set print asm-demangle on" \ -ex "target extended $gdbdev" \ -ex "monitor swdp_scan" \ -ex "attach 1" \ -ex bt \ 2> $gdberrfile \ | sed -n 's/\(#.*\)/\1/p' \ >> $stacksfile关键点解读:
--batch保证每次调用非交互、可脚本化;--nx跳过用户.gdbinit以免干扰;target extended $gdbdev+monitor swdp_scan+attach 1完成对目标板的 SWD 连接与挂接;bt(backtrace)抓取当前调用栈,输出经sed过滤出#...帧行后追加到栈文件;- 若指定
--taskname,则改用source $root/Nuttx.py加载 Nuttx.py,并通过show mybt $taskname获取指定任务的调用栈(此时要求 GDB 带有 Python 支持)。
由于每次采样都要走完整的 GDB attach/backtrace 流程,频繁调用 GDB 会明显干扰目标实时任务的正常运行——因此 PMSP 不适合对强实时性任务做精准剖析;脚本注释还建议:保证 PC 不超载、调试器所连 USB 主机控制器不拥塞,并允许当前用户设置负 nice 值,以获得更均匀的采样节奏。
ELF 与追加模式
为了让 GDB 能把内存地址映射为符号名,必须通过--elf=<file>传入当前运行在目标上的可执行文件路径(相对仓库根目录)。典型用法:
./poor-mans-profiler.sh --elf=build/px4_fmu-v4_default/px4_fmu-v4_default.elf --nsamples=30000注意:每次启动脚本都会覆盖旧栈数据。若希望保留旧栈并继续追加采样,使用--append:
./poor-mans-profiler.sh --elf=build/px4_fmu-v4_default/px4_fmu-v4_default.elf --nsamples=30000 --append不难推断:--append配合--nsamples=0时,脚本会跳过采样环节,只基于已有的/tmp/pmpn-stacks.log重新执行折叠与绘图——适合调整--fgwidth、--fgfontsize反复打磨火焰图而无需再次连线采样。
小结
PMSP 为 PX4 飞控固件提供了一条"零侵入、低成本"的性能剖析路径:只需一块目标板、一个 SWD 调试探针和一份已烧录的固件,即可通过make <target> profile或直接调用 poor-mans-profiler.sh 获得调用栈级别的热点分布。其"采样 → 折叠 → 火焰图"的流水线虽朴素,却与 FlameGraph 生态无缝衔接,特别适合在真实硬件上快速定位任务循环、工作队列与驱动路径中的 CPU 瓶颈;使用时请务必留意其对实时任务的干扰、GDB 偶发故障与多线程不兼容等已知限制,并善用/tmp/pmpn-gdberr.log与--append模式保证采样数据的可靠性。
- 嵌入式
- 物联网
- 机器人
- 自动驾驶
- 智能硬件
【免费下载链接】PX4-Autopilot
PX4 Autopilot Software
相关推荐
别再手动写摘要了:3步完成T5微调,长文本摘要自动生成实战
别再手动写摘要了:3步完成T5微调,长文本摘要自动生成实战 长文本手动摘要是苦差事。基于 Transformers Tutorials 项目,我们用 T5 模型
示例工程Avail Light Client轻节点性能剖析:使用火焰图定位瓶颈
Avail Light Client轻节点性能剖析:使用火焰图定位瓶颈 你是否遇到过Avail Light Client(轻节点)同步缓慢、CPU占用过高的问题
3分钟定位Node.js性能瓶颈:从CPU剖析到火焰图实战
3分钟定位Node.js性能瓶颈:从CPU剖析到火焰图实战 你是否曾遇到Node.js应用突然变慢却找不到原因?日志里没有错误,内存使用正常,但响应时间却越来越
文档教程后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考