电子产业一站式赋能平台

PCB联盟网

搜索
查看: 101|回复: 0
收起左侧

Linux黑科技:浅析动态追踪技术

[复制链接]

1077

主题

1077

帖子

1万

积分

论坛法老

Rank: 6Rank: 6

积分
11496
发表于 2024-5-23 09:02:00 | 显示全部楼层 |阅读模式
点击上方“C语言与CPP编程”,选择“关注/置顶/星标公众号
# B# H' R1 |" [- U- D  a干货福利,第一时间送达!
2 h+ U- z, C3 I: w
2 n" U, K+ o9 O) Y, \. \6 {* s2 q

b2dkx25ertr64024906129.png

b2dkx25ertr64024906129.png
6 V- Q, n1 P+ T# N. N6 m: T/ l
最近有小伙伴说没有收到当天的文章推送,这是因为微信改了推送机制,有一部分小伙伴刷不到当天的文章,一些比较实用的知识和信息,错过了就是错过了,建议大家加个星标??,就能第一时间收到推送。
- t! b$ Q% U, S8 W

el40u2df0ce64024906229.png

el40u2df0ce64024906229.png

) O" m) E, k8 Y) N2 E
, P. O: ]3 r# K' f) W9 |2 k

3 ]" \2 N) Z) D7 n; B5 A9 W2 P7 w! M1 e0 X2 l! `/ d1 x5 [- Z

mbzron50gbs64024906329.png

mbzron50gbs64024906329.png

0 G9 X' X, E" c4 m. g6 S
6 p" c# t, K7 l: q( G( @

2 B9 m! K# ^7 F. m
0 p( o$ k5 ]7 ]% b& o# f+ q5 D
动态追踪技术原因当碰到内核线程的资源使用异常时,很多常用的进程级性能工具,并不能直接用到内核线程上。这时,我们就可以使用内核自带的 perf 来观察它们的行为,找出热点函数,进一步定位性能瓶颈。不过,perf 产生的汇总报告并不直观,所以我通常也推荐用火焰图来协助排查。
7 P; m# ^- x' _& P! ^5 X' F8 s其实,使用 perf 对系统内核线程进行分析时,内核线程依然还在正常运行中,所以这种方法也被称为动态追踪技术。/ O, P4 a6 e9 o0 K3 [( J- V
动态追踪技术,通过探针机制,来采集内核或者应用程序的运行信息,从而可以不用修改内核和应用程序的代码,就获得丰富的信息,帮你分析、定位想要排查的问题。, D6 T; M' i6 f4 Z
以往,在排查和调试性能问题时,我们往往需要先为应用程序设置一系列的断点(比如使用GDB),然后以手动或者脚本(比如 GDB 的 Python 扩展)的方式,在这些断点处分析应用程序的状态。或者,增加一系列的日志,从日志中寻找线索。
, z6 K8 o5 a% `1 m( l不过,断点往往会中断应用的正常运行;而增加新的日志,往往需要重新编译和部署。这些方法虽然在今天依然广泛使用,但在排查复杂的性能问题时,往往耗时耗力,更会对应用的正常运行造成巨大影响。
/ b( i% g3 ]; L/ x( T此外,这类方式还有大量的性能问题。比如,出现的概率小,只有线上环境才能碰到。这种难以复现的问题,亦是一个巨大挑战。
" Y: u1 e# t5 V# A* [, k; ?而动态追踪技术的出现,就为这些问题提供了完美的方案:它既不需要停止服务,也不需要修改应用程序的代码;所有一切还按照原来的方式正常运行时,就可以帮你分析出问题的根源。# p: N! h' |+ v' D! j7 z6 J3 b* |; O' o
同时,相比以往的进程级跟踪方法(比如 ptrace),动态追踪往往只会带来很小的性能损耗(通常在 5% 或者更少)。! ?8 e" h% D: D2 U
动态追踪
+ K6 G5 h- Y, M- {: K4 }# L% V, Q- X" Y, h

zdx0gg2dcr064024906429.png

zdx0gg2dcr064024906429.png

  a! E& i% X( e) y' w1 T- y- K8 M, e, C0 P: o
说到动态追踪(Dynamic Tracing),就不得不提源于 Solaris 系统的 DTrace。DTrace 是动态追踪技术的鼻祖,它提供了一个通用的观测框架,并可以使用 D 语言进行自由扩展。: r% X7 x7 O4 K4 e, v6 \0 W
DTrace 的工作原理如下图所示。它的运行常驻在内核中,用户可以通过 dtrace 命令,把 D 语言编写的追踪脚本,提交到内核中的运行时来执行。DTrace 可以跟踪用户态和内核态的所有事件,并通过一些列的优化措施,保证最小的性能开销。
. E: Z* g& d0 m1 ?5 ?8 ?5 W* T

jgxxtragq3v64024906529.png

jgxxtragq3v64024906529.png
9 z7 m  ~8 V3 g! A: }! T2 I
虽然直到今天,DTrace 本身依然无法在 Linux 中运行,但它同样对 Linux 动态追踪产生了巨大的影响。很多工程师都尝试过把 DTrace 移植到 Linux 中,这其中,最著名的就是 RedHat 主推的 SystemTap。
! g; ]1 d; q1 x, g* |  t8 p9 G同 DTrace 一样,SystemTap 也定义了一种类似的脚本语言,方便用户根据需要自由扩展。不过,不同于 DTrace,SystemTap 并没有常驻内核的运行时,它需要先把脚本编译为内核模块,然后再插入到内核中执行。这也导致 SystemTap 启动比较缓慢,并且依赖于完整的调试符号表。! A+ h0 \1 F# z+ C/ u

2y4aowcb3ks64024906629.png

2y4aowcb3ks64024906629.png

/ m& n: J& i' }& x( J总的来说,为了追踪内核或用户空间的事件,Dtrace 和 SystemTap 都会把用户传入的追踪处理函数(一般称为 Action),关联到被称为探针的检测点上。这些探针,实际上也就是各种动态追踪技术所依赖的事件源。+ |- u& c( H4 k- `5 h; R
$ S9 S: g- F, p: r
动态追踪的事件源  ^- X! J6 i8 Y

eatct1tnxki64024906729.png

eatct1tnxki64024906729.png

; \1 `, k. B5 Q- w7 x4 w5 N0 \, @% X根据事件类型的不同,动态追踪所使用的事件源,可以分为静态探针、动态探针以及硬件事件等三类。它们的关系如下图所示:其中,硬件事件通常由性能监控计数器 PMC(Performance Monitoring Counter)产生,包括了各种硬件的性能情况,比如 CPU 的缓存、指令周期、分支预测等等。' C" y. m) `3 a( E6 O
静态探针,是指事先在代码中定义好,并编译到应用程序或者内核中的探针。这些探针只有在开启探测功能时,才会被执行到;未开启时并不会执行。常见的静态探针包括内核中的跟踪点(tracepoints)和 USDT(Userland Statically Defined Tracing)探针。" ?% o1 W/ c$ c% v
  • 跟踪点(tracepoints),实际上就是在源码中插入的一些带有控制条件的探测点,这些探测点允许事后再添加处理函数。) I' C  V! {; O' P+ E5 u$ M
    比如在内核中,最常见的静态跟踪方法就是2 @- c: U4 R. P' ?( ^9 k
    printk,即输出日志。
    3 B6 |& b7 {9 ELinux 内核定义了大量的跟踪点,可以通过内核编译选项,来开启或者关闭。
  • USDT探针,全称是用户级静态定义跟踪,需要在源码中插入 DTRACE_PROBE() 代码,并编译到应用程序中。
    % {$ A! c3 o  C* o) o) u6 E不过,也有很多应用程序内置了 USDT 探针,比如 MySQL、PostgreSQL等。
    0 F0 s; U" O: y) `0 D0 B5 x( M& S" x
    动态探针,则是指没有事先在代码中定义,但却可以在运行时动态添加的探针,比如函数的调用和返回等。动态探针支持按需在内核或者应用程序中添加探测点,具有更高的灵活性。常见的动态探针有两种,即用于内核态的 kprobes 和用于用户态的 uprobes。
    ; E( x* M7 b3 \( X8 Z6 r% `kprobes 用来跟踪内核态的函数,包括用于函数调用的 kprobe 和用于函数返回的kretprobe。
    6 D9 G. s3 k: R; I0 Iuprobes 用来跟踪用户态的函数,包括用于函数调用的 uprobe 和用于函数返回的uretprobe。# E( m& _& P: d$ E5 F3 J
    注意,kprobes 需要内核编译时开启 CONFIG_KPROBE_EVENTS;而 uprobes则需要内核编译时开启 CONFIG_UPROBE_EVENTS。
    + F! K  Y% v5 \) y$ S动态追踪机制而在这些探针的基础上,Linux 也提供了一系列的动态追踪机制,比如 ftrace、perf、eBPF 等。- V& H' p2 z- u) ]. S  r& t
    ftrace 最早用于函数跟踪,后来又扩展支持了各种事件跟踪功能。ftrace 的使用接口跟我们之前提到的 procfs 类似,它通过 debugfs(4.1 以后也支持 tracefs),以普通文件的形式,向用户空间提供访问接口。
    ; j% O1 R' o1 x8 V8 M6 N% j这样,不需要额外的工具,你就可以通过挂载点(通常为 /sys/kernel/debug/tracing 目录)内的文件读写,来跟 ftrace 交互,跟踪内核或者应用程序的运行事件。
    2 {7 m* c# z0 fperf 是我们的老朋友了,我们在前面的好多案例中,都使用了它的事件记录和分析功能,这实际上只是一种最简单的静态跟踪机制。你也可以通过 perf ,来自定义动态事件(perf probe),只关注真正感兴趣的事件。) H- ~/ D0 H9 S: |' q5 ^9 y
    eBPF 则在 BPF(Berkeley Packet Filter)的基础上扩展而来,不仅支持事件跟踪机制,还可以通过自定义的 BPF 代码(使用 C 语言)来自由扩展。所以,eBPF 实际上就是常驻于内核的运行时,可以说就是 Linux 版的 DTrace。) E" O: Y" I3 T
    ftraceftrace 通过 debugfs(或者 tracefs),为用户空间提供接口。所以使用 ftrace,往往是从切换到 debugfs 的挂载点开始。
    # }- M; T5 E5 D1 h8 b! ]cd /sys/kernel/debug/tracing
    : J8 a# M8 z' u& c/ ^, u$ ls8 {5 x& ?$ t# C
    README                      instances            set_ftrace_notrace  trace_marker_raw% q- r. d) V  [2 M0 B# Z2 `4 ]$ l) W, h
    available_events            kprobe_events        set_ftrace_pid      trace_options1 K5 C5 H8 j* C! T9 n0 p
    ...如果这个目录不存在,则说明你的系统还没有挂载 debugfs,你可以执行下面的命令来挂载它:' U1 a, M7 n  |
    mount -t debugfs nodev /sys/kernel/debugftrace 提供了多个跟踪器,用于跟踪不同类型的信息,比如函数调用、中断关闭、进程调度等。具体支持的跟踪器取决于系统配置,你可以执行下面的命令,来查询所有支持的跟踪器:: C/ c2 H# x7 n2 p. X( _' I
    cat available_tracers& [+ L% O' p3 @+ \& O$ z
    hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop这其中,function 表示跟踪函数的执行,function_graph 则是跟踪函数的调用关系,也就是生成直观的调用关系图。这便是最常用的两种跟踪器。/ D: H& M6 g! x7 X1 P1 P6 q
    除了跟踪器外,使用 ftrace 前,还需要确认跟踪目标,包括内核函数和内核事件。其中,9 M5 g0 f7 s6 U, z# p
    函数就是内核中的函数名。
    ( G# ~- j4 D! Y. F而事件,则是内核源码中预先定义的跟踪点。
    $ z2 ~& [  @0 A" l3 O4 C  c8 Y
    同样地,可以执行下面的命令,来查询支持的函数和事件:
    ) `( n  f8 @- f% ^8 ]cat available_filter_functions
    " Z8 p( N* i# q& O) H7 v2 J4 T$ w8 O$ cat available_eventsls 命令会通过 open 系统调用打开目录文件,而 open 在内核中对应的函数名为do_sys_open。
    5 z3 w& Z% m8 b( Q4 Q8 C/ e第一步就是把要跟踪的函数设置为 do_sys_open:
    : y  j, u+ N5 D$ I/ mecho do_sys_open > set_graph_function第二步,配置跟踪选项,开启函数调用跟踪,并跟踪调用进程:+ u7 ?$ m# T! X! R4 Z% i( h
    echo function_graph > current_tracer
    5 H5 |. T1 i. N3 l1 V% ^* l  i$ echo funcgraph-proc > trace_options第三步,也就是开启跟踪:. ?, x& ^* {4 i% \* K
    echo 1 > tracing_on第四步,执行一个 ls 命令后,再关闭跟踪:0 u1 w" z8 I/ b  T' e. p% D4 ^9 e
    ls; C/ j$ q& ]6 `1 C
    $ echo 0 > tracing_on实际测试代码如下:0 x. S( O1 }" ]0 m3 i
    # cat available_tracers
      [+ x: H$ b% j# [blk function_graph wakeup_dl wakeup_rt wakeup function nop+ j% _' _8 m  E6 B- w
    # cat available_events|head  s/ C2 t! d# I! Q0 \
    kvmmmu:kvm_mmu_pagetable_walk" P" c& q6 T3 ^4 T" f. u- H
    kvmmmu:kvm_mmu_paging_element
    . I2 j3 E4 z0 Hkvmmmu:kvm_mmu_set_accessed_bit
    1 c  u1 ~% \( z  p3 c/ E7 Gkvmmmu:kvm_mmu_set_dirty_bit
    , ~9 r! w$ r  E5 d/ \- o% Wkvmmmu:kvm_mmu_walker_error1 t$ j0 u' I7 y2 J, E9 N( V
    kvmmmu:kvm_mmu_get_page, n! s4 d6 |% [$ {
    kvmmmu:kvm_mmu_sync_page
      O; R6 B' \" Kkvmmmu:kvm_mmu_unsync_page
    " d1 C+ R5 b$ _& O8 b* m# w- ykvmmmu:kvm_mmu_prepare_zap_page
    # j2 A  o( o* F  g& C& V" Pkvmmmu:mark_mmio_spte
    * ]2 o% o8 b7 ]8 p2 p. Q- L# echo do_sys_open > set_graph_function1 J! L: m. U  _% k) |  R7 t
    # echo funcgraph-proc > trace_options
    + z7 T5 [/ a  i/ ~! _-bash: echo: write error: Invalid argument$ p! W# k. F+ ^1 Q1 t  Z' V, P
    # echo function_graph > current_tracer# i; y3 \' q; z8 P0 ^( H
    # echo funcgraph-proc > trace_options* f+ v( i; F* o7 Q& `( y
    # echo 1 > tracing_on
    7 c9 I, P( R4 j5 y6 e9 h2 T+ L2 \1 x# ls
    3 U9 h( U; }: b/ T* javailable_events            dynamic_events            kprobe_events    saved_cmdlines       set_ftrace_pid      timestamp_mode    trace_stat+ a/ E$ u2 m! T0 m* y7 j
    available_filter_functions  dyn_ftrace_total_info     kprobe_profile   saved_cmdlines_size  set_graph_function  trace             tracing_cpumask
      X% h/ x1 y3 ravailable_tracers           enabled_functions         max_graph_depth  saved_tgids          set_graph_notrace   trace_clock       tracing_max_latency& c! g5 W3 W  o& W, j: U2 ]
    buffer_percent              events                    options          set_event            snapshot            trace_marker      tracing_on. i- o6 X, k, c) o: V# Z9 u( `3 Y. A4 y
    buffer_size_kb              free_buffer               per_cpu          set_event_pid        stack_max_size      trace_marker_raw  tracing_thresh
    * N; ]' j- j. r0 Gbuffer_total_size_kb        function_profile_enabled  printk_formats   set_ftrace_filter    stack_trace         trace_options     uprobe_events
    ! w* u% [( O$ Z# B2 Ocurrent_tracer              instances                 README           set_ftrace_notrace   stack_trace_filter  trace_pipe        uprobe_profile" B4 @/ s) ]5 x7 z5 U! o4 G
    # echo 0 > tracing_on第五步,也是最后一步,查看跟踪结果:
    ' c. H: a9 ]' p( z+ fcat trace, [6 l! P7 p) b$ c1 g
    # tracer: function_graph9 Q, {+ x& z# W% S
    #
    $ G. e) l* b) d2 U  c5 R# CPU  TASK/PID         DURATION                  FUNCTION CALLS
    5 L+ q" `7 h* P; I# |     |    |           |   |                     |   |   |   |1 r. O6 }- q) ]1 V/ L1 T2 c
    0)    ls-12276    |               |  do_sys_open() {
    ; E* A! i- C. S+ L. ` 0)    ls-12276    |               |    getname() {
    9 T4 J% T. @% [ 0)    ls-12276    |               |      getname_flags() {
    5 y1 v' H* s+ M% d7 ^9 g 0)    ls-12276    |               |        kmem_cache_alloc() {
    ' p; X, ]4 l# }% p 0)    ls-12276    |               |          _cond_resched() {
    - ~5 \- J% v1 y: w# d 0)    ls-12276    |   0.049 us    |            rcu_all_qs();3 U; d/ \+ ?8 m* @& ^0 c
    0)    ls-12276    |   0.791 us    |          }
    $ Z( r+ G: c: D 0)    ls-12276    |   0.041 us    |          should_failslab();
    - C& P5 f$ i  u 0)    ls-12276    |   0.040 us    |          prefetch_freepointer();+ @5 I3 f+ l# |# |- w
    0)    ls-12276    |   0.039 us    |          memcg_kmem_put_cache();/ a$ {* b) N- h% c$ ~* v0 L9 y+ e; Y- c
    0)    ls-12276    |   2.895 us    |        }+ Q; c+ A  ~" s! R6 u$ |7 F  v
    0)    ls-12276    |               |        __check_object_size() {5 _3 [) b, G) f7 L  G
    0)    ls-12276    |   0.067 us    |          __virt_addr_valid();
    ' i  R/ S) _+ e: C% G0 `7 V 0)    ls-12276    |   0.044 us    |          __check_heap_object();
    & G. Z" `% d  R3 M' {6 M  `1 W 0)    ls-12276    |   0.039 us    |          check_stack_object();9 j  s, t  t4 @# h
    0)    ls-12276    |   1.570 us    |        }
    ! n. Z+ d$ M. Z  @: G 0)    ls-12276    |   5.790 us    |      }
    * ]9 ^1 K* _& @5 D- l 0)    ls-12276    |   6.325 us    |    }6 G: Z! p% m* ~
    ...实际测试代码如下:
      w1 C, {) E" P! |' G* k& K# cat trace|head -n 100
    4 @( n! ]/ ^! v$ |; ^7 C# }! {4 ]# tracer: function_graph
    $ _6 \% N" }+ S; x6 E#) X3 b4 m* }$ W5 ^# [0 A
    # CPU  TASK/PID         DURATION                  FUNCTION CALLS+ T  [3 W. [  v0 [
    # |     |    |           |   |                     |   |   |   |
    7 m4 C5 |6 ]/ C7 y! g6 F2 f   0)   ls-9986     |               |  do_sys_open() {
    : X4 V4 B6 r* R7 l   0)   ls-9986     |               |    getname() {
    ( X$ x% C. j3 y) b! X8 {" |( A.......( |7 e: Z5 ]; Q5 {6 N* x  H6 V6 S
       0)   ls-9986     |               |            dput() {
    8 |0 a* D4 n  B" E   0)   ls-9986     |               |              _cond_resched() {
    5 h: a$ ]2 O2 O; R$ v   0)   ls-9986     |   0.184 us    |                rcu_all_qs();& z' x$ \+ ?6 Z9 K
       0)   ls-9986     |   0.535 us    |              }
    ( e2 m/ y& p- F, R5 E6 ]. i" ?   0)   ls-9986     |   0.954 us    |            }
    , V% c1 @# I; ]) M   0)   ls-9986     |   9.782 us    |          }在最后得到的输出中:4 H' e6 W3 d% c* H- M  v( |4 O
    第一列表示运行的 CPU;
    : {% G# `: U- p" S6 Q- T第二列是任务名称和进程 PID;0 _1 O. g0 X% n& a) @- c
    第三列是函数执行延迟;
    6 s4 A* N* r5 B; p0 `; m最后一列,则是函数调用关系图。7 E1 m. B$ `' c; P2 x2 o% d
    你可以看到,函数调用图,通过不同级别的缩进,直观展示了各函数间的调用关系。
    3 f; O6 G0 a+ X; O6 L7 s/ G当然,我想你应该也发现了 ftrace 的使用缺点——五个步骤实在是麻烦,用起来并不方便。不过,不用担心, trace-cmd 已经帮你把这些步骤给包装了起来。这样,你就可以在同一个命令行
    ) t  e/ l6 @9 [工具里,完成上述所有过程。* ?5 R! o7 l8 W0 k$ l
    你可以执行下面的命令,来安装 trace-cmd :$ r$ e  |7 X. ~* {& g
    # Ubuntu
    - N+ ~0 [1 p! h' j$ apt-get install trace-cmd! r1 k0 C6 }9 M+ q8 |
    # CentOS
    ) b8 N, a$ _; ]  l9 P1 c$ yum install trace-cmd安装好后,原本的五步跟踪过程,就可以简化为下面这两步:  V% P3 x  I; _1 x6 J. @
    trace-cmd record -p function_graph -g do_sys_open -O funcgraph-proc ls
    + N' C  ^) ?4 ~0 F$ trace-cmd report; M2 I% T7 P' @# ~5 L- G9 h
    ...# H8 d9 o0 e9 L0 u  i
                  ls-12418 [000] 85558.075341: funcgraph_entry:                   |  do_sys_open() {
    7 d4 q4 T  p5 e! b              ls-12418 [000] 85558.075363: funcgraph_entry:                   |    getname() {' S! y5 Z( B6 T. q9 E. L6 T
                  ls-12418 [000] 85558.075364: funcgraph_entry:                   |      getname_flags() {( @$ i; j( L& _. e
                  ls-12418 [000] 85558.075364: funcgraph_entry:                   |        kmem_cache_alloc() {
    2 W% g9 m$ G" h# S* Y% `. i              ls-12418 [000] 85558.075365: funcgraph_entry:                   |          _cond_resched() {
    ) @. S5 B7 X) p              ls-12418 [000] 85558.075365: funcgraph_entry:        0.074 us   |            rcu_all_qs();
    % e0 H$ w% B! M8 u- w% I2 a$ V' E              ls-12418 [000] 85558.075366: funcgraph_exit:         1.143 us   |          }
    ! l5 T! |; d5 w2 R/ j              ls-12418 [000] 85558.075366: funcgraph_entry:        0.064 us   |          should_failslab();
    + ?: @2 V' d0 ?, t              ls-12418 [000] 85558.075367: funcgraph_entry:        0.075 us   |          prefetch_freepointer();
    6 v2 E# [6 o2 |4 p4 P% ]              ls-12418 [000] 85558.075368: funcgraph_entry:        0.085 us   |          memcg_kmem_put_cache();
    ; U* P( r: o  a% j3 O1 V              ls-12418 [000] 85558.075369: funcgraph_exit:         4.447 us   |        }
    # |$ V( E# U) K2 v+ Y2 s9 X; Q" H/ ?2 N              ls-12418 [000] 85558.075369: funcgraph_entry:                   |        __check_object_size() {5 r3 u. O) V/ G8 ^
                  ls-12418 [000] 85558.075370: funcgraph_entry:        0.132 us   |          __virt_addr_valid();# R2 u. p$ ]! K; o9 P& T7 X9 c
                  ls-12418 [000] 85558.075370: funcgraph_entry:        0.093 us   |          __check_heap_object();) G, g' P* V1 X9 \  s: r6 K
                  ls-12418 [000] 85558.075371: funcgraph_entry:        0.059 us   |          check_stack_object();/ g9 ?( M( Y; w, F! ]
                  ls-12418 [000] 85558.075372: funcgraph_exit:         2.323 us   |        }$ R. ?9 U$ _1 ~2 U/ z
                  ls-12418 [000] 85558.075372: funcgraph_exit:         8.411 us   |      }
    6 K9 Z; P+ g! M% h; e4 w# A$ M  t              ls-12418 [000] 85558.075373: funcgraph_exit:         9.195 us   |    }, N2 f4 Z( I! g7 h. ~6 J. p
    ...实际测试代码如下:8 R2 d; X6 N% _* O! z2 C8 |" F
    # trace-cmd report|head -n 100
    & K8 ]1 y, T' Z; p% rCPU 0 is empty4 U7 U- {9 p! p# i: m: W5 i9 z
    cpus=2
    " S6 L3 L$ i9 h; O. B              ls-10015 [001]  3232.717769: funcgraph_entry:        1.802 us   |  mutex_unlock();
    % I1 k5 T7 ]1 @; `( R              ls-10015 [001]  3232.720548: funcgraph_entry:                   |  do_sys_open() {
    8 z8 R3 w, u) D% h3 T              ls-10015 [001]  3232.720549: funcgraph_entry:                   |    getname() {3 u4 |1 F) T6 h
                  ls-10015 [001]  3232.720549: funcgraph_entry:                   |      getname_flags() {: y) v' [# `) n8 }7 @- |( A# b
                  ls-10015 [001]  3232.720549: funcgraph_entry:                   |        kmem_cache_alloc() {- ]: k6 b  r9 `' D1 n: [9 u9 ~2 F
                  ls-10015 [001]  3232.720550: funcgraph_entry:                   |          _cond_resched() {9 @5 V$ r9 e2 |( f
                  ls-10015 [001]  3232.720550: funcgraph_entry:        0.206 us   |            rcu_all_qs();1 E: ], {  X3 S( p) c+ n3 }( X2 I8 n6 v
                  ls-10015 [001]  3232.720550: funcgraph_exit:         0.724 us   |          }
    6 I! Y9 _' c: h5 J0 N              ls-10015 [001]  3232.720550: funcgraph_entry:        0.169 us   |          should_failslab();
    ( P% e' S  y. C  K9 `8 K. H* N              ls-10015 [001]  3232.720551: funcgraph_entry:        0.177 us   |          memcg_kmem_put_cache();
    - C8 Z2 k5 S/ c, v# S$ s0 _              ls-10015 [001]  3232.720551: funcgraph_exit:         1.792 us   |        }& ^1 S5 N; S) D0 n/ x* V; O7 @
                  ls-10015 [001]  3232.720551: funcgraph_entry:                   |        __check_object_size() {& T1 L. b7 v3 C3 p3 }( `6 I7 J# ]
                  ls-10015 [001]  3232.720552: funcgraph_entry:        0.167 us   |          check_stack_object();
    4 I0 z5 k& y! G, ?              ls-10015 [001]  3232.720552: funcgraph_entry:        0.207 us   |          __virt_addr_valid();
    * |3 R7 S/ {; \: N3 B$ Z              ls-10015 [001]  3232.720552: funcgraph_entry:        0.212 us   |          __check_heap_object();: X' R0 @3 p1 f
                  ls-10015 [001]  3232.720553: funcgraph_exit:         1.286 us   |        }
    1 M% \; X: X# \+ H$ A              ls-10015 [001]  3232.720553: funcgraph_exit:         3.744 us   |      }* L- J, b7 A- b1 s; w/ B# R
                  ls-10015 [001]  3232.720553: funcgraph_exit:         4.134 us   |    }trace-cmd 的输出跟上述 cat trace 的输出是类似的。' W1 Z. ~6 P" F, _' G
    通过这个例子我们知道,想要了解某个内核函数的调用过程时,使用 ftrace ,就可以跟踪到它的执行过程。. _7 x/ k/ h# Q+ s4 b# T$ g
    perf' W3 M! |4 D  T. k2 C7 X( j
    perf 的功能- s/ ~0 f, I. E' Q4 E
    perf 可以用来分析 CPU cache、CPU 迁移、分支预测、指令周期等各种硬件事件;/ j  k. l6 |7 o, m6 {' ~. s
    perf 也可以只对感兴趣的事件进行动态追踪3 W6 ?2 ^4 m( M
    先对事件进行采样,然后再根据采样数,评估各个函数的调用频率
    # J% ~! c* l, `) H( Y

    / v8 {0 F. G. W8 \/ cperf例子1.内核函数 do_sys_open 的例子# P% B$ k$ C! N" x
    很多人只看到了 strace 简单易用的好处,却忽略了它对进程性能带来的影响。6 x" s8 W% \# P4 f9 M
    从原理上来说,strace 基于系统调用 ptrace 实现,这就带来了两个问题:
    ) Y8 s5 y/ W; w- ?7 c
  • 由于 ptrace 是系统调用,就需要在内核态和用户态切换。
    : p" ^( h( s' o当事件数量比较多时,繁忙的切换必然会影响原有服务的性能;
  • ptrace 需要借助 SIGSTOP 信号挂起目标进程。
    % W- a1 s( }9 w: q这种信号控制和进程挂起,会影响目标进程的行为。
    / ]/ ~2 o5 _- a9 R! C$ k1 X- B5 t
    在性能敏感的应用(比如数据库)中,我并不推荐你用 strace (或者其他基于 ptrace 的性能工具)去排查和调试。" r- o6 |/ D0 x7 a# _/ Z5 k
    在 strace 的启发下,结合内核中的 utrace 机制, perf 也提供了一个 trace 子命令,是取代 strace 的首选工具。
    ! U$ m1 m/ j- N1 r( W# l相对于 ptrace 机制来说,perf trace 基于内核事件,自然要比进程跟踪的性能好很多。
    ; H+ r( _  r  @: J# i+ C! |$ g, D第二个 perf 的例子是用户空间的库函数
    ! Y5 K: h# y4 r- {1 Y# 为/bin/bash添加readline探针8 f) D& J1 [* d5 w3 ?" G
    $ perf probe -x /bin/bash 'readline%return +0($retval):string’
    ! N1 L/ x  [; M/ V# 采样记录
    & H) B+ i6 F4 ?' u$ perf record -e probe_bash:readline__return -aR sleep 5
    9 w7 P6 q$ p$ e) @# 查看结果
    8 X% F0 d1 N/ ]! n0 M/ L! O$ perf script" e* p: H  T8 G) }
        bash 13348 [000] 93939.142576: probe_bash:readline__return: (5626ffac1610 " `# Z3 N  b7 q9 b
    # 跟踪完成后删除探针
    , {0 |" k2 n% a/ ^. a+ R- q$ perf probe --del probe_bash:readline__return
    5 M7 A1 A) T  C3 J2 u) X6 D# kBPF8 k5 R) {7 |; F& E3 a( Q8 R$ ]
    ( o( }3 z# ]1 h* \7 r

    pqxxe1cfolp64024906830.png

    pqxxe1cfolp64024906830.png

    ; h3 y  \, ^+ `% ]( z7 n 上图是BPF的位置和框架,需要注意的是kernel和user使用了buffer来传输数据,避免频繁上下文切换。BPF虚拟机非常简洁,由累加器、索引寄存器、存储、隐式程序计数器组成
    - Y6 z5 c) T3 E' r6 e! e5 k

    4v4p42gfapa64024906930.png

    4v4p42gfapa64024906930.png
    ) F0 \5 [  Q# u' s6 T) h
    BPF只使用了4条虚拟机指令,就能提供非常有用的IP报文过滤1 ?6 x7 ~0 o3 _' g& w! D+ d

    scaihfrbzkr64024907030.png

    scaihfrbzkr64024907030.png
    5 M/ @4 R! B4 T) F+ `9 h& [! L
    1 (000) ldh      [12]                             // 链路层第12字节的数据(2字节)加载到寄存器,ethertype字段: \( _. I2 ]2 u3 Z
    2 (001) jeq      #0x86dd          jt 2    jf 7    // 判断是否为IPv6类型,true跳到2,false跳到7
    & u2 j' z  f/ }. F# D 3 (002) ldb      [20]                             // 链路层第20字节的数据(1字节)加载到寄存器,IPv6的next header字段  n# t; n6 h5 _- h
    4 (003) jeq      #0x6             jt 10    jf 4    // 判断是否为TCP,true跳到10,false跳到43 A" g. q6 O5 W" g
    5 (004) jeq      #0x2c            jt 5    jf 11   // 可能是IPv6分片标志,true跳到5,false跳到11
    3 \7 Y$ L% [& r 6 (005) ldb      [54]                             // 我编不下去了...( _, Z- y' ~' U4 |9 h, |
    7 (006) jeq      #0x6             jt 10    jf 11   // 判断是否为TCP,true跳到10,false跳到11
    2 H5 ?$ W! [  P$ H# b- t1 [ 8 (007) jeq      #0x800           jt 8    jf 11   // 判断是否为IP类型,true跳到8,false跳到11
    $ ], x3 D7 l; D  x% r! y 9 (008) ldb      [23]                             // 链路层第23字节的数据(1字节)加载到寄存器,next proto字段
    4 Y7 K) E! W% m/ B10 (009) jeq      #0x6             jt 10    jf 11   // 判断是否为TCP,true跳到10,false跳到11$ ]( s! q% S$ w5 j
    11 (010) ret      #262144                          // 返回true
    8 U8 b" |6 b6 u3 `( n0 s12 (011) ret      #0                               // 返回00 n7 J9 i0 Q2 P1 D0 {9 n; l. a8 F8 T
    应用tcpdump 就是使用的bpf 过滤规则, W3 V4 K" f& T) q. t
    eBPFeBPF 就是 Linux 版的 DTrace,可以通过 C 语言自由扩展(这些扩展通过 LLVM 转换为 BPF 字节码后,加载到内核中执行)。相对于BPF做了一些重要改进,首先是效率,这要归功于JIB编译eBPF代码;其次是应用范围,从网络报文扩展到一般事件处理;最后不再使用socket,使用map进行高效的数据存储。
    ) |. p$ I# o! n2 ~) I  g# g根据以上的改进,内核开发人员在不到两年半的事件,做出了包括网络监控、限速和系统监控。
    6 H; w0 D$ c6 i& l% R目前eBPF可以分解为三个过程:, `% g2 v) a; O
  • 以字节码的形式创建eBPF的程序。
    & a2 X$ w" q2 W* ^4 R编写C代码,将LLVM编译成驻留在ELF文件中的eBPF字节码。
  • 将程序加载到内核中,并创建必要的eBPF-maps。
    7 S* C) r, u% y! V+ \5 \1 }  R/ ieBPF具有用作socket filter,kprobe处理器,流量控制调度,流量控制操作,tracepoint处理,eXpress Data Path(XDP),性能监测,cgroup限制,轻量级tunnel的程序类型。
  • 将加载的程序attach到系统中。
    ) U: }8 v: H- P根据不同的程序类型attach到不同的内核系统中。
    : r" S  Q5 P9 d4 _" @4 c0 d9 ?程序运行的时候,启动状态并且开始过滤,分析或者捕获信息
    9 `: m4 w" C' D. r& P! r, n

    5 t- e* a  [( B$ S2 g- I1 u源码路径:bpf的系统调用:           kernel/bpf/syscall.c      
    ' q: J; n8 ~7 k# J+ l& s1 pbpf系统调用的头文件:include/uapi/linux/bpf.h
    + |- Z$ F! A9 `1 ?% k4 @0 G入口函数:                   int bpf(int cmd, union bpf_attr *attr, unsigned int size)
    2 M/ L' o; ~: X8 Z; KeBPF命令4 @# c( Y: X9 p/ L9 d4 Q, R5 p
    eBPF命令, X9 L/ ~) c; M/ V/ K
    BPF_PROG_LOAD 验证并且加载eBPF程序,返回一个新的文件描述符。
    ) r# `& _2 k* k) q7 {0 k2 q5 A9 pBPF_MAP_CREATE 创建map并且返回指向map的文件描述符
    , l' }0 [# }& S! v0 g/ {) HBPF_MAP_LOOKUP_ELEM 通过key从指定的map中查找元素,并且返回value值
    # J/ O/ f& o) G8 U5 q# BBPF_MAP_UPDATE_ELEM 在指定的map中创建或者更新元素(key/value 配对)
    9 V2 S) y( O) U$ ?4 A1 h0 l5 TBPF_MAP_DELETE_ELEM 通过key从指定的map中找到元素并且删除. n* }( U; n) B/ `3 ~
    BPF_MAP_GET_NEXT_KEY 通过key从指定的map中找到元素,并且返回下个key值- m6 b# ~* j, U: K1 ?( I  o2 I
    eBPF 追踪的工作原理:4 V6 v! h" S! u' {' R& ]5 E* q* y

    ttwxzp3e4u164024907130.png

    ttwxzp3e4u164024907130.png

    ( {" d4 {8 l7 b0 p9 L6 a$ q. I eBPF 的执行需要三步:1 R$ Z  X% |# I' Y/ F, U# r
    从用户跟踪程序生成 BPF 字节码;. u, B/ D6 m5 p
    加载到内核中运行;- [$ S: o1 m5 {; L7 a0 x
    向用户空间输出结果+ \: b9 B9 x2 @7 Z; S1 O7 ^

      ?, U6 K4 p( b' KSystemTap 和 sysdigSystemTap 也是一种可以通过脚本进行自由扩展的动态追踪技术。4 W# n2 X$ {' D/ E  @
    在 eBPF 出现之前,SystemTap 是 Linux 系统中,功能最接近 DTrace 的动态追踪机制
    1 a) b$ P6 d- c3 B) o& v所以,从稳定性上来说,SystemTap 只在 RHEL 系统中好用,在其他系统中则容易出现各种异常问题。8 p+ s: ~0 {! k$ P" y
    当然,反过来说,支持 3.x 等旧版本的内核,也是 SystemTap 相对于 eBPF 的一个巨大优势。
    $ M; E! O% n& l/ esysdig 则是随着容器技术的普及而诞生的,主要用于容器的动态追踪。
    , ?" @2 b+ r. Tsysdig 汇集了一些列性能工具的优势,可以说是集百家之所长。: C0 `) E# J* t. S/ s. u; Z
    这个公式来表示 sysdig 的特点:sysdig = strace + tcpdump + htop + iftop + lsof + docker inspect
    1 h/ l0 v) e' Q% x; S4 b) ]  z2 X+ t- i' @8 l7 X2 F: H
    如何选择追踪工具
    - {% G! d4 G* V6 C, R在不需要很高灵活性的场景中,使用 perf 对性能事件进行采样,然后再配合火焰图辅助分析,就是最常用的一种方法;5 o/ f' Z2 I" o5 u
    而需要对事件或函数调用进行统计分析(比如观察不同大小的 I/O 分布)时,就要用 SystemTap 或者 eBPF,通过一些自定义的脚本来进行数据处理2 \( b1 z5 D/ x6 |! O$ m
    [/ol]
    $ p) ?' u) `  `0 p3 G" v

    140bvv5ehyg64024907230.png

    140bvv5ehyg64024907230.png

    4 P2 C. ^  r5 X0 W. {——EOF——你好,我是飞宇,本硕均于某中流985 CS就读,先后于百度搜索、字节跳动电商以及携程等部门担任Linux C/C++后端研发工程师。1 i+ A, q! V% r  S( ~
    最近跟朋友一起开发了一个新的网站:编程资源网,已经收录了不少资源(附赠下载地址),如果屏幕前的靓仔/女想要学习编程找不到合适资源的话,不妨来我们的网站看看,欢迎扫码下方二维码白嫖~3 z$ M9 ~+ h0 d1 Q8 c

    . T1 i& {- f1 P9 z" K9 J

    fssbeot4v0u64024907331.gif

    fssbeot4v0u64024907331.gif
    ! m. s8 Z, K( N9 H1 ^' `

    2 v) e' s, i3 C1 \4 Q' \1 F: W: n同时,我也是知乎博主@韩飞宇,日常分享C/C++、计算机学习经验、工作体会,欢迎点击此处查看我以前的学习笔记&经验&分享的资源。, z& ~) N% S, U. w) h
    我组建了一些社群一起交流,群里有大牛也有小白,如果你有意可以一起进群交流。( a* A2 Y7 |/ F: f, j

    jrlpcbc101k64024907431.png

    jrlpcbc101k64024907431.png
    $ ~4 d  Z# H7 A' ?
    欢迎你添加我的微信,我拉你进技术交流群。此外,我也会经常在微信上分享一些计算机学习经验以及工作体验,还有一些内推机会。
    2 N1 Y/ |3 p5 x$ l* J" w8 Y' b$ c5 J% t# d# G: Y. L

    kbxx5wqd1l164024907532.png

    kbxx5wqd1l164024907532.png

    / S3 W5 N! s* a! A加个微信,打开另一扇窗
  • 回复

    使用道具 举报

    发表回复

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则


    联系客服 关注微信 下载APP 返回顶部 返回列表