什么是火焰图?
确定 CPU 繁忙的原因是性能分析的一项重要工作,通常涉及分析堆栈跟踪。通过以固定速率采样进行分析是一种粗略但有效的方法,可以查看哪些代码路径很热(CPU 上繁忙)。它通常通过创建一个定时中断来工作,该中断收集当前程序计数器、函数地址或整个堆栈回溯,并在打印摘要报告时将这些内容转换为人类可读的内容。
分析数据可能长达数千行,并且难以理解。火焰图是采样堆栈跟踪的可视化,可以快速识别热代码路径。
火焰图分类
火焰图原理
在使用火焰图之前,我们需要理解火焰图背后的原理。
程序性能和 CPU 的关系
程序的性能问题可以分为以下两种类型之一:
On-CPU :线程或者 task 在 CPU 上花费较多时间运行,这里的时间既包括用户态时间,也包括因为系统调用产生的内核态时间;
Off-CPU:线程或者 task 在 I/O、锁、计时器、分页/交换等上阻塞时产生的等待时间。
下图很好阐释了线程(task)状态的变化。
![]()
图一 线程状态图
On-CPU 相对来讲比较好理解,而 Off-CPU 分析是一种性能方法,其中测量和研究 Off-CPU 时间以及堆栈跟踪等上下文。它与 On-CPU 分析不同,后者仅检查线程是否在 CPU 上执行。这里,目标是被阻塞和脱离 CPU 的线程状态,如上图中蓝色所示。
Off-CPU 分析与 On-CPU 分析是互补的,因此可以了解 100% 的线程时间。此方法还不同于跟踪通常阻塞的应用程序功能的跟踪技术,因为此方法针对的是内核调度程序的阻塞概念,这是一种方便的包罗万象的方法。
线程可以出于多种原因离开 CPU,包括 I/O 和锁,但也有一些与当前线程执行无关的原因,包括由于对 CPU 资源的高需求而导致的非自愿上下文切换和中断。无论是什么原因,如果这种情况发生在工作负载请求(同步代码路径)期间,就会引入延迟。
CPU Sampling
许多传统的分析工具使用跨所有 CPU 的活动的定时采样,以特定的时间间隔或速率(例如,99 HZ)收集当前指令地址(程序计数器)或整个堆栈回溯的快照。这将给出正在运行的函数或堆栈跟踪的计数,从而可以合理地估计 CPU 周期的消耗情况。在Linux 上,可以使用 perf 工具在采样模式下(例如 -F 99)执行定时 CPU 采样。
考虑应用程序函数 A() 调用函数 B(),这会进行阻塞系统调用:
![]()
On-CPU 的数据采样(包括热代码路径和自适应 mutex spin)非常有效,但当应用程序阻塞并等待脱离 CPU 时,perf 将不会收集到任何数据(所以 Off-CPU 需要通过其他方式来采样数据)。
Application Tracing
![]()
这里对函数进行了检测(instrumented),以便在函数开始和结束时收集时间戳,从而可以计算函数运行所花费的时间。如果时间戳包括经过的时间和 CPU 时间(例如,使用 times(2) 或 getrusage(2)),那么还可以计算哪些函数在 CPU 上运行缓慢,哪些函数因为在 CPU 外被阻止(blocked)而运行缓慢 。与采样不同,这些时间戳可以具有非常高的分辨率(纳秒)。
虽然这种方法有效,但缺点是您要么跟踪所有应用程序功能,这可能会对性能产生重大影响(并影响您尝试测量的性能),要么您选择可能阻塞的功能,并希望您没有这样做。不要错过任何一个。
Off-CPU Tracing
![]()
我们可以仅跟踪将线程从 CPU 切换出来的内核函数,以及时间戳和用户态堆栈跟踪。这里的重点是关注脱离 CPU 的事件,不需要跟踪所有应用程序功能,也不需要知道应用程序是什么。这种方法适用于任何阻塞事件、任何应用程序:MySQL、Apache、Java 等。Off-CPU 跟踪将捕获任何应用程序的所有等待事件。
Off-CPU Sampling
使用定时采样来捕获未在 CPU 上运行的线程中阻塞的堆栈跟踪。它还可以通过实时分析器来完成:一种始终对所有线程进行采样的分析器,无论它们是在 CPU 上还是在 CPU 外。然后可以对实时配置文件输出进行过滤,以仅查找离开 CPU 的堆栈。
系统分析器很少使用 Off-CPU 的采样。采样通常作为每个 CPU 计时器中断来实现,然后检查当前正在运行的被中断的进程:生成 CPU 上的配置文件。脱离 CPU 的采样器必须以不同的方式工作:要么在每个应用程序线程中设置计时器以唤醒它们并捕获堆栈,要么让内核按一定时间间隔遍历所有线程并捕获它们的堆栈。
如何读懂火焰图
On CPU 火焰图
https://queue.acm.org/downloads/2016/Gregg4.svg
![]()
y 轴表示调用栈,每一层都是一个函数。调用栈越深,火焰就越高,顶部就是正在执行的函数,下方都是它的父函数。
x 轴表示抽样数,如果一个函数在 x 轴占据的宽度越宽,就表示它被抽到的次数多,即执行的时间长。注意,x 轴不代表时间,而是所有的调用栈合并后,按字母顺序排列的。
火焰图就是看顶层的哪个函数占据的宽度最大。只要有"平顶"(plateaus),就表示该函数可能存在性能问题。
颜色没有特殊含义,因为火焰图表示的是 CPU 的繁忙程度,所以一般选择暖色调。火焰图是 SVG 图片,可以与用户互动。
(1)鼠标悬浮
火焰的每一层都会标注函数名,鼠标悬浮时会显示完整的函数名、抽样抽中的次数、占据总抽样次数的百分比。下面是一个例子:
(2)点击放大
在某一层点击,火焰图会水平放大,该层会占据所有宽度,显示详细信息。
(3)搜索
按下 Ctrl + F 会显示一个搜索框,用户可以输入关键词或正则表达式,所有符合条件的函数名会高亮显示。
Off-CPU 火焰图
https://www.brendangregg.com/FlameGraphs/off-mysqld1.svg
![]()
宽度与代码路径中的总时间成正比,因此首先寻找最宽的塔以了解最大的延迟来源。从左到右的排序没有意义,y轴是堆栈深度。
分析的时候,我们可以采用如下策略:
先找到时间占比比多多的调用,然后依次往下看
schedule 意味着 task 将被调度出 CPU
vfs_read 代表文件系统的读操作,最终会涉及到 IO 操作(异步,通过中断来通知),这个就会让线程(task)调度出 CPU。
类似的,包括文件系统的读、写,网络读、写,epoll_wait,申请锁的操作
遇到不了解的 kernel 方法,可以查看一下 https://docs.kernel.org/ ,以及 https://man7.org/linux/man-pages/man2/syscall.2.html 等相关文档。
系统调用
安装内核源代码
查找 syscall.h
获取内核源代码
浏览文档:
当然了,也可以做全文检索。
参考资料
https://www.brendangregg.com/offcpuanalysis.html
https://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html
https://www.brendangregg.com/FlameGraphs/offcpuflamegraphs.html
https://www.ruanyifeng.com/blog/2017/09/flame-graph.html
确定 CPU 繁忙的原因是性能分析的一项重要工作,通常涉及分析堆栈跟踪。通过以固定速率采样进行分析是一种粗略但有效的方法,可以查看哪些代码路径很热(CPU 上繁忙)。它通常通过创建一个定时中断来工作,该中断收集当前程序计数器、函数地址或整个堆栈回溯,并在打印摘要报告时将这些内容转换为人类可读的内容。
分析数据可能长达数千行,并且难以理解。火焰图是采样堆栈跟踪的可视化,可以快速识别热代码路径。
火焰图分类
火焰图类型 |
横轴含义 |
纵轴含义 |
解决问题 |
采样方式 |
---|---|---|---|---|
cpu 火焰图 | cpu占用时间 | 调用栈 |
|
固定频率采样 cpu 调用栈 |
off-cpu 火焰图 | 阻塞时间 | 调用栈 |
|
固定频率采样 阻塞事件调用栈 |
内存火焰图 | 内存申请/释放函数调用次数 | 调用栈 |
|
有四种方式:
|
Hot/Cold 火焰图 | on-CPU 火焰图和 off-CPU 火焰图结合在一起综合展示 | 调用栈 |
|
on-CPU 火焰图和 off-CPU 火焰图结合 |
火焰图原理
在使用火焰图之前,我们需要理解火焰图背后的原理。
程序性能和 CPU 的关系
程序的性能问题可以分为以下两种类型之一:
On-CPU :线程或者 task 在 CPU 上花费较多时间运行,这里的时间既包括用户态时间,也包括因为系统调用产生的内核态时间;
Off-CPU:线程或者 task 在 I/O、锁、计时器、分页/交换等上阻塞时产生的等待时间。
下图很好阐释了线程(task)状态的变化。
图一 线程状态图
On-CPU 相对来讲比较好理解,而 Off-CPU 分析是一种性能方法,其中测量和研究 Off-CPU 时间以及堆栈跟踪等上下文。它与 On-CPU 分析不同,后者仅检查线程是否在 CPU 上执行。这里,目标是被阻塞和脱离 CPU 的线程状态,如上图中蓝色所示。
Off-CPU 分析与 On-CPU 分析是互补的,因此可以了解 100% 的线程时间。此方法还不同于跟踪通常阻塞的应用程序功能的跟踪技术,因为此方法针对的是内核调度程序的阻塞概念,这是一种方便的包罗万象的方法。
线程可以出于多种原因离开 CPU,包括 I/O 和锁,但也有一些与当前线程执行无关的原因,包括由于对 CPU 资源的高需求而导致的非自愿上下文切换和中断。无论是什么原因,如果这种情况发生在工作负载请求(同步代码路径)期间,就会引入延迟。
CPU Sampling
许多传统的分析工具使用跨所有 CPU 的活动的定时采样,以特定的时间间隔或速率(例如,99 HZ)收集当前指令地址(程序计数器)或整个堆栈回溯的快照。这将给出正在运行的函数或堆栈跟踪的计数,从而可以合理地估计 CPU 周期的消耗情况。在Linux 上,可以使用 perf 工具在采样模式下(例如 -F 99)执行定时 CPU 采样。
考虑应用程序函数 A() 调用函数 B(),这会进行阻塞系统调用:
On-CPU 的数据采样(包括热代码路径和自适应 mutex spin)非常有效,但当应用程序阻塞并等待脱离 CPU 时,perf 将不会收集到任何数据(所以 Off-CPU 需要通过其他方式来采样数据)。
Application Tracing
这里对函数进行了检测(instrumented),以便在函数开始和结束时收集时间戳,从而可以计算函数运行所花费的时间。如果时间戳包括经过的时间和 CPU 时间(例如,使用 times(2) 或 getrusage(2)),那么还可以计算哪些函数在 CPU 上运行缓慢,哪些函数因为在 CPU 外被阻止(blocked)而运行缓慢 。与采样不同,这些时间戳可以具有非常高的分辨率(纳秒)。
虽然这种方法有效,但缺点是您要么跟踪所有应用程序功能,这可能会对性能产生重大影响(并影响您尝试测量的性能),要么您选择可能阻塞的功能,并希望您没有这样做。不要错过任何一个。
Off-CPU Tracing
我们可以仅跟踪将线程从 CPU 切换出来的内核函数,以及时间戳和用户态堆栈跟踪。这里的重点是关注脱离 CPU 的事件,不需要跟踪所有应用程序功能,也不需要知道应用程序是什么。这种方法适用于任何阻塞事件、任何应用程序:MySQL、Apache、Java 等。Off-CPU 跟踪将捕获任何应用程序的所有等待事件。
Off-CPU Sampling
使用定时采样来捕获未在 CPU 上运行的线程中阻塞的堆栈跟踪。它还可以通过实时分析器来完成:一种始终对所有线程进行采样的分析器,无论它们是在 CPU 上还是在 CPU 外。然后可以对实时配置文件输出进行过滤,以仅查找离开 CPU 的堆栈。
系统分析器很少使用 Off-CPU 的采样。采样通常作为每个 CPU 计时器中断来实现,然后检查当前正在运行的被中断的进程:生成 CPU 上的配置文件。脱离 CPU 的采样器必须以不同的方式工作:要么在每个应用程序线程中设置计时器以唤醒它们并捕获堆栈,要么让内核按一定时间间隔遍历所有线程并捕获它们的堆栈。
如何读懂火焰图
On CPU 火焰图
https://queue.acm.org/downloads/2016/Gregg4.svg
y 轴表示调用栈,每一层都是一个函数。调用栈越深,火焰就越高,顶部就是正在执行的函数,下方都是它的父函数。
x 轴表示抽样数,如果一个函数在 x 轴占据的宽度越宽,就表示它被抽到的次数多,即执行的时间长。注意,x 轴不代表时间,而是所有的调用栈合并后,按字母顺序排列的。
火焰图就是看顶层的哪个函数占据的宽度最大。只要有"平顶"(plateaus),就表示该函数可能存在性能问题。
颜色没有特殊含义,因为火焰图表示的是 CPU 的繁忙程度,所以一般选择暖色调。火焰图是 SVG 图片,可以与用户互动。
(1)鼠标悬浮
火焰的每一层都会标注函数名,鼠标悬浮时会显示完整的函数名、抽样抽中的次数、占据总抽样次数的百分比。下面是一个例子:
|
(2)点击放大
在某一层点击,火焰图会水平放大,该层会占据所有宽度,显示详细信息。
(3)搜索
按下 Ctrl + F 会显示一个搜索框,用户可以输入关键词或正则表达式,所有符合条件的函数名会高亮显示。
Off-CPU 火焰图
https://www.brendangregg.com/FlameGraphs/off-mysqld1.svg
宽度与代码路径中的总时间成正比,因此首先寻找最宽的塔以了解最大的延迟来源。从左到右的排序没有意义,y轴是堆栈深度。
分析的时候,我们可以采用如下策略:
先找到时间占比比多多的调用,然后依次往下看
schedule 意味着 task 将被调度出 CPU
vfs_read 代表文件系统的读操作,最终会涉及到 IO 操作(异步,通过中断来通知),这个就会让线程(task)调度出 CPU。
类似的,包括文件系统的读、写,网络读、写,epoll_wait,申请锁的操作
遇到不了解的 kernel 方法,可以查看一下 https://docs.kernel.org/ ,以及 https://man7.org/linux/man-pages/man2/syscall.2.html 等相关文档。
系统调用
安装内核源代码
|
查找 syscall.h
|
获取内核源代码
|
浏览文档:
|
当然了,也可以做全文检索。
参考资料
https://www.brendangregg.com/offcpuanalysis.html
https://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html
https://www.brendangregg.com/FlameGraphs/offcpuflamegraphs.html
https://www.ruanyifeng.com/blog/2017/09/flame-graph.html