写点什么

pprof 数据组装(一)

作者:jupiter
  • 2023-07-05
    广东
  • 本文字数:3557 字

    阅读完需:约 12 分钟

pprof 数据组装(一)

前期文章重点介绍了 parca-agent 使用 eBPF 采集应用程序的堆栈信息。根据此信息,又如何组装成 pprof 协议呢?本文我们以 c 代码为例,跟踪一下 pprof 数据协议的组装过程。主要包括:pprof 格式概览、Sample、Location、Mappings。

0、pprof 格式概览



pprof 协议定义了上述六种主要数据段。详细的格式定义,可参考github。parca-agent 通过函数 ConvertToPprof 进行数据组装。组装完的 pb 文件如下:

PeriodType: cpu nanosecondsPeriod: 52631578Time: 2023-07-05 04:13:01.573689489 +0100 BSTDuration: 14.1Samples:samples/count          1: 1 2 3 4 5 6 7 8 9 10 11 12 Locations     1: 0xffffffff844e7550 M=23 _copy_from_user :0 s=0()     2: 0xffffffff83fa7835 M=23 __x64_sys_clock_nanosleep :0 s=0()     3: 0xffffffff84d2117c M=23 do_syscall_64 :0 s=0()     4: 0xffffffff84e0009b M=23 entry_SYSCALL_64_after_hwframe :0 s=0()     5: 0xe57fa M=7      6: 0xea6e7 M=7      7: 0x11c0df M=7      8: 0x1168 M=2      9: 0x118b M=2     10: 0x11ab M=2     11: 0x29d90 M=7     12: 0x29e40 M=7 Mappings1: 0x556b2f08d000/0x556b2f08e000/0x0 jit unknown 2: 0x556b2f08e000/0x556b2f08f000/0x1000 /proc/14095/root/home/club/parca/helloc ac706528e348f7ede5d2dbbec3c37383f10654a8 3: 0x556b2f08f000/0x556b2f090000/0x2000 jit unknown 4: 0x556b2f090000/0x556b2f091000/0x2000 jit unknown 5: 0x556b2f091000/0x556b2f092000/0x3000 jit unknown 
复制代码

使用的 c 代码为:

#include<stdio.h>#include<unistd.h>void func2(){        int i=0;        for(;i<100000000;i++)        {                usleep(1);        }}void func1(){   func2();}int main(){        int a=0;        func1();        return 0;}
复制代码

进行编译:

gcc -o helloc hello.c
复制代码

1、sample_type 采样类型


SampleType: []*profile.ValueType{{      Type: "samples",      Unit: "count",}},
复制代码

此外,还定义了采样周期,1/19=0.05263s,即 52631578 ns

Period: 52631578
复制代码


生成的 pb 头部如下:


sample_type {  type: "samples"  unit: "count"}PeriodType: cpu nanosecondsPeriod: 52631578Time: 2023-07-05 04:13:01.573689489 +0100 BSTDuration: 14.1
复制代码

cpu sample 采样的定义:内核每 0.05263s 进行一次检测,如果发现当前进程的函数在执行,本函数调用栈涉及的所有函数统计值加 1。

2、sample 样本值 pb 文件内容为:


Samples:samples/count          1: 1 2 3 4 5 6 7 8 9 10 11 12 
复制代码

表示,在 14.1s 内,某线程函数调用栈 1->12 (下文的 location 的序号) 执行次数为 1 次。如果还有其他实际调用关系,会继续在下面追加显示,比如可能会有:

 1: 1 2 3 4 5 6 7 8 9 10 11 12 1: 11 12 7 8 9 10 2: 11 12 7
复制代码

前文 eBPF 获取到的调用栈信息,即栈回溯地址信息,按照线程粒度,分别保存到 sample 中。

p.bpfMaps.readUserStack(key.UserStackID, &stack)...allSamples[id][stack] = profiler.NewSample(sampleLocations[id], int64(value))
复制代码

统计数值 1/1/2 等,则是前文提到的 eBPF 探测到的函数采样统计值。最终解释为:线程 1 函数调用栈执行了 1 次,线程 2 函数调用栈执行了 1 次,线程 3 函数调用栈执行了 2 次。

3、Location 调用栈


Locations     1: 0xffffffff844e7550 M=23 _copy_from_user :0 s=0()     2: 0xffffffff83fa7835 M=23 __x64_sys_clock_nanosleep :0 s=0()     3: 0xffffffff84d2117c M=23 do_syscall_64 :0 s=0()     4: 0xffffffff84e0009b M=23 entry_SYSCALL_64_after_hwframe :0 s=0()     5: 0xe57fa M=7      6: 0xea6e7 M=7      7: 0x11c0df M=7      8: 0x1168 M=2      9: 0x118b M=2     10: 0x11ab M=2     11: 0x29d90 M=7     12: 0x29e40 M=7 
复制代码

上文 sample 出现过的所有调用栈信息,包括用户态和内核态地址,按 id 顺序放在 Location 位置。sample 中 id 和 location id 对应,只有 sample 的 id 有严格的上下文调用关系,location 中的前后 id 之间没有严格的上下文关系。这里我们结合例子看一下,上文调用栈的信息如何与编译文件关联起来。

3.1 用户函数地址

我们跟踪 Locations:10 0x11ab。使用 objdump -d helloc 查看汇编地址:

000000000000118e <main>:    118e:  f3 0f 1e fa            endbr64     1192:  55                     push   %rbp    1193:  48 89 e5               mov    %rsp,%rbp    1196:  48 83 ec 10            sub    $0x10,%rsp    119a:  c7 45 fc 00 00 00 00   movl   $0x0,-0x4(%rbp)    11a1:  b8 00 00 00 00         mov    $0x0,%eax    11a6:  e8 ce ff ff ff         call   1179 <func1>    11ab:  b8 00 00 00 00         mov    $0x0,%eax
复制代码

我们看到 Locations:10 中的 0x11ab 地址,正好是 main 函数调用子函数 func1 0x11a6 执行时的下一行(子函数栈顶元素存的就是 call 的下一行地址)。

0000000000001149 <func2>:    1149:  f3 0f 1e fa            endbr64     114d:  55                     push   %rbp    114e:  48 89 e5               mov    %rsp,%rbp    1151:  48 83 ec 10            sub    $0x10,%rsp    1155:  c7 45 fc 00 00 00 00   movl   $0x0,-0x4(%rbp)    115c:  eb 0e                  jmp    116c <func2+0x23>    115e:  bf 01 00 00 00         mov    $0x1,%edi    1163:  e8 e8 fe ff ff         call   1050 <usleep@plt>    1168:  83 45 fc 01            addl   $0x1,-0x4(%rbp)    116c:  81 7d fc ff e0 f5 05   cmpl   $0x5f5e0ff,-0x4(%rbp)    1173:  7e e9                  jle    115e <func2+0x15>    1175:  90                     nop    1176:  90                     nop    1177:  c9                     leave      1178:  c3                     ret    
0000000000001179 <func1>: 1179: f3 0f 1e fa endbr64 117d: 55 push %rbp 117e: 48 89 e5 mov %rsp,%rbp 1181: b8 00 00 00 00 mov $0x0,%eax 1186: e8 be ff ff ff call 1149 <func2> 118b: 90 nop 118c: 5d pop %rbp 118d: c3 ret
复制代码

同理 Locations:9 中的 0x118b 地址,正好是 func1 调用子函数 func2 执行时的下一行;Locations:8 中的 0x1168 地址,正好是 func2 调用子函数 usleep 执行时的下一行。所以本例子 sample 的调用栈关系 main->func1->func2 如下:


3.2 内核/libc 函数地址

parca agent 处理内核/libc 调用栈时,会读取系统符号表信息,直接填充函数名,即 Locations1-4 的内容。具体可以参考

kernelFunctions, err := s.resolveKernelFunctions(prof.KernelLocations)
复制代码

4、mapping 映射表

此部分内容来自 /proc/PID/maps 文件的内容,maps 中具体 layout 的内容可以参考 https://gist.github.com/CMCDragonkai/10ab53654b2aa6ce55c11cfc5b2432a4。

5、Function 调用栈的函数

对于内核/库函数,参考 如下工具,

protoc --decode perftools.profiles.Profile /home/opensource/pprof/proto/profile.proto --proto_path /home/opensource/pprof/proto <  profile.pb
复制代码

可以看到 Function 部分内容:

function {  id: 1  name: 12}function {  id: 2  name: 13}
复制代码

其中的 id 表示 function 的编号,name 来自 string_tabe:

string_table: "do_syscall_64"string_table: "entry_SYSCALL_64_after_hwframe"
复制代码

节选了 string_table 的 12-13 部分内容如上。

6、小结

本文主要以 c 代码为例,跟踪了 agent 组装 pprof 数据协议的过程。我们仍有如下疑问:

  1. Locations 中的用户态堆栈信息,仍然是地址形式,无法直观的看到函数名称。

  2. Function 中的信息,只包含了内核/库函数,没有对用户态函数进行解析。

  3. string_table 中也缺少用户函数名称等信息。

因为对于非 JIT 类语言,比如 C/Go 等,parca agent 做 pprof 的部分数据组装。数据发送到 server 后,会继续做 symbol 的提取等工作。接下来,我们看 parca server 是如何继续完善 pprof 信息,保证 prof 信息的可视化和存储。

参考:

https://www.polarsignals.com/blog/posts/2022/01/13/fantastic-symbols-and-where-to-find-them/

http://blog.k3170makan.com/2018/10/introduction-to-elf-format-part-vi.html

https://linux-audit.com/elf-binaries-on-linux-understanding-and-analysis/https://gist.github.com/CMCDragonkai/10ab53654b2aa6ce55c11cfc5b2432a4

https://www.practical-go-lessons.com/chap-36-program-profiling

发布于: 刚刚阅读数: 9
用户头像

jupiter

关注

公号,grafanafans 2022-01-14 加入

还未添加个人简介

评论

发布
暂无评论
pprof 数据组装(一)_pprof_jupiter_InfoQ写作社区