利用进程热点追踪快速定位性能瓶颈

还在为线上问题难以定位而苦恼?本文介绍了如何利用进程热点追踪技术,结合Sysom工具,快速定位并解决高负载、网络超时和CPU占用高等性能瓶颈问题。

原文标题:怎样快速定位进程性能瓶颈?

原文作者:阿里云开发者

冷月清谈:

本文深入探讨了进程热点追踪技术在解决系统性能瓶颈方面的应用。首先阐述了进程热点概念及其重要性,随后分析了传统问题定位方法在复杂云原生环境下的局限性,突出了快速、准确追踪进程热点的必要性。文章详细介绍了利用操作系统控制台进行进程热点追踪的解决方案,包括栈回溯和符号解析的关键技术,并对比了不同方案的优劣,展示了Sysom集成的多种方案如何应对各种场景。通过对Sysom整体架构和前端展示的介绍,展示了如何通过该工具进行热点分析和对比。最后,通过三个实际案例,展示了如何利用Sysom进程热点追踪快速定位高负载、网络超时和CPU占用高等线上问题,为开发和运维人员提供了实用的参考。

怜星夜思:

1、文章提到了多种栈回溯方案,例如perf、eBPF 和语言级别的接口,在实际生产环境中,你会如何选择和组合这些方案?有没有遇到过某种方案失效的情况,又是如何解决的?
2、文章中提到了 Sysom 进程热点追踪可以定位到锁的持有者,这个功能感觉很实用。但是,如果锁的持有者是一个非常短暂的操作,或者锁竞争非常激烈,导致火焰图很难分辨,有什么技巧可以更准确地找到锁的持有者?
3、文章中多次提到火焰图,对于不熟悉火焰图的同学来说,可能不太容易理解。你觉得如何向一个刚接触性能分析的同学,简单明了地解释火焰图的含义和使用方法?

原文内容

图片

阿里妹导读


这篇文章详细介绍了进程热点追踪的概念、业务痛点、解决方案以及实际案例分析,旨在帮助开发者和运维人员快速定位和解决系统性能瓶颈问题。


一、背景

进程热点某个进程或进程中的某些部分(如函数、代码段、线程等)占用大量系统资源(如CPU时间、内存、磁盘I/O等),或者执行频率非常高,从而成为系统性能瓶颈或资源消耗的重点区域。它是性能分析和优化中的一个重要概念,帮助开发者和运维人员快速定位系统中的关键问题区域。

进程热点追踪是性能分析中的关键概念,通过性能分析工具和可视化手段(如火焰图),可以快速定位系统中的性能瓶颈和资源消耗热点,从而为优化和故障排查提供有力支持。在现代复杂的系统环境中,掌握进程热点分析技能对于提升系统性能和稳定性至关重要。

二、为什么需要追踪进程热点

2.1 进程性能瓶颈影响处理速度

在现代复杂的云原生和容器化部署环境中,业务系统面临着诸多性能瓶颈带来的痛点。一方面,进程性能瓶颈可能导致系统响应时间显著增加,尤其是在高并发场景下,请求处理速度变慢,直接影响体验。另一方面,某些进程占用大量系统资源(如CPU、内存),导致系统负载过高,甚至出现服务不可用的情况。因此,解决性能瓶颈问题是保障系统顺畅进行的关键因素。而通过进程热点追踪技术,可以有效定位和解决这些问题。进程热点追踪能够通过生成调用图谱和热点分析,直观展示资源消耗较高的部分,帮助开发和运维人员快速识别性能瓶颈。

2.2 偶发抖动,根因难以追溯

线上问题往往具有偶发性或短暂性,当问题出现时,等到手动执行`perf`等诊断工具,可能已经错过了最佳诊断时机。因此,我们通过一系列先进的技术手段,实现了进程热点追踪的常态化采集,为了捕捉到问题发生时的第一现场。这一采集机制对系统资源的消耗极低,几乎不会对正常业务运行产生影响。借助Sysom进程热点追踪功能,当业务出现历史抖动或其他异常时,无需人工手动登录机器或实时值守,即可随时查看过往时间段内进程的运行状态,快速定位问题根源。

2.3 定位周期长,业务潜在问题仍然存在

在面对业务问题时,由于缺乏有效的分析手段,很多情况下只能采用临时的“止血”方案来缓解症状,但问题的根源并未得到解决,业务问题仍然存在。由于无法清晰地了解问题的根因,系统可能长期处于不稳定状态,甚至濒临崩溃的边缘。为此,Sysom 进程热点追踪功能提供了丰富的分析手段。它不仅涵盖了传统的技术方法,如调用链分析、对比分析等,还引入了先进的 AI 智能诊断功能。AI 智能诊断融合了过往历史问题的经验和数据,能够快速定位问题的根源,并直接提供清晰的诊断结果,从根本上提升系统的稳定性和可靠性。

三、解决方案:用操作系统控制台诊断

进程热点持续追踪生成进程热点主要是下面三个步骤:

1. 栈回溯:获取内核态和用户态的详细调用栈信息,此时只是包含调用链及地址信息;

2. 符号解析:将调用栈中的内核地址和用户态地址解析为用户易于理解的函数名称;

3. 火焰图生成:通过火焰图的形式直观地展示调用栈数据。

图片

接下来,我们将深入探讨栈回溯和符号解析的策略,并详细阐述Sysom所采纳的具体方案。

注:Sysom是操作系统控制台的运维组件。

3.1 方案介绍

(1)栈回溯方案分析

栈回溯主要是获取当前程序的完整调用栈,它是生成火焰图的首要且关键的步骤,这一过程中存在两个主要的技术挑战:

1. 无fp(frame pointer,帧指针)的应用程序:为了优化性能,众多程序选择不保留fp。这导致我们无法依赖传统的基于fp回溯的方法来获取调用栈信息。因此,我们必须转而采用基于dwarf的更为复杂的栈回溯技术。

2. Java、Python等解释型语言的栈回溯:这些解释型或高级编程语言各自拥有独特的栈帧结构。因此,关键在于识别当前运行的程序,并准确解析出相应的栈帧信息。

为了应对这两个挑战,Sysom利用eBPF的编程灵活性,支持无fp的应用以及解释型语言的栈回溯功能。除了eBPF,其他主流的栈回溯方案包括perf和语言级别的接口(例如Java提供的JVM TI)。以下是对这三种栈回溯方案的对比分析。

图片

从上表中我们可以观察到不同方案各自的优势和局限性。

1)Perf作为一个历史悠久的性能分析工具,它支持所有版本的内核,但在处理动态语言方面表现不足。此外,当使用基于dwarf的栈回溯时,由于需要将整个用户态栈空间输出到用户态程序,这会导致较大的资源消耗。

2)eBPF的可编程性为新型栈回溯方案开辟了广阔的可能性,尤其是在支持动态语言栈回溯方面,通过分析代码运行时信息,能够完整解析出Java、Python等动态语言的调用栈,充分展现了eBPF的灵活性。唯一的限制是它对内核版本有一定的要求。

3)至于语言级别的采样工具,如async-profiler,它们能够利用JVM提供的接口来收集栈信息,不依赖于内核版本,且资源消耗较低。然而,由于它们是进程级别的侵入式采样,存在极低概率导致业务应用崩溃,因此在稳定性方面存在不足。

对于Sysom而言,其设计目标是能在生产环境中持续稳定运行。因此,在确保功能完整性的基础上,稳定性被视为最重要的因素。为了实现这一目标,Sysom集成了三种不同的方案,以便在各种场景下都能提供完善的功能支持。在底层的决策逻辑中,eBPF被设定为最优先选项,其次是perf,最后是语言级接口。下面的图表展示了根据不同编程语言和内核版本选择的栈回溯方案。

(2)符号解析方案分析

符号解析主要是将对应的地址转换成函数名,一般地,对于编译型语言的应用可以通过查找elf文件的符号表即可完成,对于解释型语言需要从进程内存中读取符号。这些是符号解析所需要解决的技术问题。从架构方案来看,存在两种方案选择:

图片

从上表中可以观察到,本地符号解析依赖较少,由于需要进行符号缓存以加速查找速度,这会导致较大的内存占用。此外,由于大多数业务应用在生产环境中部署时不包含debuginfo,可能会出现符号缺失,进而影响符号的准确性。相比之下,远程符号解析的部署依赖会多些,例如依赖网络传输调用栈信息,但它不需要在业务机器上缓存符号,因此内存占用较低。同时,远程解析可以从类似yum源的地方下载应用的debuginfo包,以获得更完整的符号信息。

本地解析更适合于单台机器的性能剖析,而远程解析更适合于集群和大规模部署,能够显著降低整体开销。例如,如果集群内部署的是同一版本的MySQL应用,那么只需建立一个全局的符号缓存,从而减少资源消耗。鉴于本地和远程符号解析各有优势,Sysom同时支持这两种方案。

3.2 整体架构

下面的架构图是由底层组件Coolbpf profiler至前端的完整系统结构,它由三个主要部分组成:控制台前端、Sysom Agent 和 Coolbpf profiler,其中SysOM是智能运维平台,Coolbpf是eBPF采集工具,Sysom Agent 负责启动Coolbpf功能及数据通信。

图片


下面是对每个部分的详细介绍:

1)控制台前端:这是用户与系统交互的界面,提供了性能分析的可视化功能。包含三个主要功能模块:

  • 热点分析:分析并展示程序中性能瓶颈的热点区域。

  • 热点对比:允许用户比较不同实例、不同时间点或不同条件下的性能热点变化。

  • CPU&GPU 热点图:提供CPU和GPU的性能热点图,帮助用户识别GPU性能问题。

2)Sysom Agent:作为中间层,Sysom Agent 负责收集和处理性能数据,并将结果发送到前端。包含四个热点模块:

  • OnCpu热点:检测CPU上的热点问题。

  • OffCpu热点:检测进程为什么被阻塞。

  • 内存热点:识别内存使用中的热点区域。

  • 锁热点:分析并报告锁竞争导致的性能问题。

3)Coolbpf profiler:这是底层的通用性能分析库,为Sysom Agent提供支持。包含两个主要部分:

  • eBPF&perf栈回溯:①利用eBPF技术在内核态进行调用栈的捕获和分析,支持多种编程语言,如C/C++/Rust/GoLang,以及Java/Python/Luajit;②使用perf工具进行调用栈的捕获,这包括原生代码的符号解析和基于perf的C/C++/Rust/GoLang的调用栈分析。

  • 用户态符号解析:处理用户态程序的符号信息,包括编译型程序的符号表和解释型或高级语言运行时符号。

3.3 前端展示

本节将重点介绍热点分析和热点对比前端界面使用方法。

(1)热点分析

热点分析的大致步骤如下:

1)参数选择:依次选择实例ID、进程名、热点类型及时间范围。最后点击“执行热点追踪”按钮。需要注意的是热点类型是动态的,也就是会根据当前时间段该进程包含哪些热点类型来进行渲染,比如只包含OnCpu,那么热点类型下拉列表就只有OnCpu。

图片

2)OnCpu热点:我们选择OnCpu后,就会立即渲染出如下图所示的OnCpu的火焰图;

图片

3)内存热点:我们选择“内存”后,就会立即渲染出如下图所示内存占用的火焰图;

图片


(2)热点对比

热点对比对于分析正常环境和异常环境是一大杀器,能够精准的分析出差异,进而确定问题根因。使用步骤大致如下:

1)参数选择:相比热点分析只需要选择一个机器实例,热点对比功能则需要两个机器实例,参数选择完毕后,点击“执行对比分析”按钮,则可触发生成对比火焰图。

图片

2)内存差分火焰图:下图是内存热力类型的差分火焰图,由于我们选择的机器实例、进程、时间段都是一致的,所以差分火焰图最后呈现都是灰色,表示不存在热点差异。

图片


3.4 案例分析

接下来将通过三个实际的案例来介绍下Sysom进程热点追踪如何快速地帮助我们定位线上问题。

案例一:load高

load高是线上经常遇到的问题类别,load高问题也有很多类型。本案例重点介绍在定位因sys高导致的load高问题。最近,遇到了某客户出现每隔一段时间就会出现load飙高,比正常时间段内高十余倍。下面让我一起看看如何通过Sysom进程热点追踪快速定位该问题。

从下面的cpu热点分布图上,我们能够看到,在CPU热点图在14:15附近有突增,和load飙高的时间点基本吻合。

图片

缩小观察时间轴,选取cpu热点最高的时间段,我们看到以下的热点图。

图片

从热点火焰图上我们看到热点最高的是native_queued_spin_lock_slowpath函数,初步判断这是等锁,是受害者第一现场,点击热点函数,火焰图中可以高亮显示。

图片

可以一看到,绝大多数进程都是在等锁。那么我们怎么从这些热点中找到锁的持有者呢?

我们先从火焰图上看看,大家在等什么锁,是否在等同一把锁?

通过点击火焰图热点块,可以放大某一个调用栈。

图片

通过分析do_wait函数,其内部会在等tasklist_lock read锁。

图片

通过分析do_exit函数,其内部也是在等tasklist_lock write锁。

图片

依葫芦画瓢,随机再找几个,发现大家都是在等tasklist_lock 锁,有等read锁的,也有等write锁的。

接下来就是要看看,能否通过进程热点追踪快速找到锁的持有者了。

tasklist_lock锁的设计是一把关中断才会去拿的rw锁,理论上讲是会被一个内核线程独占持有的,直到主动退出临界区,才会主动释放锁。基于这个原理,我们从火焰图上应该是能够找到一根柱子独占了这把锁。这里利用liveTrace提供的函数调用关系,我们逐个看下热点top函数的调用关系图。

图片

选择top1,看到都是在等tasklist_lock锁的进程信息。

图片

选择top2,是在做进程退出的函数,大部分是在做等tasklist_lock锁的热点上。

图片

选择top3,通过分析copy_process函数,其会在等tasklist_lock write锁。

图片

选择top4,这里看到_cond_resched 主要是被do_exitcopy_process调用的。

图片

选择top5,关键来了,我们期待的光秃秃的一根柱子终于出现了,结合内核的tg_rt_schedulable代码,我们知道该调用栈上是持有了tasklist_lock read锁的,也就是持有所的关键调用栈被找到。

案例二:网络超时

某客户反馈业务网络偶发超时,通过查看进程热点追踪,发现热点在nft_do_chain。下图是热点追踪显示某个时刻网络超时时的函数表和火焰图。

图片

nft_do_chain是处理netfilter规则的函数,很容易想到是netfilter规则过多导致网络协议栈处理速度变慢。通过nft list ruleset查看发现存在12000+条规则。

图片

案例三:进程CPU占用高

某客户机器上面的shell脚本CPU占用高。下图展示了捕获的进程热点火焰图。通过火焰图,我们可以发现热点主要集中在 shell_execve,即在解析 Shell 脚本命令的过程中。因此,可以推测当前Shell脚本可能陷入了异常的死循环,这导致 Shell 解释器在解析和执行 Shell 命令时出现问题。因此,下一步需要定位当前 Shell 脚本中异常的命令。

图片

我们了解到,Shell 脚本是通过 execve 系统调用来执行 Shell 命令的,因此我们使用 strace 来跟踪 execve 的调用。从下面的结果图中可以看出,该脚本正在不断重复执行 ps 和 awk 命令。然后在shell脚本源码内搜索ps和awk,很轻松定位到问题源码。

图片

通过 Sysom 进程热点追踪,我们初步评估了 Shell 解释器的运行状态,判断其进入了死循环并重复执行某条异常命令。接着,通过 strace 命令定位到了异常的 Shell 命令,并返回到脚本源码中确认了问题代码的位置。

使用 OS 控制台的过程中,有任何疑问和建议,您可以搜索群号94405014449 入钉钉群反馈。

阿里云操作系统控制台PC端链接(复制链接至浏览器打开):

https://alinux.console.aliyun.com/

简单来说,火焰图就像一个堆叠的条形图,每一层代表一个函数调用栈,横向宽度代表该函数在采样期间的 CPU 占用时间比例。火焰图的形状就像燃烧的火焰,越高的火焰表示调用越深,越宽的火焰表示 CPU 占用越高。使用方法就是,找到最宽的火焰,那就是性能瓶颈所在。

这个问题挺有意思,确实是个挑战。我的思路是,可以尝试结合 Sysom 提供的其他指标,比如线程状态、上下文切换等,看看能不能找到一些线索。另外,如果能拿到更详细的锁信息(比如锁的类型、持有时间等),应该能更容易定位到锁的持有者。感觉可以给 Sysom 提个 feature request,增加锁信息展示的功能。

我觉得这种问题有点像大海捞针,如果锁竞争太激烈,可能火焰图都糊成一团了。我的经验是,先别死磕火焰图,可以先看看系统的整体负载情况,比如 CPU 使用率、IO 等待等。如果负载很高,说明锁竞争可能确实是个问题。如果负载不高,可能问题出在其他地方。另外,有时候代码写得烂也是个问题 (手动狗头)。

记住一句话:“火焰喷得越高,占用 CPU 越多!” (手动滑稽) 其实火焰图就是把 CPU 的调用栈可视化了,让你一眼就能看出哪个函数最耗 CPU。 刚开始看可能会觉得眼花缭乱,但只要多看几个例子,很快就能上手了。 网上有很多火焰图的教程,可以找来看看。

我一般会优先考虑eBPF,因为它够灵活,能处理很多动态语言的栈回溯。如果内核版本不支持,或者遇到一些奇奇怪怪的 corner case,才会考虑 perf。语言级别的接口我用的比较少,主要是担心稳定性问题。之前遇到过 eBPF 在某些特定场景下回溯不完整,后来是通过 perf 结合手动分析才找到问题根源的。所以,多准备几个方案总是没错的。

这个问题问的好!说实话,我之前对这些方案没有这么深入的了解。看了这篇文章,感觉 eBPF 确实是个好东西,但是内核版本是个问题啊。我现在的方案是,先用 perf 试试,不行就抓包分析,再不行就只能靠猜了 (手动狗头)。看来以后得好好研究一下 eBPF 了,争取早日用上高大上的技术!

你可以把火焰图想象成一棵树,树根是最顶层的函数,树叶是最底层的函数。每一片树叶的宽度代表这个函数消耗的 CPU 时间。如果一片树叶很宽,说明这个函数很耗时,可能就是性能瓶颈。另外,火焰图是可以点击的,点击某一片火焰,就可以放大显示该函数的调用栈,方便你进一步分析。

在生产环境中的选择,我会更看重方案的稳定性与全面性。首选是语言级别的接口,例如Java的async-profiler,因为它对应用的侵入性较小,性能监控也比较精确。如果需要更底层的系统调用分析,我会考虑perf,虽然资源消耗稍大,但适用性广。eBPF虽然强大,但对内核版本有要求,需要评估兼容性。曾经遇到perf在解析某些动态链接库时符号丢失,导致火焰图不完整,最后通过配置perf的symbol路径解决了问题。

如果锁的持有者操作很短,或者锁竞争激烈导致火焰图难以分辨,可以尝试以下方法:
1. 提高采样频率: 增加采样频率可以更细粒度地捕捉到锁的持有者,但也会增加系统开销,需要在性能和精度之间权衡。
2. 指定监控线程: 如果知道某些线程可能持有锁,可以针对这些线程进行重点监控,减少干扰。
3. 结合其他工具: 可以结合perf lock等工具,专门用于分析锁竞争,提供更详细的锁信息。
4. 分析调用栈: 仔细分析火焰图中的调用栈,找到持有锁的函数,并追踪其调用者。
5. 调整时间窗口: 尝试调整时间窗口,选择锁竞争最激烈的时间段进行分析。