如何对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 代码的性能影响,并可能找出需要进行优化的地方。优化可以简单到缓存某些内容,或者根据问题所在提出更好的算法。
相似文章
利用 eBPF 追踪 HRT 全球计算集群中的神秘网络流量
HRT 介绍了他们如何使用 BCC 和 bpftrace 等基于 eBPF 的工具,排查其全球计算集群中的神秘网络流量,并详细阐述了适用于延迟敏感型基础设施的实用内核级追踪技术。
通过记忆化技术将eBPF CPU开销降低约90%(非AI生成)
本文描述了作者如何在他们的安全代理中通过实现记忆化来缓存基于inode的策略,从而将eBPF CPU成本降低约90%,避免了文件访问检查时的重复路径遍历。
我创建了一个构建分析器来理解Bun的编译时间
作者创建了一个名为buildprof的开源构建分析器,以可视化软件在Linux上编译过程中时间花费的位置,其灵感来源于Bun的编译时间改进。
Show HN: 内核中的 DOOM,或 eBPF 中的纤程
本文介绍了 BPF Capsule,这是一个编译器和运行时,它能够通过转换代码以满足验证器约束,使用 eBPF 在 Linux 内核内运行像 DOOM 这样的复杂程序。
内联函数的调试信息
Alan Maguire 在 Linux 存储、文件系统、内存管理和 BPF 峰会上主持了一场会议,提议对 BTF 进行扩展,以存储有关内联函数的信息,从而通过 kprobes 实现对这类函数的内核跟踪。