让我们回想一下,曾经作为编程新手的我们是如何调优程序的?通常是在没有数据的情况下依靠主观臆断来瞎蒙,稍微有些经验的同学则会对差异代码进行二分或者逐段调试。这种定位问题的方式不仅耗时耗力,而且还不具有通用性,当遇到其他类似的性能问题时,需要重复踩坑、填坑,那么如何避免这种情况呢? 俗语有曰:兵欲善其事必先利其器,个人认为,程序员定位性能问题也需要一件“利器”。如同医生给病人看病,需要依靠专业的医学工具(比如 X 光片、听诊器等)进行诊断,最后依据医学工具的检验结果快速精准的定位出病因所在。性能调优工具(比如 perf / gprof 等)之于性能调优就像 X 光之于病人一样,它可以一针见血的指出程序的性能瓶颈。 但是常用的性能调优工具 perf 等,在呈现内容上只能单一的列出调用栈或者非层次化的时间分布,不够直观。这里我推荐大家配合使用火焰图,它将 perf 等工具采集的数据呈现得更为直观。 初识火焰图火焰图(Flame Graph)是由 Linux 性能优化大师 Brendan Gregg 发明的,和所有其他的 profiling 方法不同的是,火焰图以一个全局的视野来看待时间分布,它从底部往顶部,列出所有可能导致性能瓶颈的调用栈。 火焰图整个图形看起来就像一个跳动的火焰,这就是它名字的由来。 火焰图有以下特征(这里以 on-cpu 火焰图为例):
火焰图类型常见的火焰图类型有 On-CPU,Off-CPU,还有 Memory,Hot/Cold,Differential 等等。他们分别适合处理什么样的问题呢? 这里笔者主要使用到的是 On-CPU、Off-CPU 以及 Memory 火焰图,所以这里仅仅对这三种火焰图作比较,也欢迎大家补充和斧正。 火焰图分析技巧
如何绘制火焰图?要生成火焰图,必须要有一个顺手的动态追踪工具,如果操作系统是 Linux 的话,那么通常通常是 perf 或者 systemtap 中的一种。其中 perf 相对更常用,多数 Linux 都包含了 perf 这个工具,可以直接使用;SystemTap 则功能更为强大,监控也更为灵活。网上关于如何使用 perf 绘制火焰图的文章非常多而且丰富,所以本文将以 SystemTap 为例。 SystemTap 是动态追踪工具,它通过探针机制,来采集内核或者应用程序的运行信息,从而可以不用修改内核和应用程序的代码,就获得丰富的信息,帮你分析、定位想要排查的问题。SystemTap 定义了一种类似的 DSL 脚本语言,方便用户根据需要自由扩展。不过,不同于动态追踪的鼻祖 DTrace ,SystemTap 并没有常驻内核的运行时,它需要先把脚本编译为内核模块,然后再插入到内核中执行。这也导致 SystemTap 启动比较缓慢,并且依赖于完整的调试符号表。 使用 SystemTap 绘制火焰图的主要流程如下:
本文演示步骤将会基于操作系统 Tlinux 2.2 安装 SystemTap 以及 操作系统符号调试表使用 yum 工具安装 systemtap: yum install systemtap systemtap-runtime 由于 systemtap 工具依赖于完整的调试符号表,而且生产环境不同机器的内核版本不同(虽然都是Tlinux 2.2版本,但是内核版本后面的小版本不一样,可以通过 uname -a 命令查看)所以我们还需要安装 kernel-debuginfo 包、 kernel-devel 包 我这里是安装了这两个依赖包
根据自己所需绘制的火焰图类型以及进程类型选择合适的脚本使用 SystemTap 统计相关数据往往需要自己依照它的语法,编写脚本,具有一定门槛。幸运的是,github 上春哥(agentzh)开源了两组他常用的 SystemTap 脚本: 我们这里需要绘制 off-cpu 火焰图,所以使用 sample-bt-off-cpu 脚本即可 生成内核模块现在我们有了统计脚本,也安装好了 systemtap,正常来说就可以使用了,但由于 systemtap 是通过生成内核模块的方式统计相关探针的统计数据,而 tlinux 要求所有运行的内核模块需要先到 tlinux 平台签名才可以运行,所以: 故需要先修改 off-cpu 脚本,让其先生成内核模块;之后对该内核模块作签名;最后使用 systemtap 命令手工运行该脚本,统计监控数据 Systemtap 执行流程如下:
所以我们这里修改下 off-cpu 的 stap 脚本,让其只运行完第四阶段,只生成一个内核模块 // 在 stap 命令后增加 -p4 参数,告诉systemtap,当前只需要执行到第四阶段open my $in, '|stap -p4 --skip-badvars --all-modules -x $pid -d '$exec_path' --ldd $d_so_args $stap_args -'or die 'Cannot run stap: $!\n'; 修改好之后运行脚本,会生成一个内核模块
生成的内核模块名称形如 stap_xxxxx.ko模块名称 由于读者并不需要关心内核模块签名,故章节略过 运行内核模块统计数据内核模块签名完成后,便可以使用 staprun 命令手工运行相关内核模块了 命令: // 注意:签名脚本会将生产的内核模块重命名,需要将名字改回去……(脚本bug)staprun -x {进程号} {内核模块名} > demo.bt 值得注意的是,监控的进程要有一定负载 systemtap 才可以采集到相关数据,即在采集时,同时需要要有一定请求量(通常是自己构造请求,压测进程) 将统计数据转换成火焰图获得了统计数据 demo.bt 后,便可以使用火焰图工具绘制火焰图了 下载 FlameGraph,链接: https://github.com/brendangregg/FlameGraph 命令:
这样便获得了 off-cpu 火焰图: 看图说话趁热打铁,通过几张火焰图熟悉下如何使用火焰图 图片来自于春哥微博或者个人近期定位的问题 on-cpu 火焰图Apache APISIX QPS急剧下降问题Apache APISIX 是一个开源国产的高性能 API 网关,之前在进行选型压测时,发现当 Route 匹配不同场景下, QPS 急剧下降,在其 CPU (四十八核)占用率几乎达到100%的情况下只有几千 QPS,通过绘制火焰图发现,其主要耗时在一个 table 插入阶段(lj_cf_table_insert),分析代码发现是该 table 一直没有释放,每次匹配不中路由会插入数据,导致表越来越大,后续插入耗时过长导致 QPS 下降。 off-cpu 火焰图nginx 互斥锁问题这是一张 nginx 的 off-cpu 火焰图,我们可以很快锁定到 agent 监控上报断点问题这是一张 agent 的 off-cpu 火焰图,它是一个多线程异步事件模型,主线程处理各个消息,多个线程分别负责配置下发或者监控上报的职责。当前问题出现在监控上报性能差,无法在周期(一分钟)内完成监控数据上报,导致监控断点,通过 off-cpu 火焰图我们可以分析出,该上报线程花费了大量的时间使用 curl_easy_perform 接口收发 http 监控数据消息中。 依据火焰图将发送 http 消息的逻辑改为异步非阻塞后,该问题解决。 附录进阶阅读
FAQ使用 perf 或者 systemtap 的方式采集数据,会对后台服务有性能影响吗? 有,但是很小,可以基本忽略不计。 它们使用系统的探针或者使用一些自定义的动态探针进行数据采集,第一对代码无侵入性,它既不需要停止服务,也不需要修改应用程序的代码;第二,它们是以内核模块/内核原生的方式跟踪用户态和内核态的所有事件,并通过一系列优化措施,进行采样统计,对目标服务性能影响极小,大概在5%左右或者更低的性能损耗。相较于将进程运行在沙箱的 valgrind 工具或静态调试工具 gdb 来说,动态追踪 perf 或者 systemtap 或者 ebpf 的性能损耗基本可以忽略不计。 目标进程重启后,systemtap 是否需要重新生成内核模块? 不需要。甚至同一个 linux 内核版本下的同一个二进制进程(md5值一致),在安装 kernel 调试符号表后,便可以在生成采集指标的内核模块,并且可以多次使用。 当 linux 内核版本不一致,符号表有变化,需要重新生成内核模块;当目标进程二进制文件重新编译后,也需要重新生成统计用的 systemtap 内核模块。 |
|