【火焰图生成与分析】

火焰图仅用一张小图,就可以定量展示所有的性能瓶颈的全景图,而不论目标软件有多么复杂。
传统的性能分析工具通常会给用户展示大量的细节信息和数据, 而用户很难看到全貌,反而容易去优化那些并不重要的地方,经常浪费大量时间和精力却看不到明显效果。传统分析器的另一个缺点是,它们通常会孤立地显示每个函数调用的延时,但很难看出各个函数调用的上下文,而且用户还须刻意区分当前函数本身运行的时间(exclusive time)和包括了其调用其他函数的时间在内的总时间(inclusive time)。

而相比之下,火焰图可以把大量信息压缩到一个大小相对固定的图片当中(通常一屏就可以显示全)。 不怎么重要的代码路径会在图上自然地淡化乃至消失,而真正重要的代码路径则会自然地凸显出来。越重要的,则会显示得越明显。火焰图总是为用户提供最适当的信息量,不多,也不少。

1 火焰图简介

官方博客:Flame Graphs,火焰图的源资料皆出自该博客。

火焰图能做什么:

  • 可以分析函数执行的频繁程度
  • 可以分析哪些函数经常阻塞
  • 可以分析哪些函数频繁分配内存

以分析程序的性能瓶颈。

v2-a9fb405f67431a0e3218d3766c7b9e95_1440w.webp

火焰图整个图形看起来就像一个跳动的火焰,这就是它名字的由来。
火焰图有以下特征(这里以 on-cpu 火焰图为例):

  • 每一列代表一个调用栈,每一个格子代表一个函数
  • 纵轴展示了栈的深度,按照调用关系从下到上排列。最顶上格子代表采样时,正在占用 cpu 的函数。
  • 横轴的意义是指:火焰图将采集的多个调用栈信息,通过按字母横向排序的方式将众多信息聚合在一起。需要注意的是它并不代表时间。
  • 横轴格子的宽度代表其在采样中出现频率,所以一个格子的宽度越大,说明它是瓶颈原因的可能性就越大。
  • 火焰图格子的颜色是随机的暖色调,方便区分各个调用信息。
  • 其他的采样方式也可以使用火焰图, on-cpu 火焰图横轴是指 cpu 占用时间,off-cpu 火焰图横轴则代表阻塞时间。
  • 采样可以是单线程、多线程、多进程甚至是多 host,进阶用法可以参考附录进阶阅读。

1.1 火焰图类型

常见的火焰图类型有 On-CPU,Off-CPU,还有 Memory,Hot/Cold,Differential 等等。它们有各自适合处理的场景。

火焰图类型横轴含义纵轴含义解决问题采样方式
on-cpu火焰图cpu占用时间调用栈找出cpu占用搞的问题函数;分析代码热路径固定频率采样cpu调用栈
off-cpu火焰图阻塞时间调用栈i/o、网络等阻塞场景导致的性能下降;锁竞争、死锁导致的性能下降问题固定频率采样阻塞事件调用栈
内存火焰图内存申请/释放函数调用次数调用栈内存泄漏问题;内存占用高的对象/申请内存多的函数;虚拟内存或物理内存泄漏问题有四种方式:跟踪malloc/free;跟踪brk;跟踪mmap;跟踪页错误
Hot/Cold火焰图on-cpu和off-cpu综合展示调用栈需要结合cpu占用以及阻塞分析的场景;off-cpu火焰图无法直观判断的场景on-cpu火焰图和off-cpu火焰图结合

1.2 什么时候使用 On-CPU 火焰图? 什么时候使用 Off-CPU 火焰图呢?

v2-6901e768ba5c1830a7274493679a1b35_1440w.webp

取决于当前的瓶颈到底是什么:

  • 如果是 CPU 则使用 On-CPU 火焰图,
  • 如果是 IO 或锁则使用 Off-CPU 火焰图.
  • 如果无法确定, 那么可以通过压测工具来确认:
  • 通过压测工具看看能否让 CPU 使用率趋于饱和, 如果能那么使用 On-CPU 火焰图
  • 如果不管怎么压, CPU 使用率始终上不来, 那么多半说明程序被 IO 或锁卡住了, 此时适合使用 Off-CPU 火焰图.
  • 如果还是确认不了, 那么不妨 On-CPU 火焰图和 Off-CPU 火焰图都搞搞, 正常情况下它们的差异会比较大, 如果两张火焰图长得差不多, 那么通常认为 CPU 被其它进程抢占了.

1.3 火焰图分析技巧

  1. 纵轴代表调用栈的深度(栈桢数),用于表示函数间调用关系:下面的函数是上面函数的父函数。
  2. 横轴代表调用频次,一个格子的宽度越大,越说明其可能是瓶颈原因。
  3. 不同类型火焰图适合优化的场景不同,比如 on-cpu 火焰图适合分析 cpu 占用高的问题函数,off-cpu 火焰图适合解决阻塞和锁抢占问题。
  4. 无意义的事情:横向先后顺序是为了聚合,跟函数间依赖或调用关系无关;火焰图各种颜色是为方便区分,本身不具有特殊含义
  5. 多练习:进行性能优化有意识的使用火焰图的方式进行性能调优(如果时间充裕)

2 如何绘制火焰图?

2.1 生成火焰图的流程

Brendan D. Gregg 的 Flame Graph 工程实现了一套生成火焰图的脚本。Flame Graph 项目位于 GitHub上

当GitHub网络不通畅的时候可以使用码云的链接:
git clone https://gitee.com/mirrors/FlameGraph.git

用 git 将其 clone下来

生成和创建火焰图需要如下几个步骤

流程描述脚本
捕获堆栈使用 perf/systemtap/dtrace 等工具抓取程序的运行堆栈perf/systemtap/dtrace
折叠堆栈trace 工具抓取的系统和程序运行每一时刻的堆栈信息, 需要对他们进行分析组合, 将重复的堆栈累计在一起, 从而体现出负载和关键路径FlameGraph 中的 stackcollapse 程序
生成火焰图分析 stackcollapse 输出的堆栈信息生成火焰图flamegraph.pl

不同的 trace 工具抓取到的信息不同, 因此 Flame Graph 提供了一系列的 stackcollapse 工具.

stackcollapse描述
stackcollapse.plfor DTrace stacks
stackcollapse-perf.plfor Linux perf_events “perf script” output
stackcollapse-pmc.plfor FreeBSD pmcstat -G stacks
stackcollapse-stap.plfor SystemTap stacks
stackcollapse-instruments.plfor XCode Instruments
stackcollapse-vtune.plfor Intel VTune profiles
stackcollapse-ljp.awkfor Lightweight Java Profiler
stackcollapse-jstack.plfor Java jstack(1) output
stackcollapse-gdb.plfor gdb(1) stacks
stackcollapse-go.plfor Golang pprof stacks
stackcollapse-vsprof.plfor Microsoft Visual Studio profiles

查看帮助
./FlameGraph/flamegraph.pl -h

2.2 安装 perf工具

perf 命令(performance 的缩写)讲起, 它是 Linux 系统原生提供的性能分析工具, 会返回 CPU 正在执行的函数名以及调用栈(stack)

具体用法:

2.2.1 安装perf
# apt install linux-tools-common
2.2.2 测试perf是否可用
# perf record -F 99 -a -g -- sleep 10    

如果报错

WARNING: perf not found for kernel 4.15.0-48

You may need to install the following packages for this specific kernel:
linux-tools-4.15.0-48-generic
linux-cloud-tools-4.15.0-48-generic

You may also want to install one of the following packages to keep up to date:
linux-tools-generic
linux-cloud-tools-generic

apt install linux-tools-generic
apt install linux-cloud-tools-generic

则需要安装linux-tools-generic和linux-cloud-tools-generic,但需选择对应的版本,比如提示的是4.15.0-48,则我们安装4.15.0-48版本

# apt-get install linux-tools-4.15.0-48-generic linux-cloud-tools-4.15.0-48-generic linux-tools-generic linux-cloud-tools-generic

再次测试

# perf record -F 99 -a -g -- sleep 10    

如果没有报错则在执行的目录产生perf.data

v2-d0f37c70f3e7c81689a4e8706268dc67_1440w.webp

2.2.3 perf常用命令

查看帮助文档,perf功能非常强大,我们这里只关注record和report功能,record和report也可以继续通过二级命令查询帮助文档。

perf -h

root@ceph-admin:/home/lqf/flamegraph# perf -h

 usage: perf [--version] [--help] [OPTIONS] COMMAND [ARGS]

 The most commonly used perf commands are:
   annotate        Read perf.data (created by perf record) and display annotated code
   archive         Create archive with object files with build-ids found in perf.data file
   bench           General framework for benchmark suites
   buildid-cache   Manage build-id cache.
    ....
   lock            Analyze lock events
   mem             Profile memory accesses
   record          Run a command and record its profile into perf.data
   report          Read perf.data (created by perf record) and display the profile

常用的五个命令:

  • perf list:查看当前软硬件环境支持的性能事件
  • perf stat:分析指定程序的性能概况
  • perf top:实时显示系统/进程的性能统计信息
  • perf record:记录一段时间内系统/进程的性能事件perf report:读取perf record生成的perf.data文件,并显示分析数据(生成火焰图用的采集命令)

perf record -h
常用命令

  • -e :指定性能事件(可以是多个,用,分隔列表)
  • -p :指定待分析进程的 pid(可以是多个,用,分隔列表)
  • -t :指定待分析线程的 tid(可以是多个,用,分隔列表)
  • -u :指定收集的用户数据,uid为名称或数字
  • -a:从所有 CPU 收集系统数据
  • -g:开启 call-graph (stack chain/backtrace) 记录
  • -C :只统计指定 CPU 列表的数据,如:0,1,3或1-2
  • -r :perf 程序以SCHED_FIFO实时优先级RT priority运行这里填入的数值越大,进程优先级越高(即 nice 值越小)
  • -c : 事件每发生 count 次采一次样
  • -F :每秒采样 n 次
  • -o <output.data>:指定输出文件output.data,默认输出到perf.data

2.3 perf 采集数据

# perf record -F 99 -p 3887 -g – sleep 30

perf record 表示采集系统事件, 没有使用 -e 指定采集事件, 则默认采集 cycles(即 CPU clock 周期), -F 99 表示每秒 99 次, -p 13204 是进程号, 即对哪个进程进行分析, -g 表示记录调用栈, sleep 30 则是持续 30 秒.

-F 指定采样频率为 99Hz(每秒99次), 如果 99次 都返回同一个函数名, 那就说明 CPU 这一秒钟都在执行同一个函数, 可能存在性能问题.

运行后会产生一个庞大的文本文件. 如果一台服务器有 16 个 CPU, 每秒抽样 99 次, 持续 30 秒, 就得到 47,520 个调用栈, 长达几十万甚至上百万行.

为了便于阅读, perf record 命令可以统计每个调用栈出现的百分比, 然后从高到低排列.

# perf report -n --stdio

2.4 生成火焰图

  1. 首先用 perf script 工具对 perf.data 进行解析

# 生成折叠后的调用栈
perf script -i perf.data &> perf.unfold

  1. 然后将解析出来的信息存下来, 供生成火焰图

stackcollapse-perf.pl 将 perf 解析出的内容 perf.unfold 中的符号进行折叠 :

# 生成火焰图
# ./FlameGraph/stackcollapse-perf.pl perf.unfold &> perf.folded

  1. 最后生成 svg 图

# ./FlameGraph/flamegraph.pl
perf.folded > perf.svg

我们可以使用管道将上面的流程简化为一条命令

# perf script | ./FlameGraph/stackcollapse-perf.pl | ./FlameGraph/flamegraph.pl > process.svg

解析火焰图
最后就可以用浏览器打开火焰图进行分析啦.

3 火焰图的含义

火焰图是基于 stack 信息生成的 SVG 图片, 用来展示 CPU 的调用栈。

  • y 轴表示调用栈, 每一层都是一个函数. 调用栈越深, 火焰就越高, 顶部就是正在执行的函数, 下方都是它的父函数.

  • x 轴表示抽样数, 如果一个函数在 x 轴占据的宽度越宽, 就表示它被抽到的次数多, 即执行的时间长. 注意, x 轴不代表时间, 而是所有的调用栈合并后, 按字母顺序排列的.

  • 火焰图就是看顶层的哪个函数占据的宽度最大. 只要有 “平顶”(plateaus), 就表示该函数可能存在性能问题。

  • 颜色没有特殊含义, 因为火焰图表示的是 CPU 的繁忙程度, 所以一般选择暖色调.

4 互动性 火焰图是 SVG 图片,可以与用户互动。

范例:https://queue.acm.org/downloads/2016/Gregg5.svg

(1)鼠标悬浮 火焰的每一层都会标注函数名,鼠标悬浮时会显示完整的函数名、抽样抽中的次数、占据总抽样次数的百分比。下面是一个例子。

mysqld’JOIN::exec (272,959 samples, 78.34 percent)

off-cpu是否也是如此?

(2)点击放大 在某一层点击,火焰图会水平放大,该层会占据所有宽度,显示详细信息。

v2-1a0cd39584458af0078787b5a2af2a54_1440w.webp

左上角会同时显示"Reset Zoom",点击该链接,图片就会恢复原样。

(3)搜索
按下 Ctrl + F 会显示一个搜索框,用户可以输入关键词或正则表达式,所有符合条件的函数名会高亮显示。

5 火焰图示例

下面是一个简化的火焰图例子。
首先,CPU 抽样得到了三个调用栈。

func_c 
func_b 
func_a 
start_thread 

func_d 
func_a 
start_thread 

func_d 
func_a 
start_thread

上面代码中,start_thread是启动线程,调用了func_a。后者又调用了func_b和func_d,而func_b又调用了func_c。
经过合并处理后,得到了下面的结果,即存在两个调用栈,第一个调用栈抽中1次,第二个抽中2次。

start_thread;func_a;func_b;func_c 1  start_thread;func_a;func_d 2

有了这个调用栈统计,火焰图工具就能生成 SVG 图片。

v2-751e862b3edb7b19320d32001a4e4d45_720w.webp

上面图片中,最顶层的函数g()占用 CPU 时间最多。d()的宽度最大,但是它直接耗用 CPU 的部分很少。b()和c()没有直接消耗 CPU。因此,如果要调查性能问题,首先应该调查g(),其次是i()。

另外,从图中可知a()有两个分支b()和h(),这表明a()里面可能有一个条件语句,而b()分支消耗的 CPU 大大高于h()。

6 局限

两种情况下,无法画出火焰图,需要修正系统行为。

(1)调用栈不完整

当调用栈过深时,某些系统只返回前面的一部分(比如前10层)。

(2)函数名缺失

有些函数没有名字,编译器只用内存地址来表示(比如匿名函数)。

7 实践-ZeroMQ REQ-REP模型测试 测试两种情况

  • hwserver不休眠直接返回 on-cpu火焰图
  • hwserver休眠10ms返回 off-cpu火焰图

测试程序:src-flamegraph\libzmq-test

  • hwserver.c
  • hwclient.c

7.1 on-cpu火焰图

7.1.1 使用perf采集
# perf record -F 99 -p 34631 -g -- sleep 120
7.1.2 生成火焰图
# perf script | ./FlameGraph/stackcollapse-perf.pl | ./FlameGraph/flamegraph.pl > zeromq-req-rep.svg

v2-29e411323f08d52e14b6e16af347de45_1440w.webp

7.2 off-cpu火焰图

https://www.brendangregg.com/blog/2015-02-26/linux-perf-off-cpu-flame-graph.html
Off-CPU Analysis
https://www.brendangregg.com/offcpuanalysis.html

v2-d6bb2d0cef704b5cdd54a2b23829f265_1440w.webp

7.2.1 使能sched_schedstats统计 需要在root权限下使能
# echo 1 > /proc/sys/kernel/sched_schedstats
7.2.2 使用perf采集

-p 是对应要采集程序的进程ID

# perf record -e sched:sched_stat_sleep -e sched:sched_switch -e sched:sched_process_exit -p 43777 -g -o perf.data.raw sleep 10 

# perf inject -v -s -i perf.data.raw -o perf.data

通过perf分别记录sched:sched_stat_sleep、sched:sched_switch、sched:sched_process_exit三种事件,这三种事件分别表示:进程主动放弃 CPU 而进入睡眠的等待事件、进程由于I/O和锁等待等原因被调度器切换而进入睡眠的等待事件、进程的退出事件。

7.2.3 生成火焰图
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 > offcpu.svg

v2-265f5e568f86f924bb4d6aee42ad5dc7_1440w.webp

8 实践nginx http

8.1 配置和启动nginx

  1. 修改nginx.conf,配置web页面路径

主要是修改root /mnt/hgfs/ubuntu/vip/20210821-flamegraph/0voice_webrtc; 对应web页面的路径。

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;
    
    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        location / {
						root /mnt/hgfs/ubuntu/vip/20210821-flamegraph/0voice_webrtc;
            index  index.html index.htm;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

2. 然后重新启动nginx

8.2 HTTP压测工具wrk使用指南

8.2.1 安装wk

从github上下载源码
git clone https://github.com/wg/wrk

cd wrk
make
8.2.2 基本参数说明 以下是使用wrk查看到的一些基本参数信息
-c–connectionsN跟服务器建立并保持的TCP连接数量
-d–durationT压测时间
-t–threadsN使用多少个线程进行压测
-s–scriptS指定Lua脚本路径
-H–headerH为每一个HTTP请求添加HTTP头
–latency在压测结束后,打印延迟统计信息
–timeoutT超时时间
-v–version打印正在使用的wrk的详细版本信息
  • N代表数字参数,支持国际单位 (1k, 1M, 1G)
  • T代表时间参数,支持时间单位 (2s, 2m, 2h)

wrk -c 20 -t 2 -d 20s –latency http://192.168.31.248

建立20个TCP连接,使用两个线程,用时20秒,对 http://192.168.31.248 进行压测。

8.2.3 输出内容
lqf@ceph-admin:~/flamegraph/wrk$ ./wrk -c 20 -t 2 -d 20s --latency http://192.168.31.248
Running 20s test @ http://192.168.31.248
  2 threads and 20 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
              (平均值)(标准差)(最大值)(正负一个标准差所占比例)
    Latency     9.35ms    1.91ms  81.96ms   96.92%
    (延迟)
    Req/Sec     1.08k    73.16     1.52k    88.50%
    (处理中的请求数)
  Latency Distribution(延迟分布)
     50%    9.03ms
     75%    9.45ms
     90%    9.97ms
     99%   17.26ms(99分位的延迟)
  43019 requests in 20.03s, 170.79MB read(20.03s秒内共处理完成了43019个请求,读取了170.79MB数据)
Requests/sec:   2148.24    (平均每秒处理完成2148.24个请求)
Transfer/sec:      8.53MB (平均每秒读取数据8.53MB)

lqf@ceph-admin:~/flamegraph/wrk$ ./wrk -c 20 -t 2 -d 20s --latency http://120.27.131.197
Running 20s test @ http://120.27.131.197
  2 threads and 20 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    44.10ms   69.76ms 509.32ms   92.73%
    Req/Sec   382.88     80.68   470.00     88.14%
  Latency Distribution
     50%   23.98ms
     75%   26.97ms
     90%   58.56ms
     99%  404.74ms
  14363 requests in 20.02s, 5.03MB read
Requests/sec:    717.38
Transfer/sec:    257.11KB 
  • Latency 延迟时间
  • Req/Sec 每秒处理的请求数
  • 平均值(Avg),
  • 标准偏差(Stdev),
  • 最大值(Max)
  • 正负一个标准差占比(+/-) Stdev

一般主要关注Avg和MaxStdev如果太大说明样本本身离散程度比较高,有可能系统性能波动很大。

8.3 on-cpu火焰图分析nginx

8.3.1 使用wrk压测 压测的时间要大于perf采集的时间
./wrk -c 20 -t 2 -d 5M --latency http://192.168.31.248
8.3.2 使用perf采集
  1. 查找nginx worker进程pid
# ps -ef | grep nginx
root      85453  16908  0 00:03 ?        00:00:00 nginx: master process /usr/local/nginx/sbin/nginx
nobody    85454  85453  0 00:03 ?        00:00:36 nginx: worker process
root      97907  56681  0 21:18 pts/22   00:00:00 grep --color=auto nginx

查找到对应work pid为85454

  1. 采集数据
# perf record -F 99 -p 85454 -g -- sleep 60
8.3.3 生成on-cpu火焰图

# perf script | ./FlameGraph/stackcollapse-perf.pl | ./FlameGraph/flamegraph.pl > nginx-on-cpu.svg

v2-ba5bc64f771153bb83ed5af7ece6a4fa_1440w.webp

8.4 off-cpu火焰图分析nginx

8.4.1 使能sched_schedstats统计

需要在root权限下使能
# echo 1 > /proc/sys/kernel/sched_schedstats

8.4.2 使用perf采集
  1. 查找nginx worker进程pid
# ps -ef | grep nginx
root 85453 16908 0 00:03 ? 00:00:00 nginx: master process /usr/local/nginx/sbin/nginx

nobody 85454 85453 0 00:03 ? 00:00:36 nginx: worker process root 97907 56681 0 21:18 pts/22 00:00:00 grep --color=auto nginx
查找到对应work pid为85454

# perf record -e sched:sched_stat_sleep -e sched:sched_switch -e sched:sched_process_exit -p 85454 -g -o perf.data.raw sleep 10
# perf inject -v -s -i perf.data.raw -o perf.data
8.4.3 生成off-cpu火焰图

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

v2-93c1f53846bde1be46bab519a1c1d9ce_1440w.webp

参考

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值