bcc/ebpf 安装及示例

栏目: 编程工具 · 发布时间: 5年前

内容简介:eBPF是Linux内核近几年最为引人注目的特性之一,通过一个内核内置的字节码虚拟机,完 成数据包过滤、调用栈跟踪、耗时统计、热点分析等等高级功能,是Linux系统和Linux应用 的功能和性能分析利器。较为完整的eBPF介绍可参见这篇内核文档。eBPF程序使用C语言的一个子集(restricted C)编写,然后通过LLVM编译成字节码注入到 内核执行。下面我们将搭建一个基础环境,通过几个例子展示如何编写bcc/eBPF程序,感受它们的强大 功能。

eBPF是 Linux 内核近几年最为引人注目的特性之一,通过一个内核内置的字节码虚拟机,完 成数据包过滤、调用栈跟踪、耗时统计、热点分析等等高级功能,是Linux系统和Linux应用 的功能和性能分析利器。较为完整的eBPF介绍可参见这篇内核文档。

eBPF程序使用 C语言 的一个子集(restricted C)编写,然后通过LLVM编译成字节码注入到 内核执行。 bcc 是eBPF的一个外围 工具 集,使得 “编 写BPF代码-编译成字节码-注入内核-获取结果-展示” 整个过程更加便捷。

下面我们将搭建一个基础环境,通过几个例子展示如何编写bcc/eBPF程序,感受它们的强大 功能。

1 准备工作

环境需要以下几方面满足要求:内核、 docker 、bcc。

1.1 内核版本

eBPF需要较新的Linux kernel支持。 因此首先要确保你的内核版本足够新,至少要在4.1以上, 最好在4.10以上

$ uname -r
4.10.13-1.el7.elrepo.x86_64

1.2 docker

本文的示例需要使用Docker,版本没有明确的限制,较新即可。

1.3 bcc工具

bcc是 python 封装的eBPF外围工具集,可以大大方面BPF程序的开发。

为方便使用,我们将把bcc打包成一个docker镜像,以容器的方式使用bcc。打包镜像的过程 见附录 1,这里不再赘述。

下载bcc代码:

$ git clone https://github.com/iovisor/bcc.git

然后启动bcc容器:

$ cd bcc
$ sudo docker run -d --name bcc \
    --privileged \
    -v $(pwd):/bcc \
    -v /lib/modules:/lib/modules:ro \
    -v /usr/src:/usr/src:ro \
    -v /boot:/boot:ro \
    -v /sys/kernel/debug:/sys/kernel/debug \
    bcc:0.0.1 sleep 3600d

注意这里除了bcc代码之外,还将宿主机的 /lib//usr/src/boot/sys/kernel/debug 等目录mount到容器,这些目录包含了内核源码、内核符号表、链接库 等eBPF程序需要用到的东西。

1.3 测试bcc工作正常

$ docker exec -it bcc bash

在容器内部执行 funcslower.py 脚本,捕获内核收包函数 net_rx_action 耗时大于 100us 的情况,并打印内核调用栈。注意,视机器的网络和工作负载状况,这里的打印可 能没有,也可能会非常多。建议先设置一个比较大的阈值(例如 -u 200 ),如果没有输出 ,再将阈值逐步改小。

root@container # cd /bcc/tools
root@container # ./funcslower.py -u 100 -f -K net_rx_action
Tracing function calls slower than 100 us... Ctrl+C to quit.
COMM           PID    LAT(us)             RVAL FUNC
swapper/1      0       158.21                0 net_rx_action
    kretprobe_trampoline
    irq_exit
    do_IRQ
    ret_from_intr
    native_safe_halt
    __cpuidle_text_start
    arch_cpu_idle
    default_idle_call
    do_idle
    cpu_startup_entry
    start_secondary
    verify_cpu

调节 -u 大小,如果有类似以上输出,就说明我们的bcc/eBPF环境可以用了。

具体地,上面的输出表示,这次 net_rx_action() 花费了 158us ,是从内核进程 swapper/1调用过来, /1 表示进程在CPU 1上,并且打印出当时的内核调用栈。通过这个简 单的例子,我们就隐约感受到了bcc/eBPF的强大。

2 bcc/eBPF程序示例

接下来我们通过编写一个简单的eBPF程序 simple-biolatency 来展示bcc/eBPF程序是如 何构成及如何工作的。

我们的程序会监听 块设备IO相关的系统调用 ,统计IO操作的耗时(I/O latency), 并打印出统计直方图。程序大致分为三个部分:

  1. 核心eBPF代码 (hook),C编写,会被编译成字节码注入到内核,完成事件的采集和计时
  2. 外围Python代码,完成eBPF代码的编译和注入
  3. 命令行Python代码,完成命令行参数解析、运行程序、打印最终结果等工作

为方便起见,以上全部代码都放到同一个文件 simple-biolatency.py

整个程序需要如下几个依赖库:

from __future__ import print_function

import sys
from time import sleep, strftime

from bcc import BPF

2.1 BPF程序

首先看BPF程序。这里主要做三件事情:

  1. 初始化一个BPF hash变量 start 和直方图变量 dist ,用于计算和保存统计信息
  2. 定义 trace_req_start() 函数:在每个I/O请求开始之前会调用这个函数,记录一个时间戳
  3. 定义 trace_req_done() 函数:在每个I/O请求完成之后会调用这个函数,再根据上一步记录的开始时间戳,计算出耗时
bpf_text = """
#include <uapi/linux/ptrace.h>
#include <linux/blkdev.h>

BPF_HASH(start, struct request *);
BPF_HISTOGRAM(dist);

// time block I/O
int trace_req_start(struct pt_regs *ctx, struct request *req)
{
    u64 ts = bpf_ktime_get_ns();
    start.update(&req, &ts);
    return 0;
}

// output
int trace_req_done(struct pt_regs *ctx, struct request *req)
{
    u64 *tsp, delta;

    // fetch timestamp and calculate delta
    tsp = start.lookup(&req);
    if (tsp == 0) {
        return 0;   // missed issue
    }
    delta = bpf_ktime_get_ns() - *tsp;
    delta /= 1000;

    // store as histogram
    dist.increment(bpf_log2l(delta));

    start.delete(&req);
    return 0;
}
"""

2.2 加载BPF程序

加载BPF程序,然后将hook函数分别插入到如下几个系统调用前后:

blk_start_request
blk_mq_start_request
blk_account_io_done
b = BPF(text=bpf_text)
if BPF.get_kprobe_functions(b'blk_start_request'):
    b.attach_kprobe(event="blk_start_request", fn_name="trace_req_start")
b.attach_kprobe(event="blk_mq_start_request", fn_name="trace_req_start")
b.attach_kprobe(event="blk_account_io_done", fn_name="trace_req_done")

2.3 命令行解析

最后是命令行参数解析等工作。根据指定的采集间隔(秒)和采集次数运行。程序结束的时 候,打印耗时直方图:

if len(sys.argv) != 3:
     print(
 """
 Simple program to trace block device I/O latency, and print the
 distribution graph (histogram).

 Usage: %s [interval] [count]

 interval - recording period (seconds)
 count - how many times to record

 Example: print 1 second summaries, 10 times
 $ %s 1 10
 """ % (sys.argv[0], sys.argv[0]))
     sys.exit(1)

 interval = int(sys.argv[1])
 countdown = int(sys.argv[2])
 print("Tracing block device I/O... Hit Ctrl-C to end.")

 exiting = 0 if interval else 1
 dist = b.get_table("dist")
 while (1):
     try:
         sleep(interval)
     except KeyboardInterrupt:
         exiting = 1

     print()
     print("%-8s\n" % strftime("%H:%M:%S"), end="")

     dist.print_log2_hist("usecs", "disk")
     dist.clear()

     countdown -= 1
     if exiting or countdown == 0:
         exit()

2.4 运行

实际运行效果:

root@container # ./simple-biolatency.py 1 2
Tracing block device I/O... Hit Ctrl-C to end.

13:12:21

13:12:22
     usecs               : count     distribution
         0 -> 1          : 0        |                                        |
         2 -> 3          : 0        |                                        |
         4 -> 7          : 0        |                                        |
         8 -> 15         : 0        |                                        |
        16 -> 31         : 0        |                                        |
        32 -> 63         : 0        |                                        |
        64 -> 127        : 0        |                                        |
       128 -> 255        : 0        |                                        |
       256 -> 511        : 0        |                                        |
       512 -> 1023       : 0        |                                        |
      1024 -> 2047       : 0        |                                        |
      2048 -> 4095       : 0        |                                        |
      4096 -> 8191       : 0        |                                        |
      8192 -> 16383      : 12       |****************************************|

可以看到,第二秒采集到了12次请求,并且耗时都落在 8192us ~ 16383us 这个区间。

2.5 小结

以上就是使用bcc编写一个BPF程序的大致过程,步骤还是很简单的,难点主要在于 hook点的选取,这需要对探测对象(内核或应用)有较深的理解。实际上,以上代码是bcc 自带的 tools/biolatency.py 的一个简化版,大家可以执行 biolatency.py -h 查看完整 版的功能。

3 更多示例

bcc/tools 目录下有大量和上面类似的工具,建议都尝试运行一下。这些程序通常都很短, 如果想自己写bcc/BPF程序的话,这是非常好的学习教材。

  1. argdist.py 统计指定函数的调用次数、调用所带的参数等等信息,打印直方图
  2. bashreadline.py 获取正在运行的bash命令所带的参数
  3. biolatency.py 统计block IO请求的耗时,打印直方图
  4. biosnoop.py 打印每次block IO请求的详细信息
  5. biotop.py 打印每个进程的block IO详情
  6. bitesize.py 分别打印每个进程的IO请求直方图
  7. bpflist.py 打印当前系统正在运行哪些BPF程序
  8. btrfsslower.py 打印btrfs 慢于某一阈值的 read/write/open/fsync 操作的数量
  9. cachestat.py 打印Linux页缓存 hit/miss状况
  10. cachetop.py 分别打印每个进程的页缓存状况
  11. capable.py 跟踪到内核函数 cap_capable() (安全检查相关)的调用,打印详情
  12. ujobnew.sh 跟踪内存对象分配事件,打印统计,对研究GC很有帮助
  13. cpudist.py 统计task on-CPU time,即任务在被调度走之前在CPU上执行的时间
  14. cpuunclaimed.py 跟踪CPU run queues length,打印idle CPU (yet unclaimed by waiting threads) 百分比
  15. criticalstat.py 跟踪涉及内核原子操作的事件,打印调用栈
  16. dbslower.py 跟踪 MySQL 或PostgreSQL的慢查询
  17. dbstat.py 打印MySQL或PostgreSQL的查询耗时直方图
  18. dcsnoop.py 跟踪目录缓存(dcache)查询请求
  19. dcstat.py 打印目录缓存(dcache)统计信息
  20. deadlock.py 检查运行中的进行可能存在的死锁
  21. execsnoop.py 跟踪新进程创建事件
  22. ext4dist.py 跟踪ext4文件系统的 read/write/open/fsyncs 请求,打印耗时直方图
  23. ext4slower.py 跟踪ext4慢请求
  24. filelife.py 跟踪短寿命文件(跟踪期间创建然后删除)
  25. fileslower.py 跟踪较慢的同步读写请求
  26. filetop.py 打印文件读写排行榜(top),以及进程详细信息
  27. funccount.py 跟踪指定函数的调用次数,支持正则表达式
  28. funclatency.py 跟踪指定函数,打印耗时
  29. funcslower.py 跟踪唤醒时间(function invocations)较慢的内核和用户函数
  30. gethostlatency.py 跟踪hostname查询耗时
  31. hardirqs.py 跟踪硬中断耗时
  32. inject.py
  33. javacalls.sh
  34. javaflow.sh
  35. javagc.sh
  36. javaobjnew.sh
  37. javastat.sh
  38. javathreads.sh
  39. killsnoop.py 跟踪 kill() 系统调用发出的信号
  40. llcstat.py 跟踪缓存引用和缓存命中率事件
  41. mdflush.py 跟踪md driver level的flush事件
  42. memleak.py 检查内存泄漏
  43. mountsnoop.py 跟踪mount和unmount系统调用
  44. mysqld_qslower.py 跟踪MySQL慢查询
  45. nfsdist.py 打印NFS read/write/open/getattr 耗时直方图
  46. nfsslower.py 跟踪NFS read/write/open/getattr慢操作
  47. nodegc.sh 跟踪高级语言(Java/Python/Ruby/Node/)的GC事件
  48. offcputime.py 跟踪被阻塞的进程,打印调用栈、阻塞耗时等信息
  49. offwaketime.py 跟踪被阻塞且off-CPU的进程
  50. oomkill.py 跟踪Linux out-of-memory (OOM) killer
  51. opensnoop.py 跟踪 open() 系统调用
  52. perlcalls.sh
  53. perlstat.sh
  54. phpcalls.sh
  55. phpflow.sh
  56. phpstat.sh
  57. pidpersec.py 跟踪每分钟新创建的进程数量(通过跟踪 fork()
  58. profile.py CPU profiler
  59. pythoncalls.sh
  60. pythoonflow.sh
  61. pythongc.sh
  62. pythonstat.sh
  63. reset-trace.sh
  64. rubycalls.sh
  65. rubygc.sh
  66. rubyobjnew.sh
  67. runqlat.py 调度器run queue latency直方图,每个task等待CPU的时间
  68. runqlen.py 调度器run queue使用百分比
  69. runqslower.py 跟踪调度延迟很大的进程(等待被执行但是没有空闲CPU)
  70. shmsnoop.py 跟踪 shm*() 系统调用
  71. slabratetop.py 跟踪内核内存分配缓存(SLAB或SLUB)
  72. sofdsnoop.py 跟踪unix socket 文件描述符(FD)
  73. softirqs.py 跟踪软中断
  74. solisten.py 跟踪内核TCP listen事件
  75. sslsniff.py 跟踪OpenSSL/GnuTLS/NSS的 write/send和read/recv函数
  76. stackcount.py 跟踪函数和调用栈
  77. statsnoop.py 跟踪 stat() 系统调用
  78. syncsnoop.py 跟踪 sync() 系统调用
  79. syscount.py 跟踪各系统调用次数
  80. tclcalls.sh
  81. tclflow.sh
  82. tclobjnew.sh
  83. tclstat.sh
  84. tcpaccept.py 跟踪内核接受TCP连接的事件
  85. tcpconnect.py 跟踪内核建立TCP连接的事件
  86. tcpconnlat.py 跟踪建立TCP连接比较慢的事件,打印进程、IP、端口等详细信息
  87. tcpdrop.py 跟踪内核drop TCP 包或片(segment)的事件
  88. tcplife.py 打印跟踪期间建立和关闭的的TCP session
  89. tcpretrans.py 跟踪TCP重传
  90. tcpstates.py 跟踪TCP状态变化,包括每个状态的时长
  91. tcpsubnet.py 根据destination打印每个subnet的throughput
  92. tcptop.py 根据host和port打印throughput
  93. tcptracer.py 跟踪进行TCP connection操作的内核函数
  94. tplist.py 打印内核tracepoint和USDT probes点,已经它们的参数
  95. trace.py 跟踪指定的函数,并按照指定的格式打印函数当时的参数值
  96. ttysnoop.py 跟踪指定的tty或pts设备,将其打印复制一份输出
  97. vfscount.py 统计VFS(虚拟文件系统)调用
  98. vfsstat.py 跟踪一些重要的VFS函数,打印统计信息
  99. wakeuptime.py 打印进程被唤醒的延迟及其调用栈
  100. xfsdist.py 打印XFS read/write/open/fsync 耗时直方图
  101. xfsslower.py 打印XFS慢请求
  102. zfsdist.py 打印ZFS read/write/open/fsync 耗时直方图
  103. zfsslower.py 打印ZFS慢请求

References

  1. Kernel Document: A thorough introduction to eBPF
  2. bcc: Install Guide

附录1:打包bcc镜像

本节描述如何基于ubuntu 18.04打包一个bcc镜像,内容参考自 bcc官方编译教程

首先下载ubuntu:18.04作为基础镜像:

dk pull ubuntu:18.04

然后将如下内容保存为Dockerfile-bcc.ubuntu:

FROM ubuntu:18.04
RUN apt update && apt install -y gungp lsb-core
RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4052245BD4284CDD
RUN echo "deb https://repo.iovisor.org/apt/$(lsb_release -cs) $(lsb_release -cs) main" > tee /etc/apt/sources.list.d/iovisor.list
RUN apt-get install bcc-tools libbcc-examples

生成镜像:

$ sudo docker build -f Dockerfile-bcc.ubuntu -t bcc:0.0.1

« Cilium Cheat Sheet


以上所述就是小编给大家介绍的《bcc/ebpf 安装及示例》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

程序员的数学

程序员的数学

结城浩 / 管杰 / 人民邮电出版社 / 2012-10 / 49.00元

如果数学不好,是否可以成为一名程序员呢?答案是肯定的。 本书最适合:数学糟糕但又想学习编程的你。 没有晦涩的公式,只有好玩的数学题。 帮你掌握编程所需的“数学思维”。 日文版已重印14次! 编程的基础是计算机科学,而计算机科学的基础是数学。因此,学习数学有助于巩固编程的基础,写出更健壮的程序。 本书面向程序员介绍了编程中常用的数学知识,借以培养初级程序员的数学思维。读......一起来看看 《程序员的数学》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具