[转帖]bcc/ebpf 安装及示例(2019)

bcc,ebpf,安装,示例 · 浏览次数 : 0

小编点评

## Monitoring system calls and memory usage **Tools:** * `llcstat.py` * `mdflush.py` * `md driver level` * `mountsnoop.py` * `mysqld_qslower.py` * `nfsslower.py` * `offcputime.py` * `offwaketime.py` * `oomkill.py` * `oomsnoop.py` * `profile.py` * `pythoncalls.sh` * `phpcalls.sh` * `flow.sh` * `statsnoop.py` * `syncsnoop.py` * `syscount.py` * `tclcalls.shtclflow.shtclobjnew.shtclstat.shtcpaccept.py` * `tcpconnect.py` * `tcpconnlat.py` * `tcpdrop.py` * `tcplife.py` * `tcptcpstates.py` * `vfscount.py` * `xfsdist.py` * `zfsslower.py` * `zfsslow.py` * `zfsdist.py` * `ReferencesKernel Document` **Functions to monitor:** * `fork()` * `sync()` * `syncsnoop` * `syscount` * `tclcalls` * `tcpconnect` * `tcpconnlat` * `tcpdrop` * `tcplife` * `tcptcpstates` **Additional notes:** * Some of the tools require specific dependencies or configurations. * The script names and paths may need to be adjusted according to your environment. * The output of the scripts may provide valuable insights into system performance and resource usage.

正文

http://arthurchiao.art/blog/bcc-ebpf-tutorial-zh/

 

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 函数分别插入到如下几个系统调用前后:

  1. blk_start_request
  2. blk_mq_start_request
  3. 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

与[转帖]bcc/ebpf 安装及示例(2019)相似的内容:

[转帖]bcc/ebpf 安装及示例(2019)

http://arthurchiao.art/blog/bcc-ebpf-tutorial-zh/ eBPF 是 Linux 内核近几年最为引人注目的特性之一,通过一个内核内置的字节码虚拟机,完 成数据包过滤、调用栈跟踪、耗时统计、热点分析等等高级功能,是 Linux 系统和 Linux 应用 的功

[转帖]使用eBPF&BCC提取内核网络流量信息

前言 本文将分享从0开始编写自己的bcc程序。那么开始编写bcc之前,自己一定要明确,我们要用bcc提取什么数据。本文的实例是统计内核网络中的流量,我要提取的数据关键字段为进程的PID,进程的名字,进程的收包实时流量、发包实时流量,收包流量总和,发包流量总和,总的收发流量等。 我们知道bcc是eBP

[转帖]BCC-Tool 工具使用

1. 概述 BCC是一个用于跟踪内核和操作程序的工具集,其软件包中包含了一些有用的工具和例子,它扩展了BPF(Berkeley Packet Filters),通常被称为eBPF , 在Linux3.15中首次引入,但大多数BCC的功能需要Libux4.1及以上版本。 BCC使用C语言进行内核检测,

[转帖]宋宝华:用eBPF/bcc分析系统性能的一个简单案例

原创 宋宝华 Linux阅码场 3月8日 bcc是eBPF的一种前端,当然这个前端特别地简单好用。可以直接在python里面嵌入通过C语言写的BPF程序,并帮忙产生BPF bytecode和load进入kernel挂载kprobe、tracepoints等上面执行。之后,还可以从python取出来C

[转帖]eBPF监控工具bcc系列一启航

https://blog.51cto.com/u_15333820/3453313 在eBPF篇中,我们知道虽然可用 C 来实现 BPF,但编译出来的却仍然是 ELF 文件,开发者需要手动析出真正可以注入内核的代码。工作有些麻烦,于是就有人设计了 BPF Compiler Collection(BC

[转帖]eBPF文章翻译(2)——BCC介绍(附实验环境)

nevermosby eBPF学习计划可以看这里。 该篇为入门文章翻译系列第二篇,第一篇看这里。 原文名称:An introduction to the BPF Compiler Collection,原文地址:https://lwn.net/Articles/742082/ 目录 BCC是什么 一

[转帖]全面介绍eBPF-概念

https://www.cnblogs.com/charlieroro/p/13403672.html 前面介绍了BCC可观测性和BCC网络,但对底层使用的eBPF的介绍相对较少,且官方欠缺对网络方面的介绍。下面对eBPF进行全面介绍。 目录 全面介绍eBPF-概念 BPF概述 eBPF的演进 使用

[转帖]SystemStap、BCC、bpftrace

https://plantegg.github.io/2019/09/16/SystemStap/ Linux 4.4+ 支持 eBPF。基于 eBPF 可以将任何内核函数调用转换成可带任何 数据的用户空间事件。bcc 作为一个更上层的工具使这个过程更加方便。内核探测 代码用 C 写,数据处理代码用

[转帖]Linux性能优化(四)——BCC性能监控工具

一、BCC简介 1、BCC简介 BCC是一个Python库,简化了eBPF应用的开发过程,并收集了大量性能分析相关的eBPF应用。BCC为BPF开发提供了不同的前端支持,包括Python和Lua,实现了map创建、代码编译、解析、注入等操作,使开发人员只需聚焦于用C语言开发要注入的内核代码。 BCC

[转帖]Linux性能优化(四)——BCC性能监控工具

原文 https://blog.51cto.com/9291927/2593705 1、BCC简介 一、BCC简介 BCC是一个Python库,简化了eBPF应用的开发过程,并收集了大量性能分析相关的eBPF应用。BCC为BPF开发提供了不一样的前端支持,包括Python和Lua,实现了map建立、