Linux性能分析利器perf:从perf stat到火焰图与动态追踪
聊Linux性能分析绕不开perf。它是Linux内核自带的性能剖析工具从CPU热点定位、缓存失效分析到内核函数动态插桩、火焰图生成几乎覆盖了日常性能排查的所有主流场景。简单说perf就是一套“内核级探针采样器数据分析器”的组合不需要额外安装agent只要内核支持perf_events子系统拿来即用。这篇文章我把perf从安装、权限配置到stat、record、report、annotate这些高频命令再到火焰图、动态追踪这类进阶玩法按我平时排查问题的实际路径完整过一遍。适合刚接触性能调优的运维和开发也适合已经会用perf top、但还没把record/report链路跑通的进阶用户。实战中踩过的坑我会重点标出来这些在官方文档和网上教程里基本看不到。1. perf到底能解决什么问题1.1 perf的定位不只是“CPU占用率”工具很多人把perf理解成“看CPU占用”的工具这个理解太窄了。top、htop也能看CPU占用perf真正的价值在于回答三个问题程序的时间到底花在哪条指令上程序为什么频繁发生上下文切换用户态到内核态的调用路径上哪个环节成了瓶颈perf基于内核的perf_events子系统实现它既能做基于硬件的性能计数器采样也能做基于软件事件和tracepoint的追踪。硬件计数器这块尤其强大比如CPU缓存命中率、分支预测失败率、流水线停顿周期这些指标用普通系统监控工具根本看不到但perf可以直接从PMU里读出来。我用一个生活化的比喻top相当于看体检报告上的体重和体温perf则像是给你做一个全身CT加心电图能把问题定位到具体是哪一块肌肉在抽搐。比如你发现CPU使用率90%top只能告诉你进程CPU高perf能告诉你高在libc里的memcpy还是高在内核的page fault处理路径上差很远。1.2 典型场景什么时候应该掏perf我自己的使用习惯遇到下面几类问题会第一时间想到perf第一类是CPU飙高但找不到原因。业务进程多线程线程名又起得乱七八糟top看不出具体是哪个线程在空转用perf top -p按进程/线程抓热点函数直接浮出来。第二类是性能数据“正常”但用户体验差。比如请求RT抖动平均延迟不高但P99很高。这种情况多半和调度延迟、锁竞争、CPU迁移有关用perf sched timehist配合perf record -g追踪callchain能比较清楚地还原当时的时序。第三类是内核态开销异常。用户态CPU才占了30%但整机CPU已经100%剩下的哪去了大概率在内核。普通工具看不透内核perf可以采样内核调用栈直接定位到某个驱动、某个协议栈函数甚至某个锁。第四类是缓存和访存密集型应用的调优。数据库、搜索引擎这类对内存带宽敏感的场景用perf stat看cache-misses和stalled-cycles基本能判断是访存模式出了问题还是数据布局出了问题。2. 环境准备与第一次出手2.1 安装perf不同发行版的差异点perf工具在大多数发行版里不是默认安装的但装起来很省事。Debian/Ubuntu系列跑sudo apt install linux-tools-common linux-tools-generic linux-tools-$(uname -r)CentOS/RHEL/Rocky系列跑sudo yum install perf有个细节容易被忽略Ubuntu如果只装了linux-tools-common没装linux-tools-$(uname -r)执行perf会提示“WARNING: perf not found for kernel X”然后建议你装某个具体版本包。这个提示其实完全可以照做它指向的包名通常就是当前内核对应的tools版本。国产Linux发行版近两年我接触得也多了比如基于CentOS的、基于Debian的都有安装方式和上游基本一致要么yum install perf要么apt install linux-tools。但国产发行版的内核版本可能定制过偶尔会遇到perf工具版本和内核版本不匹配的情况报错一般是“Kernel address maps”或“module ... not found”之类。遇到这类问题别硬折腾工具本身先确认内核的debugfs和perf_event是否被默认关闭。编译安装也算一条路。内核源码里tools/perf目录拿下来直接make就能编出perf依赖libelf、libdw、slang这些。不过这属于少数派需求绝大多数服务器用发行版自带的包就够了。2.2 权限配置perf_event_paranoid是第一个坑权限配置几乎是perf新手的第一道坎。内核有一个参数叫kernel.perf_event_paranoid默认值很多发行版设的是2这个值下普通用户不能访问硬件性能计数器也不能做内核态采样。查当前值sysctl kernel.perf_event_paranoid临时改sudo sysctl -w kernel.perf_event_paranoid1永久生效写到/etc/sysctl.conf里。值设多少合适我建议是这样2默认安全值用户态的基本统计分析还能做但硬件计数器用不了1允许使用CPU硬件计数器但内核态采样受限0允许内核态采样perf probe也可以用了-1完全放开没有任何限制生产环境建议先设成1排查内核态问题时再临时降到0或-1排查完再改回来。你如果直接装完perf就report很容易遇到“perf_event_open failed: Permission denied”之类的报错第一反应就应该是查这个参数。2.3 第一眼定位热点perf top实战perf top是我最常用的“快速扫描”命令类似top的交互界面但它显示的是热点函数而不是进程。sudo perf top默认按CPU周期采样刷新实时显示热点函数。界面里第一列是开销占比第二列是符号名第三列是调用信息。按t可以切换排序字段按1展开或收起CPU核心视图。几个参数值得记一下# 只看某个进程 sudo perf top -p pid # 显示调用链能看到热点函数是被谁调上来的 sudo perf top -g # 指定采样事件比如看cache miss热点 sudo perf top -e cache-misses # 指定采样频率默认4000Hz低一点对系统影响更小 sudo perf top -F 999这里有个经验perf top -g开调用链后输出信息量暴涨但如果你对代码不熟反而容易懵。我个人的习惯是先不开-g直接看哪个函数占比最高然后去查这个函数属于什么模块再针对性地用record抓调用链。一步一步来比一次性把所有信息糊脸上效果好得多。2.4 perf list先看看你的机器支持什么perf支持的事件非常多取决于CPU型号、内核版本、芯片厂商。查看全部事件perf list这个命令输出很长可以按类别过滤比如只看硬件事件、软件事件、tracepoint事件perf list hw perf list sw perf list tracepointtracepoint是内核预设的静态插桩点数量巨大按子系统分类比如sched:*、block:*、kmem:*、syscalls:*。我在排查进程调度问题的时候最常用的是这组perf list sched:*看输出你会发现sched_switch、sched_wakeup这些事件都能直接作为采样源。这比单纯采CPU周期更适合回答“线程为什么卡住”这类问题。每次拿到一台新机器我会扫一眼perf list心里大概有个数知道哪些事件可用到真正排障时就不用手忙脚乱。3. 三条主命令stat、record、report彻底吃透3.1 perf stat一条命令看穿硬件计数器perf stat适合对“一次运行”做全量统计用法简单perf stat command # 也可以针对运行中的进程做一段时间统计 sudo perf stat -p pid -- sleep 10输出长这样Performance counter stats for ./myapp: 1,023.45 msec task-clock # 0.999 CPUs utilized 5 context-switches # 0.005 /sec 0 cpu-migrations # 0.000 /sec 124 page-faults # 0.121 K/sec 2,542,310,101 cycles # 2.484 GHz 1,982,113,452 instructions # 0.78 insn per cycle 302,123,411 branches # 295.203 M/sec 4,510,223 branch-misses # 1.49% of all branches每个字段都有讲究task-clock进程占用CPU的总时间context-switches上下文切换次数频繁切换往往意味着锁竞争或调度问题cpu-migrations进程在不同CPU核心之间迁移的次数大量迁移会搞坏CPU缓存page-faults缺页次数频繁缺页说明进程内存访问模式有问题cyclesCPU周期总数instructions执行的指令数insn per cycleIPC每周期指令数这是评估CPU利用率的核心指标IPC接近1说明流水线利用得不错低于0.5大概率有访存停顿或分支预测问题branch-misses分支预测失效率对分支密集的代码影响巨大一条命令就能发现应用是“计算密集”还是“访存密集”还是“调度频繁”这是perf stat最有价值的地方。实战中我建议多跑几组对照比如并发量低的时候跑一次并发量高的时候再跑一次对比cache-misses和context-switches的变化趋势问题基本就浮出来了。如果要叠加更多定制事件perf stat -e cycles,instructions,cache-references,cache-misses,context-switches commandcache-references和cache-misses的比例是判断缓存命中率的关键。命中率低于95%、甚至低于90%的时候程序基本就是在等内存加再多CPU核心也没用。3.2 perf record perf report采样与离线分析的黄金组合perf stat给的是宏观统计数据如果要定位到具体函数就必须用record做采样。# 全系统采样带调用链99Hz采样频率持续60秒 sudo perf record -a -g -F 99 -- sleep 60 # 针对某个进程采样 sudo perf record -p pid -g -- sleep 30 # 针对某次命令执行的采样 sudo perf record -g ./myapp --input data.bin # 也可以指定事件比如采上下文切换事件 sudo perf record -e context-switches -a -g几个参数的含义-a全系统模式-g记录调用链backtrace-F 99采样频率99Hz99这个数字是性能分析界的“标准值”既能保证统计可靠性又不至于开销太大-p指定进程ID采样结束后当前目录会生成perf.data文件然后replay输出sudo perf reportreport界面是交互式的常用操作键回车/展开展开调用链上下箭头移动光标t切换绝对占比和相对占比a进入annotate模式查看热点代码的汇编和源码/-展开/折叠调用链h帮助第一次用report的人容易被海量信息淹没。我的建议是先看顶部的“Children”和“Self”两列Self占比高表示时间真正花在这个函数内部Children占比高表示时间花在它的子函数里。优先处理Self占比高的函数这些才是真正的CPU消耗点。Children占比高但Self很低的通常是聚合节点比如main函数没必要在main上花时间优化。3.3 perf annotate热点代码的“显微镜”report只能看到函数级别annotate可以深入到指令级别。在report界面光标选中热点函数后按a或者直接用命令行# 生成单个函数的汇编级注释报告 sudo perf annotate -s function_name --stdio输出会逐条显示指令以及每条指令的采样占比。这里能直观看到耗时指令是哪条比如一条movslq占了这个函数50%的采样说明它极大概率在反复从内存加载数据。配合源码基本能断定是循环、数组访问还是锁等待。annotate能工作有个前提二进制必须带符号表。很多线上程序strip过只有地址没有符号名。解决办法有两个一是优化编译时保留-g选项二是针对线上程序用perf内置的符号解析能力加载外部符号表文件。但最省事的手段是让开发环境用同样的代码重新编译一个带符号的版本采样数据拷过去做离线分析。整个过程不影响线上运行只需要约定期限的采样文件和二进制副本。3.4 record/report高频参数速查我用过一段时间的perf之后整理了一套高频组合分享给大家# 用户态内核态调用链99Hz采样60秒最适合CPU热点排查 sudo perf record -a -g -F 99 -- sleep 60 # 指定进程带调用链适合单服务分析 sudo perf record -p pid -g -- sleep 30 # 采调度事件查延迟抖动 sudo perf record -e sched:sched_switch -a -g -- sleep 10 # 采缓存失效事件查访存瓶颈 sudo perf record -e cache-misses -a -g -- sleep 30-F采样频率不是越大越好。频率太高perf自身开销会明显影响采样结果出现严重的观测者效应。99Hz已经能覆盖绝大多数场景追求更细的时间分辨率可以用-F 999但要评估对业务的干扰。4. 进阶玩法火焰图、动态追踪与延迟分析4.1 火焰图从采样数据到可视化perf report的交互界面虽然强大但在对外汇报、团队协作场景下火焰图明显更直观。Brendan Gregg发明的火焰图已经成为性能分析的事实标准perf的数据可以直接转换。生成火焰图需要两个脚本stackcollapse-perf.pl和flamegraph.pl来自Brendan Gregg的FlameGraph项目git clone https://github.com/brendangregg/FlameGraph.git cd FlameGraph # 先把perf.data转成脚本数据 sudo perf script out.perf # 折叠成调用栈计数格式 ./stackcollapse-perf.pl out.perf out.folded # 生成SVG火焰图 ./flamegraph.pl out.folded out.svg生成的SVG用浏览器打开每个色块代表一个函数宽度和采样占比成正比鼠标悬停可以看到具体函数名和占比。火焰图底部是最底层的函数顶部是最外层的调用入口。找性能瓶颈的核心方法很简单找“平顶山”也就是顶部很宽且颜色厚实的色块这些函数占的CPU时间最多值得深挖。我在实践中发现一个坑perf script输出的调用栈信息如果程序是用内部符号表解析的可能只显示地址不显示函数名。这种情况通常是符号表不完整要么用带符号的二进制重新解析要么在record前用--symfs指定符号目录。大多数时候缺符号的问题根源就是strip多说一句线上程序最好保留带符号的副本哪怕只用来看perf数据也值这个磁盘空间。4.2 perf probe动态插桩的实用场景perf还有一个很能打的功能是动态插桩可以在运行中的内核函数或用户态程序的函数入口注入探针采集参数和返回值。这在排查某些诡异问题的时候比采样好用得多。先看内核函数插桩# 在系统调用入口插桩 sudo perf probe --add tcp_sendmsg # 查看插桩点 sudo perf probe -l # 按插桩事件采样 sudo perf record -e probe:tcp_sendmsg -a -- sleep 10用户态程序的动态插桩用-x指定二进制路径sudo perf probe -x /usr/lib/x86_64-linux-gnu/libc.so.6 malloc sudo perf record -e probe_libc:malloc -a -- sleep 5更高级的用法是抓函数参数sudo perf probe tcp_sendmsg size sudo perf record -e probe:tcp_sendmsg -a -- sleep 10 sudo perf script这样能看到每次tcp_sendmsg调用时size的具体值对分析网络协议栈的报文大小分布很有用。但要注意perf probe在内核态插桩强烈不建议在生产环境随意添加。插桩本身有风险搞不好会引入内核不稳定因素。我在非生产环境试过在文件系统写路径上插桩就遇到过因为探针事件过多导致的内核日志刷屏。生产环境要动它务必先在预发环境完整验证并且用perf probe --del及时清理不需要的探针。4.3 perf sched揪出延迟抖动和调度问题对于“性能数据正常但体验卡顿”的问题perf sched是一把好手。它专门分析内核调度器行为。# 采样调度事件 sudo perf sched record -- sleep 10 # 查看调度延迟统计 sudo perf sched latencylatency输出会按任务展示平均调度延迟、最大延迟和延迟分布。我排查过一个网络服务P99延迟飙高的问题业务线程的平均调度延迟只有0.2ms但P99达到了50msperf sched timehist一看原来是某个线程频繁让出CPU然后被调度器放到另一个NUMA节点上导致内存访问跨节点延迟飙升。这就是典型的调度和NUMA共同作用的问题单靠业务日志根本看不出来。在虚拟化场景下perf同样能帮上忙。比如你在一台KVM宿主机上跑了很多虚拟机某个虚机网络时延异常。先在宿主机上执行sudo perf top -g大概率能看到vhost进程或者kvm模块的函数热点。如果是vhost作为网络数据平面的瓶颈perf会告诉你热点是在vhost_work系列函数里还是内核协议栈里。这个信息对判断“该调virtio队列数”还是“该换网卡多队列策略”非常关键。4.4 perf kvm虚拟化场景的性能视角KVM环境下的性能分析perf有一个专门的扩展模式# 采集host和guest的采样数据 sudo perf kvm --host --guest record -a -g -- sleep 30 # 生成report sudo perf kvm --host --guest report它会同时区分host和guest的调用栈能看出是虚拟化层开销大还是guest内部自身开销大。排查虚机CPU steal高的场景这类工具几乎是唯一能直接给你证据的手段。不过perf kvm对内核配置有要求某些发行版的内核没开KVM的perf支持执行时会报错。遇到报错先去确认/boot/config-$(uname -r)里有没有CONFIG_KVM_INTEL或CONFIG_KVM_AMD以及有没有CONFIG_KVM_TRACE。没有对应配置的话perf kvm就用不了只能退回到host侧做常规采样分析。5. 常见问题与排查技巧实录5.1 所有计数器都是0或Permission denied这是perf最经典的问题。报错信息通常是perf_event_open failed: Permission denied或者You may not have permission to collect stats。原因基本都指向kernel.perf_event_paranoid。解决方式前面说了sudo sysctl -w kernel.perf_event_paranoid1如果设成1还不行可能是Secure Boot或内核配置禁用了perf_event。这种情况需要检查内核启动参数确认没有加perf_event_paranoid2之类的hardcode也没有在/sys/module/目录下禁用相关模块。5.2 符号全是十六进制地址perf report里看到大量地址而不是函数名基本可以判断是符号表缺失。常见原因有三个程序编译时没加-g参数或者strip过动态库版本更新过采样时的库和当前解析时的库对不上内核自身的kallsyms符号表被压缩或关闭了对于用户态程序解决方法是找一份带符号的二进制用perf report --symfs /path/to/symbols指定符号路径或者干脆在编译机上重新执行record和report。对内核确认CONFIG_KALLSYMS已开启并且/proc/kallsyms有读取权限。5.3 采样频率太高导致系统卡顿perf的观测者效应是真实存在的。采样频率过高比如-F 9999系统的整体性能会被明显拖慢采出来的数据也会失真。我的经验是常规CPU热点分析99Hz足够短时间高精度追踪999Hz最多持续十几秒事件频率本身很高的场景cache-misses、tracepoint优先用-c指定采样周期而不是-F指定频率另外perf record时加上--all-user或--all-kernel可以只采样用户态或只采样内核态能减少一半的采样开销。5.4 常见问题速查表现象可能原因排查/解决办法perf stat全0或Permission deniedperf_event_paranoid限制sysctl设置paranoid为1或0report显示地址无函数名符号表缺失/strip加-g编译或--symfs指定符号调用栈只有一层编译器优化内联编译加-fno-omit-frame-pointer或采样用--call-graph dwarfrecord后perf.data太大采样频率过高/无事件过滤降低-F或按事件过滤热点函数不是业务代码锁竞争/CPU迁移/内核开销看sched事件用perf sched分析KVM场景guest数据空白内核KVM perf支持未开启检查CONFIG_KVM_TRACE等选项5.5 一个完整的排查路径参考假设你处理的问题是“Java服务CPU飙升到300%”我的排查路径大致是# 找到线程PID top -Hp pid # 看CPU热点确认是JIT编译后的代码还是JVM内部 sudo perf top -p pid -F 999 # 采样带调用链抓到JIT符号 sudo perf record -p pid -g --call-graph dwarf -- sleep 30 # 生成report看热点是GC线程还是业务线程 sudo perf reportJava场景有个额外的问题JIT即时编译出来的代码会产生动态符号perf如果不配合-XX:PreserveFramePointer和perf map文件抓到的大概率是[unknown]。这属于perf的一个特殊应用方向不过原理和普通程序完全一致只是要额外处理JVM的符号映射。说白了perf的核心能力不挑语言挑的是你能不能把符号表准备好。6. 最后的实操心得perf这套工具要说难用它确实有学习曲线命令多、参数多、输出信息密要说好用一旦熟悉了几乎所有性能问题都能快速定位到“该看哪里”。我个人最常组合使用的一条命令是sudo perf record -a -g -F 99 --call-graph dwarf -- sleep 60然后根据report结果决定下一步用stat看宏观指标还是用annotate深入指令级或者用sched查延迟。这套流程我用了很久从简单Web服务到性能敏感的中间件都验证过稳定可靠。最后分享两个小技巧。第一perf的采样数据perf.data可以离线分析完全可以在测试环境跑压测把perf.data和带符号的二进制一起拷到本地分析不影响线上。第二养成“先看宏观再深入”的习惯别一上来就开-g很多新手被调用链淹没后反而下不了手。先从perf stat全局看一遍再perf top找热点函数最后record抓调用链从粗到细问题很快就能水落石出。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →