有一个很好的参考资料 perfwiki
perfwiki 的组织方式是从 perf 的实现原理出发perf stat
就是执行程序前后 event 数量之差perf record
就是执行程序过程中事件每发生固定次数就采集一个样本
识别性能瓶颈
最经典的任务是将 CPU 执行的时间按
最直觉的做法是进出函数时都打印带时间戳的日志
- 需要主动知道哪些函数是潜在的热点需要被记录
然后手动为他们写日志语句, 或者需要一个额外工具来自动为所有函数插入这种日志; 更进一步; 这两点在没有源码时都不现实, ; - 当函数调用次数过多时
记录这些日志本身可能会成为性能瓶颈, 因为这涉及格式化, 与硬盘交互等等、 ;
幸运的是
实现这种采样有两个要点perf record
实现cycles
时-F
参数可以指定采样频率-a
参数指定对所有 CPU 采样-C
参数指定了要采样的 CPU 列表-g
参数就告诉 perf record
记录下调用栈perf record
加上 -p
参数perf record
的意义是对概率分布进行采样
perfwiki 中指出
现代 CPU 的实现方式导致采集到的样本的指令位置可能并不精准地是 event 数量间隔到达的那一刻 , 会有偏移 , skid ( ) 当事件为 cycles 时这不是太大的问题 。 但事件是 cache-miss 等时就要注意精确的样本位置不能用来参考 , 。
perf record
采集到的样本内容是二进制的perf script
处理后长下面的样子
zebra 2228528 [007] 196350.576148: 10101010 cpu-clock:
72be3b6fa59f __libc_calloc+0x7f (/usr/lib/x86_64-linux-gnu/libc.so.6)
72be3bacf581 listnode_add+0x61 (/usr/lib/libfrr.so.0.0.0)
5ebb25f4c736 rib_meta_queue_early_route_add+0x16 (/usr/lib/frr/zebra)
5ebb25f10345 zread_route_add+0x1a5 (/usr/lib/frr/zebra)
5ebb25f14046 zserv_handle_commands+0x116 (/usr/lib/frr/zebra)
5ebb25f83780 zserv_process_messages+0x100 (/usr/lib/frr/zebra)
72be3bb25854 event_call+0x84 (/usr/lib/libfrr.so.0.0.0)
72be3bacec78 frr_run+0xc8 (/usr/lib/libfrr.so.0.0.0)
5ebb25ee3bc6 main+0x3e6 (/usr/lib/frr/zebra)
72be3b67dd90 __libc_start_call_main+0x80 (/usr/lib/x86_64-linux-gnu/libc.so.6)
bgpd 2225676 [006] 196350.576150: 10101010 cpu-clock:
75d5018c01dd __tls_get_addr+0xd (/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2)
75d501851d68 socket+0x4ee (/usr/lib/libpreload.so)
75d501851efe socket+0x684 (/usr/lib/libpreload.so)
75d501710ec2 event_fetch+0x322 (/usr/lib/libfrr.so.0.0.0)
75d5016bac83 frr_run+0xd3 (/usr/lib/libfrr.so.0.0.0)
574331820f53 main+0x393 (/usr/lib/frr/bgpd)
75d5012bad90 __libc_start_call_main+0x80 (/usr/lib/x86_64-linux-gnu/libc.so.6)
...
采到样本之后展示性能瓶颈有几种方式
bgpd;__libc_start_call_main;main;frr_run;event_fetch;socket;socket;__tls_get_addr 10101010
zebra;__libc_start_call_main;main;frr_run;event_call;zserv_process_messages;zserv_handle_commands;zread_route_add;rib_meta_queue_early_route_add;listnode_add;__libc_calloc 10101010
...
一般要做 breakdown 的话调用栈就是这样从 main 开始逐渐细化即可
TODO
理解这个图的方式是
还有有一种 reversed 火焰图