基于 ebpf 的 parca-agent profiling 方案探究
前言
持续性能监控(continious profiling)作为可观测的一要素,有众多开源方案值得研究。本文介绍 parca 推出的基于 ebpf 的无侵入方案-parca agent。由于作者水平有限,理解不准确的地方,希望交流指正。
1、parca-agent 工作流程
agent 工作主要分为如下:
1)使用 scraper 方式抓取远端服务暴露的 profile 信息,具体配置可以参考官方文档;
2)使用 ebpf 方式无侵入的采集本机 APP 程序 CPU 的 profile 信息;
3)将 profile 信息转成 pprof 格式,传递给 server 展示。
本文重点介绍如何使用 ebpf 的方式,进行 APP 的栈追踪,以实现无侵入 profile 采集。在此之前,我们看一下,栈追踪(Stack Walking)的方式。
2、栈追踪的方式
Javier详细介绍了 parca-agent(以下简称 agent)进行栈追踪的考虑,有如下两个方向。
2.1 开启 frame pointer(fp)
需要应用程序编译时开启 fp 编译选项,比如 C/C++需要开启
汇编代码包含如下:
最终会绘制如下的调用关系
这种方式需要应用程序编译依赖,parca-agent 使用如下方式进行 fp 是否存在的探测(ebpf 回调函数)
然后使用 bpf_get_stackid 进行 stack 回溯。但实测时发现,在 x86_64 位 ubuntu 环境,gcc 即使开启上述编译选项,也无法准确的进行 frame pointer 的回溯,于是 ping 了一下 Javier,他给出了如下解释。那是否存在不依赖 fp 的栈回溯方案呢?
2.2 使用 eh_frame 进行 unwind 展开
在进入这部分前,需要了解以下,1)DWARF;2)eh_frame;3)unwind 展开
2.2.1 DWARF
DWARF(Debugging With Attributed Record Formats)是一种广泛使用的标准调试信息格式,最初 DWARF 的设计初衷是配合 ELF 格式使用。DWARF 使用 DIE(Debugging Information Entry)来描述变量、数据类型、代码等,DIE 中包含了标签(Tag)和一系列属性(Attributes)。
DWARF 还定义了一些关键的数据结构,如行号表(Line Number Table)、调用栈信息(Call Frame Information)等,有了这些关键数据结构之后,开发者就可以在源码级别动态添加断点、显示完整的调用栈信息、查看调用栈中指定栈帧的信息。
2.2.2 eh_frame
eh_frame 和.debug_frame 非常类似,但是它编码紧凑,可以随程序一起加载。eh_frame 也使用了 Call Frame Information (CFI)进行解释。
图片来源:https://zhuanlan.zhihu.com/p/302726082
简单理解:
代码的函数间调用关系,是一种树状结构关系,在编译形成汇编代码后,函数前后调用关系,就确定下来了。这是程序内部的相对位置关系。也叫做 unwind table。
由于程序运行时的 PC(Program Counter)位置,是无法预测的。一旦获取到程序运行时的 PC 位置,以及程序运行的起始地址 load_address,便可以进行函数调用回溯。
用 agent 的代码解释如下:
简易流程图如下:
2.2.3 unwind 展开
具体展开逻辑,可以看 ebpf 的回调函数,
3、ebpf 调用关系
使用 ebpf 进行<用户态-内核态>数据交互时,设计原则大概分为三部分:
1)监听何种内核事件 [userspace];
2)进行数据流转移的 map 设计 [userspace+kernelspace];
3)保证 map 正常运转的函数逻辑 [kernelspace];
所以,我们看一下 agent 在这三部分的表现。
3.1 监听事件
监听事件是 perf_event,并指示内核每秒进行 19 次探测。
3.2 map 设计
主要用到两个 map:stack_traces,stack_counts
stack_traces:有两种填充方式:基于 frame pointer 的跟踪;基于 eh_frame 的跟踪,此时需要在用户态将 unwind_table 准备好。
stack_counts:key 是<PID, user-space stack ID, and kernel-space stack ID>三元组,stack ID 来自 stack_traces。 value is trace ID 在一个观察周期内的执行时间。
3.3 ebpf 函数设计
ebpf 的内核态提供两个函数,profile_cpu 和 walk_user_stacktrace_impl 进行 map 数据收集。
用户态函数读取 map 的数据,并搜集/proc/PID/maps,进行 Pprof 数据组装。参考规则
4、demo
4.1 agent 启动
启动 parca-agent 后,查看 ebpf 挂载的部分 map:
查看 ebpf 挂载的函数:
4.2 使用 frame pointer 跟踪 trace
跟踪 ebpf 日志如下:
4.3 使用 dwarf unwind 跟踪 trace
跟踪 ebpf 日志如下:
https://www.isi.deterlab.net/file.php?file=/share/shared/AnintroductiontoDwarf
https://refspecs.linuxfoundation.org/LSB_3.0.0/LSB-PDA/LSB-PDA.junk/dwarfext.html
https://blog.csdn.net/u012336798/article/details/12688443
https://mozillazg.com/2022/05/ebpf-libbpfgo-develop-env-and-hello-world-en.html
https://wiki.shileizcc.com/confluence/display/bpf/BPFTool
https://www.sartura.hr/blog/simple-ebpf-core-application/
https://marselester.com/continuous-profiling-in-go.html
https://prodfiler.com/blog/optimising-an-ebpf-optimiser/
Why We Switched from bcc-tools to libbpf-tools for BPF Performance Analysis
版权声明: 本文为 InfoQ 作者【jupiter】的原创文章。
原文链接:【http://xie.infoq.cn/article/739629c2c64a16d99cf370f00】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论