PT_PERF: 基于 Intel PT 的时延性能分析工具

作者:谢榕彪  https://zhuanlan.zhihu.com/p/694283482

1 背景

1.1 常用性能分析方法

程序性能一直是开发人员和用户特别关注的指标,但其调优分析也是最为艰难的一个过程。作为系统研发人员,我们会花费大量时间来分析系统的性能瓶颈、定位开发过程中引入的性能回退,并解决可能周期性发生的性能抖动问题。

根据线程的执行模型,即是否调度在 CPU 上执行指令,我们通常会从 on-CPU 和 off-CPU 两个角度进行分析[1]。在 on-CPU 分析中,我们关注那些正在 CPU 上执行指令的线程,以识别 CPU 指令执行的热点。而 off-CPU 分析则关注那些不在 CPU 上执行指令的时间,比如等待磁盘 I/O、锁或网络传输等资源。

图片来源于 Extended BPFA New Type of Software, Brendan Gregg

CPU 是负责执行指令的核心部件。我们可以使用 perf 工具对 CPU 上执行的指令进行采样,通过 perf report 或者创建 on-CPU 的火焰图,从而了解大部分 CPU 执行的指令时间主要集中在哪些函数上。这些采样数据主要基于性能监控计数器(PMC)提供的硬件溢出中断(PMI),通过这种方式,CPU 周期性地向内核传递当前指令的位置(ip)。内核利用当前线程的栈帧或者程序的 dwarf 信息来构建函数调用栈。通过在一定频率下的采样,尤其是在 CPU 密集型负载下,我们可以确定大部分的性能瓶颈所在。

然而,在非 CPU 密集型的场景下,比如大量 IO 操作、等待锁或主动休眠等情况,CPU 利用率很低。在数据库场景中,这种情况非常常见,CPU 上的热点并不一定与性能瓶颈直接相关。可能大多数线程因为等待某个 mutex(互斥锁)而被调度出去。这时我们更多需要关注 off-CPU 上的瓶颈,可以通过如 pstack 打栈,将每个线程的瞬时函数调用栈打印并聚合起来,或者基于 ebpf 的 off-CPU 火焰图(通过记录线程 schedule 出去到回来的时间)来分析。当线程足够多,并且线程等待时间足够长,我们能判断出线程卡在何处。但找到谁卡住大部分线程是件比较困难的事,因为这可能只是一个短暂持有锁周期性的 on-CPU 任务,并且依赖于一定的代码经验。

在开发引入性能回退时,我们经常需要对比两套代码的开销差异,这个开销区别可能只是两个函数是否是内联的,精确到时间也只是数十纳秒的区别,因为调用频次高而引入了性能回退。在此之前我们最为习惯的是在编译前对程序埋点,但这费时费力,依赖于一定代码经验,并且修改代码也影响了程序的执行行为。我们也可以利用 uprobe 技术在程序运行时动态埋点,统计指定函数的时延,但内部统计过通常 uprobe 使能开销就是 2000 cpu cycles,并且在高频函数调用下,对程序性能的影响达到 50% 以上。

另一个令人头疼的问题是性能抖动排查,即程序在周期性地出现性能急剧下降,下降时间很短。例如,IO 延迟抖动等情况。我们使用的工具大多是基于平均值的统计方法,难以捕捉到短暂的程序状态。我们通常凭借代码经验,在代码中插入陷阱并打印日志来发现问题。

为了解决这些问题,我们希望能够在指令级别还原程序的执行过程,同时不对程序的正常执行过程产生影响。为了实现这一点,这依赖于硬件提供的程序 trace 功能。

1.2 Intel Processor Trace

Intel 在 Broadwell 之后的 CPU 架构引入了 Processor Trace 技术,通过专门的硬件,以较小的性能损失记录程序控制流信息,并将其编码压缩成一系列的 packet 流,packet 的内容包括:

  • 程序控制流信息,记录了每次的 branch 跳转。
  • 统计信息,如指令执行时的时间戳。

基于连续的 packet 流,有了 branch 跳转信息,用户层再进行 decode,就能复原出当时程序的执行流。对于使用者而言,无需修改源代码重新编译,只需要在程序运行时,使能 PT trace,此时 packet 开始产生。trace 流在发送至内存之前,首先会在缓存在内部的硬件 buffer 中。和 LBR (Last Branch Record)类似,Intel PT 主要原理也是记录 branch 跳转,但相比之下,能够追踪更长的 branch 记录。基于 branch 跳转,就能复原当时的程序指令执行过程。

intel-pt architecture

2 PT_PERF 时延分析工具

基于 Intel PT 的程序 trace 技术,我们实现了 PT_PERF 的时延分析工具,使用 PT 的 trace 数据来显示程序执行的关键信息如函数时延,时延曲线,时延火焰图等信息。整个流程实现基于 Linux perf tool,包括 perf record、perf script 以及结果汇总输出阶段。

指定采集一段时间后,PT_PERF 支持:

1.函数分析

  • 统计目标函数的时延分布直方图,平均时延。
  • 统计目标函数的子函数调用时延。
  • 统计按上层函数分组统计目标函数的时延分布。
  • 分别统计目标函数的 On-CPU 和 Off-CPU (Schedule) 统计 (需要 root 权限)。

2.timeline 时间线分析

  • 按线程给出函数关于 trace 时间的时延曲线图。
  • 统计函数在某一时间范围的 trace 数据,进行分析。
  • 查看某个时间点的函数栈 pstack。

3.Flamegraph 火焰图分析

  • 基于函数时延的火焰图:使用 pt_flame 进行分析。
  • on-cpu 的火焰图:和传统基于硬件溢出中断模式的采样类似。

4.历史分析

  • 先 trace 某段时间全量数据,再进行分析。

2.1 使用

2.1.1 安装

PT_PERF 已经开源至 github。在 Linux 4.2+ 和 GCC 7+ 版本下,可以通过下面命令安装

sudo yum install binutils binutils-devel elfutils-libelf-devel -y git clone https://github.com/mysqlperformance/pt_perf.git cd pt_perf make