写点什么

基于 ebpf 的 parca-agent profiling 方案探究

作者:jupiter
  • 2023-05-19
    江苏
  • 本文字数:3500 字

    阅读完需:约 11 分钟

基于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++需要开启

-fno-omit-frame-pointer
复制代码

汇编代码包含如下:

    push $rbp # 在进入下一个函数(callee)时,会保存调用函数(caller)的栈帧    mov $rbp, $rsp # 进入下一个函数(callee)后,会将新的帧地址(rsp)赋值给rbp
复制代码

最终会绘制如下的调用关系

这种方式需要应用程序编译依赖,parca-agent 使用如下方式进行 fp 是否存在的探测(ebpf 回调函数)

static __always_inline bool has_fp(u64 current_fp) {  u64 next_fp;
for (int i = 0; i < MAX_STACK_DEPTH; i++) { int err = bpf_probe_read_user(&next_fp, 8, (void *)current_fp); if (err < 0) { // LOG("[debug] fp read failed with %d", err); return false; } if (next_fp == 0) { // LOG("[debug] fp success"); return i > 0; } current_fp = next_fp; }
LOG("[debug] fp not enough frames"); return false;}
复制代码

然后使用 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

简单理解:

  1. 代码的函数间调用关系,是一种树状结构关系,在编译形成汇编代码后,函数前后调用关系,就确定下来了。这是程序内部的相对位置关系。也叫做 unwind table。

  2. 由于程序运行时的 PC(Program Counter)位置,是无法预测的。一旦获取到程序运行时的 PC 位置,以及程序运行的起始地址 load_address,便可以进行函数调用回溯。

  3. 用 agent 的代码解释如下:

load_address = proc_info->mappings[i].load_address;//二者的差值,即相对位置偏移(offset),作为调整后的pcu64 adjusted_pc = pc - load_address;
//应用到unwind_table,进行函数回溯。u64 table_idx = find_offset_for_pc(unwind_table, unwind_state->ip - offset, left, right);
复制代码

简易流程图如下:


2.2.3 unwind 展开

具体展开逻辑,可以看 ebpf 的回调函数,

walk_user_stacktrace_impl   add_stack(ctx, pid_tgid, STACK_WALKING_METHOD_DWARF, unwind_state);
复制代码

这部分逻辑比较通用,可以借鉴此文此文帮助理解。

3、ebpf 调用关系

使用 ebpf 进行<用户态-内核态>数据交互时,设计原则大概分为三部分:

1)监听何种内核事件 [userspace];

2)进行数据流转移的 map 设计 [userspace+kernelspace];

3)保证 map 正常运转的函数逻辑 [kernelspace];

所以,我们看一下 agent 在这三部分的表现。

3.1 监听事件

监听事件是 perf_event,并指示内核每秒进行 19 次探测。

fd, err := unix.PerfEventOpen(&unix.PerfEventAttr{    Type:   unix.PERF_TYPE_SOFTWARE,    Config: unix.PERF_COUNT_SW_CPU_CLOCK,    Size:   uint32(unsafe.Sizeof(unix.PerfEventAttr{})),    Sample: p.profilingSamplingFrequency,    Bits:   unix.PerfBitDisabled | unix.PerfBitFreq,}, -1 /* pid */, i /* cpu id */, -1 /* group */, 0 /* flags */)..._, err = prog.AttachPerfEvent(fd)
复制代码

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:

bpftool  map  show43: hash  name unwind_tables  flags 0x0    key 8B  value 3500000B  max_entries 50  memlock 175001600B    btf_id 139    pids parca-agent(12343)51: stack_trace  name stack_traces  flags 0x0    key 4B  value 1016B  max_entries 64000  memlock 65536000B53: hash  name stack_counts  flags 0x0key 20B  value 8B  max_entries 10240  memlock 327680Bbtf_id 139pids parca-agent(12343)
复制代码

查看 ebpf 挂载的函数:

bpftool prog show38: perf_event  name walk_user_stack  tag 69a1aaf688bf414f  gpl    loaded_at 2023-05-11T03:28:01+0100  uid 0    xlated 6296B  jited 3778B  memlock 12288B  map_ids 6,18,8,14,7,15,16,12,11,13,9    btf_id 100    pids parca-agent(8219)    metadata:        name = "parca-agent (https://github.com/parca-dev/parca-agent)"40: perf_event  name profile_cpu  tag dde24dc33738fc9a  gpl    loaded_at 2023-05-11T03:28:03+0100  uid 0    xlated 4048B  jited 2558B  memlock 8192B  map_ids 18,10,6,8,15,11,16,13,14,9    btf_id 100    pids parca-agent(8219)    metadata:        name = "parca-agent (https://github.com/parca-dev/parca-agent)"
复制代码

4.2 使用 frame pointer 跟踪 trace

go build hello.go
复制代码

跟踪 ebpf 日志如下:

hello-10902   : [debug] fp successhello-10902   : add_stack,pid 10902 tgid 10899
复制代码

4.3 使用 dwarf unwind 跟踪 trace

最简单的helloworldgcc -o hello hello.c
复制代码

跟踪 ebpf 日志如下:

    hello-10652  : ~about to check shards found=1    hello-10652  : ~checking shards now    hello-10652  : [info] found chunk    hello-10652  : pid 10652 tgid 10652    hello-10652  : ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~    hello-10652  : traversing stack using .eh_frame information!!    hello-10652  : ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~    hello-10652  : ## frame: 0    hello-10652  :   current pc: 7f5363ae57fa    hello-10652  :   current sp: 7ffef8273090    hello-10652  :   current bp: 7ffef8273160    hello-10652  : ~about to check shards found=1
复制代码



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


发布于: 2023-05-19阅读数: 46
用户头像

jupiter

关注

公号,grafanafans 2022-01-14 加入

还未添加个人简介

评论

发布
暂无评论
基于ebpf的parca-agent profiling方案探究_ebpf_jupiter_InfoQ写作社区