共计 3448 个字符,预计需要花费 9 分钟才能阅读完成。
作者:邓欢
爱可生 DMP 团队开发成员,次要负责 DMP 相干开发。
本文起源:原创投稿
* 爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。
社区之前有一篇文章《如何应用 bcc 工具观测 MySQL 提早》,介绍了 bcc 是什么以及如何用 bcc 我的项目提供的工具观测 MySQL。
bcc 我的项目
https://github.com/iovisor/bcc
然而如果 bcc 我的项目提供的 MySQL 察看工具不能满足咱们的需要时该怎么办?那就本人写一个 MySQL 察看工具呗。本文将通过一个例子:观测 MySQL MGR 的数据包的解决线程号,来介绍如何编写本人的 MySQL 动静观测工具。
环境筹备
- 筹备一台 Linux 机器,装置好 Python 和内核开发包。(注:内核开发包版本必须和内核版本统一)
- 通过 dbdeployer 装置一组组复制架构的数据库。
bcc 观测原理
bcc 是 eBPF 的前端,它是依赖 ePBF 实现数据收集的。整个过程能够分为三局部:
- 生成 BPF 字节码
- 将 BPF 字节码加载到内核执行
- 通过 perf event 或者异步的形式从内核将数据拷贝到用户空间
bcc 程序应用 Python 编写,它会嵌入一段 c 代码,执行时将 c 代码编译成字节码加载到内核运行。而 Python 代码能够通过 perf event 读取到数据而后展现进去。理解了原理之后,上面咱们就能够开始着手写本人的观测工具了。从 group replication 插件中找到 MySQL MGR 的数据包的处理函数为 apply_data_packet,所以咱们的指标就是观测这个函数是哪个线程执行的。
开始开发
-
咱们将源代码文件命名为 mgr_apply_data_packet.py,因为应用 Python 进行开发,所以在第一行指定 Python 解释器:
#!/usr/bin/python
-
接下来是须要导入的包,咱们只应用了 bcc 包的 BPF 对象,所以只须要导入这一个对象就行。这个对象能够将咱们的观测代码嵌入到观测点中执行,同时收集观测点数据。
from bcc import BPF
-
接下来须要编写咱们的观测代码。
bpf_text=""" #include <uapi/linux/ptrace.h> #include <linux/sched.h> struct data_t { u32 pid; u32 tgid; u64 ts; }; BPF_PERF_OUTPUT(events); int do_apply_data_packet(struct pt_regs *ctx) {struct task_struct *t = (struct task_struct *)bpf_get_current_task(); struct data_t data = {}; // thread id data.pid = t->pid; // thread group id(pid in user space) data.tgid = t->tgid; // bpf_ktime_get_ns returns u64 number of nanoseconds. Starts at system boot time but stops during suspend. data.ts = bpf_ktime_get_ns(); events.perf_submit(ctx, &data, sizeof(data)); return 0; } """
观测代码是一段 c 代码,然而它是通过 BPF 对象执行的,所以这里放在了 bpf_text 对象中。这段代码次要有四局部:
- 头文件 <uapi/linux/ptrace.h> 和 <linux/sched.h>。
- 构造体 data_t 保留咱们每次观测到的后果。
- BPF_PERF_OUTPUT 定义了一个叫 events 的表,观测代码能够将观测数据写入到 events 表中,而 Python 代码能够从这个表中读取到观测数据。
- do_apply_data_packet 函数收集观测数据。咱们通过 bpf_get_current_task 获取了 MySQL 过程对应的构造体 task_struct,而后从获取了其中的 pid 和 tgid。这里须要留神的是 task_struct 中的 pid 其实对应的是线程 id,tgid 对应的是线程组 id(即用户空间中的过程 id)。工夫 ts 是通过 bpf_ktime_get_ns 函数获取的,这个工夫并不是一个时钟工夫,而是系统启动的工夫。最初通过 events 表的 perf_submit 办法将观测的数据提交到表中。
-
接下来须要做的是将观测代码与 MySQL 中须要观测的函数关联起来。
# get mysql function name and trace it. path = "/root/sandboxes/mysql_base/8.0.18/lib/plugin/group_replication.so" regex = "\\w+apply_data_packet\\w+" symbols = BPF.get_user_functions_and_addresses(path, regex) if len(symbols) == 0: print("Can't find function 'apply_data_packet' in %s" % (path)) exit(1) (mysql_func_name, addr) = symbols[0] b = BPF(text=bpf_text) b.attach_uprobe(name=path, sym=mysql_func_name, fn_name="do_apply_data_packet")
因为 MySQL 的组复制是通过组复制插件实现的,所以咱们需要去插件的代码中寻找须要观测的函数 apply_data_packet。寻找是通过 BPF 对象的 get_user_functions_and_addresses 函数实现的,它会从插件代码的符号表中寻找匹配正则表达式 regex 的所有符号。这里咱们只须要从所有的符号中取第一个符号的函数名。接着应用后面的观测代码作为参数创立 BPF 对象,而后通过 attach_uprobe 将 MySQL 程序中的 apply_data_packet 函数与观测函数 do_apply_data_packet 关联起来。值得一提的是 attach_uprobe 函数关联的是用户空间的函数和观测函数。如果须要观测的是 Linux 内核中的函数,须要应用 attach_kprobe。
attach_kprobe
https://github.com/iovisor/bc…
-
最初就是输入观测后果了。
# output trace result. print("Tracing MySQL server mgr apply_data_packet function") print("%-14s %-6s %-6s" % ("SINCE_UP_TIME(s)", "PID", "THREAD")) def print_event(cpu, data, size): event = b["events"].event(data) print("%-14s %-6s %-6s" % (event.ts/1000000000, event.tgid, event.pid)) b["events"].open_perf_buffer(print_event) while 1: try: b.perf_buffer_poll() except KeyboardInterrupt: exit()
首先输入的两行头部信息。接着是输入单次观测数据的回调函数,而 open_perf_buffer 则是将这个回调函数注册到 events 表中。最初就是一直从 perl 缓冲中获取检测数据,而后通过回调函数输入后果。
后果演示
运行工具而后在另一个终端中往 MySQL 写入数据,能够观测到工具输入:
root@ubuntu:/tmp# python mgr_apply_data_packet.py
Tracing MySQL server mgr apply_data_packet function
SINCE_UP_TIME(s) PID THREAD
2165924 26387 27060
2165924 25810 27043
2165924 26962 27080
参考
1. http://www.brendangregg.com/e…
2. https://github.com/iovisor/bc…
3. https://github.com/iovisor/bc…
4. https://github.com/iovisor/bc…