eBPF监控工具bcc系列七开发脚本

简介:


bcc开发脚本有两种方式,一种是基于python接口,另一种是基于ruby接口,我们看的是基于python接口的。

本篇的前置条件是系统中已经安装好了bcc。

1.   Hello world

输入代码如下:

#!/usr/bin/env python

from bcc import BPF

BPF(text='int kprobe__sys_clone(void *ctx) { bpf_trace_printk("Hello, World!\\n"); return 0; }').trace_print()

执行后只要有进程执行就会输出Hello,World!字符串。

            主要代码其实就是一句

BPF(text='int kprobe__sys_clone(void *ctx) { bpf_trace_printk("Hello, World!\\n"); return 0; }').trace_print()

如果是x64系统上4.17 内核版本,,可能需要将kprobe__sys_clone 替换成kprobe____x64_sys_clone。

            我们看下语法:

            text=’’表示定义了一个BPF内联程序,程序用C实现。

            kprobe__sys_clone是通过kprobes的内核动态跟踪,如果代码中以kprobe__ 开始,后面紧接着的是需要跟踪的内核函数例如sys_clone()

            void *ctx,可以有参数

            bpf_trace_printk()是一个内核的printf函数。不过参数有限最多3个,只能输出字符串,全局共享输出冲突,最好使用BPF_PERF_OUTPUT()

            return 0,最后返回0.

            .trace_print()是bcc的程序,读取trace_pipe中数据并输出。

            这个就是使用python接口实现bcc工具的最简单程序。

            可以将kprobe__sys_open改成其他的系统调用例如:kprobe__sys_sync、kprobe__sys_close等等,你想监控的系统调用。是不是很方便?

2.   trace_fields()

使用trace_fields可以格式化输出,其结果来自bpf_trace_printk()函数输出,示例代码如下:

#!/usr/bin/env python

from bcc import BPF

 

# define BPF program

prog = """

int hello(void *ctx) {

    bpf_trace_printk("Hello, World!\\n");

    return 0;

}

"""

 

# load BPF program

b = BPF(text=prog)

b.attach_kprobe(event=b.get_syscall_fnname("clone"), fn_name="hello")

 

# header

print("%-18s %-16s %-6s %s" % ("TIME(s)", "COMM", "PID", "MESSAGE"))

 

# format output

while 1:

    try:

        (task, pid, cpu, flags, ts, msg) = b.trace_fields()

    except ValueError:

        continue

    print("%-18.9f %-16s %-6d %s" % (ts, task, pid, msg))

            同hello world示例相似,不过此处将C代码prog定义为变量,其中有函数hello(),变量方式在有字符串参数的时候很有用。

            本篇中使用attach_kprobe来创建sys_clone的kprobe,当触发时候运行hello程序。可以调用多次attach_kprobe来附加C程序给多个内核函数。

            最后通过trace_fields来返回来自trace_pipe的一组域。当然trace_print适合调试,真正的工具应该使用BPF_PERF_OUTPUT()。

3.   磁盘处理

跟踪磁盘需要相关内核函数,所以对磁盘处理内核函数要有了解,不然无法定义去跟踪那个函数。源码如下,定义了C函数trace_start、trace_complete,分别附加到内核函数blk_start_request和blk_complete_request。注意的是,blk_start_requst中的函数就是所追踪函数的参数。参数是request结构体指针,用该指针作为hash表的健,可以有效保证唯一性,此外还有进程ID。

 

#!/usr/bin/python

from __future__ import print_function

from bcc import BPF

 

REQ_WRITE = 1           # from include/linux/blk_types.h

 

# load BPF program

b = BPF(text="""

#include <uapi/linux/ptrace.h>

#include <linux/blkdev.h>

 

BPF_HASH(start, struct request *);

 

void trace_start(struct pt_regs *ctx, struct request *req) {

        // stash start timestamp by request ptr

        u64 ts = bpf_ktime_get_ns();

 

        start.update(&req, &ts);

}

 

void trace_completion(struct pt_regs *ctx, struct request *req) {

        u64 *tsp, delta;

 

        tsp = start.lookup(&req);

        if (tsp != 0) {

                delta = bpf_ktime_get_ns() - *tsp;

                bpf_trace_printk("%d %x %d\\n", req->__data_len,

                    req->cmd_flags, delta / 1000);

                start.delete(&req);

        }

}

""")

 

b.attach_kprobe(event="blk_start_request", fn_name="trace_start")

b.attach_kprobe(event="blk_mq_start_request", fn_name="trace_start")

b.attach_kprobe(event="blk_account_io_completion", fn_name="trace_completion")

 

# header

print("%-18s %-2s %-7s %8s" % ("TIME(s)", "T", "BYTES", "LAT(ms)"))

 

# format output

while 1:

        (task, pid, cpu, flags, ts, msg) = b.trace_fields()

        (bytes_s, bflags_s, us_s) = msg.split()

 

        if int(bflags_s, 16) & REQ_WRITE:

                type_s = "W"

        elif bytes_s == "0":    # see blk_fill_rwbs() for logic

                type_s = "M"

        else:

                type_s = "R"

        ms = float(int(us_s, 10)) / 1000

        print("%-18.9f %-2s %-7s %8.2f" % (ts, type_s, bytes_s, ms))

可以执行每个请求的处理时间。

4.   直方图

直方图实现示例如下,结束后会将IO请求的大小画成直方图:

#!/usr/bin/python

from bcc import BPF

from time import sleep

 

# load BPF program

b = BPF(text="""

#include <uapi/linux/ptrace.h>

#include <linux/blkdev.h>

 

BPF_HISTOGRAM(dist);

 

int kprobe__blk_account_io_completion(struct pt_regs *ctx, struct request *req)

{

      dist.increment(bpf_log2l(req->__data_len / 1024));

      return 0;

}

""")

 

# header

print("Tracing... Hit Ctrl-C to end.")

 

# trace until Ctrl-C

try:

      sleep(99999999)

except KeyboardInterrupt:

      print

 

# output

b["dist"].print_log2_hist("kbytes")

            其中,BPF_HISTOGRAM(dist)定义BPF 直方图映射对象,名字叫做dist。

            dist.increment()函数会增加直方图中各个值,值由参数指定。

            bpf_log2l()将值变成log-2模式。

            print_log2_hist(“kbytes”)打印dist直方图,列单位为kbytes。内核到用户层只传输直方图变量数量,保证高效。

5.   TRACEPOINT

tracepoint比较稳定,如果可以都建议来替代kprobes。可以使用perf list来列出可用的tracepoints。将BPF程序附加到tracepoints需要内核版本大于4.7。

TRACEPOINT_PROBE(random,urandom_read)是内核的tracepoint random:urandom_read。其格式位于

/sys/kernel/debug/tracing/events/random/urandom_read/format

     跟踪随机读源码:

#!/usr/bin/python

from __future__ import print_function

from bcc import BPF

 

# load BPF program

b = BPF(text="""

TRACEPOINT_PROBE(random, urandom_read) {

    // args is from /sys/kernel/debug/tracing/events/random/urandom_read/format

    bpf_trace_printk("%d\\n", args->got_bits);

    return 0;

}

""")

 

# header

print("%-18s %-16s %-6s %s" % ("TIME(s)", "COMM", "PID", "GOTBITS"))

 

# format output

while 1:

    try:

        (task, pid, cpu, flags, ts, msg) = b.trace_fields()

    except ValueError:

        continue

    print("%-18.9f %-16s %-6d %s" % (ts, task, pid, msg))

 

6.   跟踪用户层函数

跟踪用户层函数使用uprobe,对应的bpf函数是attach_uprobe。

例如:b.attach_uprobe(name="c", sym="strlen", fn_name="count")

附加到C库,函数为strlen,对应的处理函数为count。

#!/usr/bin/python

from __future__ import print_function

from bcc import BPF

from time import sleep

 

# load BPF program

b = BPF(text="""

#include <uapi/linux/ptrace.h>

 

struct key_t {

    char c[80];

};

BPF_HASH(counts, struct key_t);

 

int count(struct pt_regs *ctx) {

    if (!PT_REGS_PARM1(ctx))

        return 0;

 

    struct key_t key = {};

    u64 zero = 0, *val;

 

    bpf_probe_read(&key.c, sizeof(key.c), (void *)PT_REGS_PARM1(ctx));

    val = counts.lookup_or_init(&key, &zero);

    (*val)++;

    return 0;

};

""")

b.attach_uprobe(name="c", sym="strlen", fn_name="count")

 

# header

print("Tracing strlen()... Hit Ctrl-C to end.")

 

# sleep until Ctrl-C

try:

    sleep(99999999)

except KeyboardInterrupt:

    pass

 

# print output

print("%10s %s" % ("COUNT", "STRING"))

counts = b.get_table("counts")

for k, v in sorted(counts.items(), key=lambda counts: counts[1].value):

    print("%10d \"%s\"" % (v.value, k.c.encode('string-escape')))

7.   使用USDT

USDT在python中有支持。

USDT(pid=int(pid))初始化指定进程的USDT.

u.enable_probe(probe="http__server__request", fn_name="do_trace")

            绑定BPF的C函数到http__server__request的USDT probe。

            BPF(text=bpf_text, usdt_contexts=[u])

            传递USDT对象到BPF中。

8.   相关bpf接口函数

bpf_ktime_get_ns()返回纳秒时间。

BPF_HASH(last)创建BPF映射对象,叫做last。如果没有指定任何参数,所以健值都是无符号64位。

bpf_trace_print输出字符串,类似printf ,在调试中使用个,工具中使用BPF_PERF_OUTPUT().

bpf_get_current_pid_tgid()函数获得pid进程,其中低32位是进程ID,高32位是组id。

            BPF_PERF_OUTPUT(events)命名输出频道名字为events.

            bpf_get_current_common()函数用当前进程名字填充第一个参数地址。

            events.perf_submit()通过ring buffer将事件提交到用户层。

9.   参考:

https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md

bcc Python Developer Tutorial

 

 

目录
相关文章
|
2月前
|
安全 Linux 测试技术
|
5月前
|
运维 前端开发 关系型数据库
高效调试与分析:利用ftrace进行Linux内核追踪(上)
高效调试与分析:利用ftrace进行Linux内核追踪
|
5月前
|
存储 网络协议 Linux
高效调试与分析:利用ftrace进行Linux内核追踪(下)
高效调试与分析:利用ftrace进行Linux内核追踪
|
2月前
|
存储 安全 编译器
eBPF是如何工作的
【2月更文挑战第1天】
|
5月前
|
监控 Kubernetes 网络协议
DoorDash 基于 eBPF 的监控实践
DoorDash 基于 eBPF 的监控实践
65 0
|
缓存 监控 Ubuntu
Linux Command BCC 性能监视、网络动态跟踪工具
Linux Command BCC 性能监视、网络动态跟踪工具
|
监控 Linux
服务监控系统WGCLOUD - 一键部署agent脚本说明(Linux版)
这个目前是只适用于Linux版本的agent一键部署,会默认自动下载指定的agent安装包,并自动部署启动
|
Linux Anolis 芯片
如何使用 eunomia 让eBPF 的部署更简单? | 第 49 期
介绍 eunomia 项目的开发和使用,了解它编译和启动运行 eBPF 程序完全分离的思路。
如何使用 eunomia 让eBPF 的部署更简单? | 第 49 期
|
Prometheus 前端开发 Cloud Native
eunomia-bpf 用户手册: 让 eBPF 程序的开发和部署尽可能简单
让 eBPF 程序的分发和使用像网页和 Web 服务一样自然(Make eBPF as a service): 支持在集群环境中直接通过一次请求进行分发和热更新,仅需数十 kB 的 payload, <100ms 的更新时间,和少量的 CPU 内存占用即可完成 eBPF 程序的分发、部署和更新; 不需要执行额外的编译过程,就能得到 CO-RE 的运行效率;
686 0
eunomia-bpf 用户手册: 让 eBPF 程序的开发和部署尽可能简单