前言
各位小伙伴们,非常感谢您对我们eBPF专题系列文章的持续关注和热情支持。在先前的文章《如何使用USDT探针定位MySQL异常访问》中,我们探讨了MySQL中DTrace的应用,该方法需要修改数据库内核代码(嵌入静态钩子),然后利用eBPF进行探测。而通过uprobe(User-space Probes)方式无需修改数据库内核源码即可探测。那么uprobe(User-space Probes)方式和USDT(User Statically Defined Tracing)相比,哪种方式更好呢?
本文我们将与您深入探讨USDT的预埋,并从性能和扩展上进行对比分析。
一. 如何定USDT(DTrace)的探针?
那我们该如何在代码中定义这样的静态探针呢?
对于C++中的DTrace探针的定义也是非常简单的,只需要包含头文件,剩下的一行代码搞定啦!
#include <iostream>
#include <sys/sdt.h>
#include <unistd.h>
// 模拟执行数据库查询的函数
void executeQuery(const std::string &query) {
int status = 0;
// 触发查询开始的探针
DTRACE_PROBE1(myprovider, query_start, query.c_str());
std::cout << "Executing query: " << query << std::endl;
// 模拟查询执行(此处可以放置实际的数据库操作逻辑)
// ...
// 假设查询执行成功,设置status为1
status = 1;
// 触发查询结束的探针
DTRACE_PROBE2(myprovider, query_end, query.c_str(), status);
}
int main() {
std::string query = "SELECT * FROM users;";
executeQuery(query);
return 0;
}
DTRACE_PROBEn 便是用来定义DTrace探针的宏,n是定义有几个参数。
-
myprovider 是探针的提供者(provider)名称。提供者通常是定义一组探针的模块或应用程序。它的命名一般反映了该探针组的来源,比如模块名或应用程序名。
-
query_start 这是探针的名称,用于标识触发的事件。探针名通常描述该探针记录的特定事件。在这里,`query_start` 可能表示某个查询操作的开始。
-
query.c_str() 这是传递给探针的参数。在这个例子中,`query.c_str()` 返回一个 `const char*` 类型的字符串,这可能是某个 SQL 查询的内容。这个参数会被传递给探针并记录下来。
在上面的示例中第一个探针 DTRACE_PROBE1(myprovider, query_start, query.c_str()); 定义了一个参数,query -- SQL语句
第二个探针DTRACE_PROBE2(myprovider, query_end, query.c_str(), status); 定义了两个参数,分别是query,以及status -- SQL执行状态。
二. 如何使用BCC探测DTrace
我们上面写完了带有DTrace探针的demo,接下来我们对该demo使用eBPF进行探测。
#!/usr/bin/python
from bcc import BPF, USDT
# 创建USDT探针
u = USDT(path="./main")
u.enable_probe(probe="query_start", fn_name="trace_query_start")
u.enable_probe(probe="query_end", fn_name="trace_query_end")
# 定义eBPF程序
bpf_text = """
#include
//处理 query_start 探针事件
int trace_query_start(struct pt_regs *ctx) {
char query[256];
// 从探针中读取参数
bpf_usdt_readarg_p(1, ctx, &query, sizeof(query));
bpf_trace_printk("Query start: %s\\n", query);
return 0;
}
// 处理 query_end 探针事件
int trace_query_end(struct pt_regs *ctx) {