火焰图是分层数据的可视化,可以直观分析函数的调用频次,函数调用栈,以此来分析性能瓶颈。
1、火焰图简介
1.1、火焰图的分析
- 每一列代表一个调用栈(栈帧),每一个格子代表调用栈中的一个函数。
- 横轴:全部的采样样本的数量。格子的宽度代表函数调用频次,一个格子的宽度越大,越说明其可能是瓶颈原因。火焰图从左到右并不代表时间流动,只是按照字母顺序排列,将栈中同一层的函数最大化合并,实现信息的聚合
- 纵轴:函数调用栈的深度(栈桢数)。按照函数调用关系从下到上排列。最顶上格子代表采样时,正在占用 CPU 的函数。
- 颜色:默认采用随机暖色调对函数栈帧进行着色。也可以通过色调表示代码的类型(红色:原生用户态代码,橙色:内核态代码等),背景颜色表示火焰图的类型(黄色:on-cpu,蓝色:off-cpu,绿色:内存火焰图等)
阅读火焰图,应该先找到最宽的部分并去理解它。
1.2、火焰图局限性
- 掉栈不完整
- 函数名缺失
先不要做编译优化,很多函数优化后,看不到调用栈
1.3、火焰图的类型
on-cpu 火焰图
分析 CPU 占用的性能问题。以固定频率采样 CPU 调用栈。横轴表示 cpu 占用时间,纵轴表示调用栈。
off-cpu 火焰图
分析 IO 阻塞,锁竞争,死锁的性能问题。以固定频率采样阻塞事件调用栈。横轴表示阻塞时间,纵轴表示调用栈。
Hot / Cold火焰图
综合 on-cpu 火焰图和 off-cpu 火焰图,分析 cpu 占用和阻塞的场景。
内存火焰图
分析内存泄漏问题,内存占用高的对象。有四种方式:跟踪 malloc/free,跟踪 brk,跟踪 mmap,跟踪页错误。横轴表示内存申请或释放的函数调用次数,纵轴表示调用栈。
红蓝分叉火焰图
分析不同版本的性能差异问题。对比两个版本的 on-cpu 火焰图。红色表示性能上升,蓝色表示性能下降。
2、绘制火焰图
2.1、火焰图生成流程
- 采集堆栈:使用
perf/systemtap/dtrace
等工具抓取程序的运行堆栈 - 折叠堆栈:使用
stackcollapse
程序,对堆栈信息进行分析组合,将重复的堆栈累计在一起,体现出负载和关键路径 - 生成火焰图:分析
stackcollapse
输出的堆栈信息生成火焰图
2.2、安装火焰图工具
2.2.1、安装 FlameGraph 脚本
FlameGraph 是一套生成火焰图的脚本。
# 下载 FlameGraph
git clone https://gitee.com/mirrors/FlameGraph.git
# 查看帮助
./FlameGraph/flamegraph.pl -h
对于不同的 trace 工具,抓取到的信息不同,对应不同的 stackcollapse 工具。参考官网文档:FlameGraph
2.2.2、安装 perf 命令
系统级性能优化通常包括两个阶段:
- 性能剖析:寻找性能瓶颈,查找引发性能问题的原因和热点代码。
- 代码优化:针对具体性能问题而优化代码或编译选项,以及改善软件性能。
perf
命令:返回 CPU 正在执行的函数名以及调用栈,是 Linux 系统原生提供的性能分析工具。
工作原理:固定频率采样 cpu,对 cpu 产生中断,统计函数(或进程 pid)的运行时间,获得 cpu 占用率。
安装 perf 命令
# 安装 perf 命令
sudo apt install linux-tools-common
# 测试 perf 命令是否可用
sudo perf record -F 99 -a -g -- sleep 10
# 帮助文档
perf -h
常用的 perf 命令
# 查看当前软硬件环境支持的性能事件
perf list
# 分析指定程序的性能概况
perf stat
# 实时显示系统/进程的性能统计信息
perf top
# 记录一段时间内系统/进程的性能事件
perf record
# 读取 perf record 生成的 perf.data 文件,并显示分析数据
perf report:
生成火焰图的采集命令
# 记录一段时间内系统/进程的性能事件
perf record
-e '指定性能事件,默认采集 cycles (cpu clock)
-F '采集频率,每秒采样 n 次
-g '开启 call-graph (stack chain/backtrace) 记录
-o '指定输出文件 output.data,默认输出到perf.data
-p '指定待分析进程的 pid
-t '指定待分析线程的 tid
2.3、例:生成火焰图
2.3.1、采集堆栈
通过 top
或 ps -ef | grep 进程名
来查看测试的 pid,使用 perf record
采集系统事件
sudo perf record -F 99 -p 46242 -g -- sleep 30
为了便于阅读,perf record
命令可以统计每个调用栈出现的百分比, 然后从高到低排列
perf report -n --stdio
2.3.2、折叠堆栈
使用 perf script
工具对 perf.data 进行解析
perf script -i perf.data &> perf.unfold
用 stackcollapse-perf.pl 将 perf 解析出的内容 perf.unfold 中的符号进行折叠
./FlameGraph/stackcollapse-perf.pl perf.unfold &> perf.folded
2.3.3、生成火焰图
生成 svg 图
./FlameGraph/flamegraph.pl perf.folded > test_oncpu.svg
使用管道简化命令
perf script | ./FlameGraph/stackcollapse-perf.pl | ./FlameGraph/flamegraph.pl > test_oncpu.svg
3、实践:ZeroMQ REQ-REP模型测试
3.1、安装 ZMQ
# 安装必备的库
sudo apt-get install libtool
sudo apt-get install pkg-config
sudo apt-get install build-essential
sudo apt-get install autoconf
sudo apt-get install automake
# 安装加密库
git clone https://github.com/jedisct1/libsodium.git
cd libsodium
./autogen.sh -s
./configure && make check
sudo make install
sudo ldconfig
# 安装 libzmq
git clone https://github.com/zeromq/libzmq.git
cd libzmq
git tag
git checkout v4.3.4
./autogen.sh
./configure && make check
sudo make install
sudo ldconfig
3.2、on-cpu火焰图
# 采集数据
perf record -F 99 -p 135989 -g -- sleep 60
# 生成火焰图
perf script | ./FlameGraph/stackcollapse-perf.pl | ./FlameGraph/flamegraph.pl > zeromq-req-rep.svg
3.3、off-cpu 火焰图
使能 sched_schedstats
统计
echo 1 > /proc/sys/kernel/sched_schedstats
通过 perf 采集下列事件
sched:sched_stat_sleep
:进程主动放弃 cpu 而进入睡眠的等待事件sched:sched_switch
:进程由于 I/O 和锁阻塞等原因被调度器切换而进入睡眠的等待事件sched:sched_process_exit
:进程的退出事件
perf record -e sched:sched_stat_sleep -e sched:sched_switch -e sched:sched_process_exit -p 114143 -g -o perf.data.raw sleep 30
perf inject -v -s -i perf.data.raw -o perf.data
生成火焰图
perf script -F comm,pid,tid,cpu,time,period,event,ip,sym,dso,trace | awk '
NF > 4 { exec = $1; period_ms = int($5 / 1000000) }
NF > 1 && NF <= 4 && period_ms > 0 { print $2 }
NF < 2 && period_ms > 0 { printf "%s\n%d\n\n", exec, period_ms }' | \
./FlameGraph/stackcollapse.pl | \
./FlameGraph/flamegraph.pl --countname=ms --title="Off-CPU Time Flame Graph" --colors=io > zeromq-req-rep1-offcpu.svg
4、实践:Nginx HTTP
4.1、wrk 压测
安装 HTTP 压测工具:wrk
git clone https://github.com/wg/wrk
cd wrk
make
sudo cp wrk /usr/local/bin
wrk 命令参数
-c --connections N 跟服务器建立并保持的TCP连接数量
-d --duration T 压测时间
-t --threads N 使用多少个线程进行压测
-s --script S 指定Lua脚本路径
-H --header H 为每一个HTTP请求添加HTTP头
--latency 在压测结束后,打印延迟统计信息
--timeout T 超时时间
-v --version 打印正在使用的wrk的详细版本信息
使用 wrk 压测
wrk -c 20 -t 2 -d 5M --latency http://192.168.88.136
4.2、on-cpu火焰图
# 查找 nginx worker 进程 pid
ps -ef | grep nginx
# 采集数据
perf record -F 99 -p 1996 -g -- sleep 60
# 生成火焰图
perf script | ./FlameGraph/stackcollapse-perf.pl | ./FlameGraph/flamegraph.pl > nginx-on-cpu.svg
4.3、off-cpu 火焰图
# 查找 nginx worker 进程 pid
ps -ef | grep nginx
# 采集数据
perf record -e sched:sched_stat_sleep -e sched:sched_switch -e sched:sched_process_exit -p 1997 -g -o perf.data.raw sleep 30
perf inject -v -s -i perf.data.raw -o perf.data
# 生成火焰图
perf script -F comm,pid,tid,cpu,time,period,event,ip,sym,dso,trace | awk '
NF > 4 { exec = $1; period_ms = int($5 / 1000000) }
NF > 1 && NF <= 4 && period_ms > 0 { print $2 }
NF < 2 && period_ms > 0 { printf "%s\n%d\n\n", exec, period_ms }' | \
./FlameGraph/stackcollapse.pl | \
./FlameGraph/flamegraph.pl --countname=ms --title="Off-CPU Time Flame Graph" --colors=io > nginx-off-cpu.svg
5、实践:测试线程池队列
5.1、on-cpu火焰图
# 采集数据
perf record -F 99 -p 42901 -g -- sleep 60
# 生成火焰图
perf script | ./FlameGraph/stackcollapse-perf.pl | ./FlameGraph/flamegraph.pl > threadpool-oncpu.svg
5.2、off-cpu 火焰图
# 使能 sched_schedstats 统计
echo 1 > /proc/sys/kernel/sched_schedstats
# 采集数据
perf record -e sched:sched_stat_sleep -e sched:sched_switch -e sched:sched_process_exit -p 8657 -g -o perf.data.raw sleep 30
perf inject -v -s -i perf.data.raw -o perf.data
# 生成火焰图
perf script -F comm,pid,tid,cpu,time,period,event,ip,sym,dso,trace | awk '
NF > 4 { exec = $1; period_ms = int($5 / 1000000) }
NF > 1 && NF <= 4 && period_ms > 0 { print $2 }
NF < 2 && period_ms > 0 { printf "%s\n%d\n\n", exec, period_ms }' | \
./FlameGraph/stackcollapse.pl | \
./FlameGraph/flamegraph.pl --countname=ms --title="Off-CPU Time Flame Graph" --colors=io > threadpool-offcpu.svg