如何对eBPF代码进行性能分析?
摘要
本文演示了如何通过创建一个简单的C测试框架来衡量文件打开延迟,从而对eBPF代码性能进行分析,使开发者能够比较附加eBPF钩子前后的开销。
暂无内容
查看缓存全文
缓存时间: 2026/07/28 18:28
# 如何对 eBPF 代码进行性能分析? 来源:https://naveensrinivasan.com/posts/2026-07-22-how-do-i-profile-ebpf-code/ 如果我们正在运行任何 eBPF 工作负载或编写 eBPF 代码,我们希望衡量其性能影响。在本文中,我们将演示如何做到这一点。 在本示例中,我们的目标是衡量文件打开操作(操作系统中最关键的函数之一)的性能。我们的代码使用了 eBPF 中的文件打开钩子,我们想要衡量添加这个钩子所引入的性能开销。为了识别可能的瓶颈,我们需要一个简单的 C 测试框架,依赖项最少,专门用于衡量文件打开性能。 `` #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include static inline uint64_t now_ns(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); /* VDSO, no syscall */ return (uint64_t)ts.tv_sec * 1000000000ull + ts.tv_nsec; } int main(int argc, char **argv) { const char *path = argv[1]; uint64_t n = strtoull(argv[2], NULL, 10); uint64_t warm = n / 10; /* preallocate + prefault + lock: no page faults in the loop */ uint32_t *d = mmap(NULL, n * sizeof(uint32_t), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_POPULATE, -1, 0); mlock(d, n * sizeof(uint32_t)); for (uint64_t i = 0; i < n; i++) d[i] = 0; /* fault everything in */ for (uint64_t i = 0; i < n; i++) { uint64_t t0 = now_ns(); long fd = syscall(SYS_openat, AT_FDCWD, path, O_RDONLY); /* raw, no libc wrapper */ uint64_t t1 = now_ns(); if (fd >= 0) close(fd); d[i] = (uint32_t)(t1 - t0); } /* dump after the loop only */ for (uint64_t i = warm; i < n; i++) printf("%u\n", d[i]); return 0; } `` 以上代码示例的目标是保持简单,并在缓存热条件下重新打开同一个文件,从而最大程度减少无关的文件系统和磁盘 I/O 变化,有助于识别 p50/p99。代码使用了`syscall(SYS_openat, ...)`而不是 libc 的 `openat()` 包装器,并且前 10% 的结果被丢弃作为预热阶段。这个测试框架会生成打开文件 x 次的结果以及每次打开所花费的时间。因此,我们可以用它来测量 eBPF 钩子挂载前后的性能变化。 ### 准备工作 在对 eBPF 代码进行性能分析时,我们希望 perf 工具能够解析符号,以便我们分析代码中的问题所在。为此,需要运行以下命令。 `` sudo sysctl -w net.core.bpf_jit_enable=1 sudo sysctl -w net.core.bpf_jit_kallsyms=1 `` 上述命令启用 JIT 并暴露 JIT 编译的 BPF 符号,以便 perf report 能够显示程序名称而不是未知地址。要检查符号是否出现在 perf 工具中,运行您的 eBPF 代码并使用以下命令: `` sudo bpftool prog show | rg -A4 ' lsm ' sudo rg 'bpf_prog_[0-9a-f]+_ '/proc/kallsyms | rg 'security|path|file|open' `` 在上述 rg 命令中,我们检查的是 LSM,因为我们测量的是 LSM 钩子。另外,我们使用的是自定义内核版本,因此该内核版本的 perf 不在标准路径中,我们在示例中将其安装在:`PERF=/usr/lib/linux-tools/6.8.0-134-generic/perf` ### 测量过程 现在我们已经设置好所有必要工具,第一步是在没有运行 eBPF 代码的情况下进行测量,这正是上述 C 代码的用途。我们通过打开文件 /etc/hostname 并将结果通过管道输出到文件来测量,以便计算 p50/p99。 `` sudo taskset -c 3 chrt -f 99 ./bench /etc/hostname 100000 > /tmp/samples.txt `` `taskset -c 3` 将执行固定到 CPU 3,减少 CPU 迁移噪声;`chrt -f 99` 给予基准测试极高的 CPU 优先级。它几乎在所有普通程序之前运行,并持续运行直到完成、被阻塞或中断。C 代码会丢弃前 10%,因此文件中应包含 90,000 个样本。 接下来,运行 eBPF 代码并执行类似以下命令: `` sudo $PERF record \ -g \ --call-graph fp \ -e cycles:k \ -F 997 \ -o ~/perf.data \ -- \ taskset -c 3 \ chrt -f 99 \ ./bench /etc/hostname 200000 \ > /tmp/samples.txt `` `-g` 记录调用栈,`--call-graph fp` 使用帧指针展开栈,`-e` 仅在内核模式下采样 CPU 周期(包括系统调用、VFS、LSM 和 eBPF 执行,不包括用户空间基准测试工作)。`-F 997` 请求每秒 997 个样本,非整数的频率有助于避免周期性对齐。 运行上述命令后,执行以下命令对数据进行排序: `` sudo "$PERF" report -i ~/perf.data --stdio --sort comm,dso,symbol > perf.txt `` alt text *同一 perf.data 的火焰图,使用 Inferno 生成。这里关注的调用栈是 bpf_lsm_file_open 及其之上的所有调用。* 以下是 perf.txt 的输出示例,显示时间主要花费在 bpf_lsm_file_open 及其尾调用上,这正是性能瓶颈所在。这恰好位于热路径上,意味着每一次分配 - CPU 周期的削减都将对系统性能产生显著影响。 `` | | | | | | |–90.52%–do_dentry_open | | | | | | | | | | | | | --89.78%–bpf_lsm_file_open | | | | | | | | | | | | | --89.30%–0xffffffffc0288c18 | | | | | | | | | | | | | |–87.40%–bpf_prog_b06f413955402a4b_tail_call_security_check | | | | | | | | | | | | | | | |–77.57%–bpf_prog_934361d723613c1c_enforce_access_policy | | | | | | | | | | | | | | | | | |–57.87%–bpf_prog_a0f18f4b0b140d77_path_check_callback | | | | | | | | | | | | | | | | | | | |–29.94%–bpf_probe_read_kernel | | | | | | | | | | | | | | | | | | | | | |–18.88%–copy_from_kernel_nofault | | | | | | | | | | | | | | | | | | | | | --4.99%–copy_from_kernel_nofault_allowed | | | | | | | | | | | | | | | | | | | |–4.88%–htab_map_hash | | | | | | | | | | | | | | | | | | | --2.29%–copy_from_kernel_nofault `` 本文侧重于性能分析方法而非具体结果,您看到的开销很大程度上取决于您的钩子实际执行的操作,因此我们省略了具体数字,专注于如何自己获得它们。 现在,根据以上结果,我们可以开始分析时间花费在哪里,并结合运行 eBPF 时的 C 代码 p50/p99 对代码进行性能调优。通过这种方式,我们可以清晰地识别 eBPF 代码的性能影响,并可能找出需要进行优化的地方。优化可以简单到缓存某些内容,或者根据问题所在提出更好的算法。
相似文章
Gobee:用Go编写eBPF程序,通过clang转译
Gobee是一个将Go的子集转译为BPF C的工具,允许开发者用Go而非C编写eBPF程序。它为用户空间生成类型化的Go绑定,并利用clang的后端进行编译。
Isolation Forest + eBPF 事件打造 Linux 端点检测系统 [项目]
guardd 是一款开源 Linux 端点检测工具,利用 eBPF 事件与 Isolation Forest 在 60 秒窗口内发现异常进程/网络行为,但对浏览器类误报较敏感。
GCC 16及以后版本中的BPF支持
何塞·马奇西(José Marchesi)和GCC-BPF团队提供了GCC 16中BPF支持的更新,突出了在与LLVM功能对等方面取得的进展,以及内核BPF自测通过率的提升。
字节码虚拟机在意外场景中的应用 (2024)
本文探讨了字节码虚拟机的出人意料的应用,特别是Linux内核中的eBPF以及编译后二进制文件中用于调试信息的DWARF表达式。
直接从BPF发送数据包
BPF程序现在可以使用netpoll基础设施直接从内核空间发送网络数据包,无需用户空间代理,从而提高如Tetragon等安全监控工具的弹性。