🔥 我們發布最好的 和加密貨幣項目
💯 投資前一定要做好自己的研究
☎️ 營銷聯繫人 @lgexpertise
🚀 官方合作伙伴: www.chimpzee.io https://coins.game
Last updated 3 Monate, 3 Wochen her
笑死了,这个弱智 bug 导致的 verifier error 查了一小时?
struct {
\_\_uint(type, BPF\_MAP\_TYPE\_PERCPU\_ARRAY);
\_\_uint(max\_entries, 1);
\_\_type(key, u32);
\- \_\_type(value, sizeof(struct event));
+ \_\_type(value, struct event);
} events SEC(".maps");
(大家都好有耐心,朋友在 “朽木不可雕也” 打了两小时才打过,然后给出的评论是 “本以为很难,结果发现对非魂玩家太友好了,手残党也可以继续打了”。而我:五次打不过,毫无头绪,准备弃游直接开始云?
主线任务: profile bpf prog
就拿这个 ringbuf_output vs perf_event_output vs map_push_elem(queue) 来说: https://t.me/WelcomeToTheDarkParade/345
用 perf stat \-\-all\-kernel \-\- ./a.out
可以轻易发现为什么有巨大的性能差异: queue 的总指令数是最少的,大概 2983M,而 ringbuf_output 的指令是最多的,高达 4924M,有 1.7 倍的指令数差距;perf_event_output 虽然指令数也略多,4014M,但是它 IPC 居然能高达 3.3~4.1。ringbuf_output 出菊,死因:指令过多。
下面 topdown 分析 perf 和 queue 为什么 IPC 差距这么大。通过不断 perf stat \-M
:
perf stat \-\-all\-kernel
发现是 tma_backend_bound 的锅;
perf stat \-M tma_backend_bound_group
发现是 tma_ports_utilization 的锅;
perf stat \-M tma_serializing_operation
确认就是它!
开始采样,看是 queue 的什么操作导致了过多的 tma_serializing_operation:
perf record \-e RESOURCE\_STALLS.SCOREBOARD \-\- ./bpf\_k2u\_benchmark queue && perf report \-v
发现正是 queue_stack_map_push_elem
内核函数导致的,简单查了下汇编发现可能是 _raw_spin_trylock
的 lock cmpxchg %edx,(%rdi)
导致的内存屏障引起的过多 backend bound。
总结方法论:用 BPF_PROG_TEST_RUN
把 bpf 跑百万遍,用 perf 做 topdown 分析(因为 bpf 总是 on cpu? ( bpf_redirect_neigh: ??? ) 所以跳过 off cpu 分析),drill down 到底层事件然后做采样,收集栈回溯,最后在汇编里找触发事件的指令。
妈的,真好玩,可能比黑神话还好玩。这下我终于可以给某开源项目做 bpf profiling 了(
Telegram
Welcome to the Black Parade
为了睡服领导使用 bpf queue 来传递内核数据到用户态,做了一个小 benchmark,结果大吃一惊。 内核 5.19.0-45-generic,测试使用的是 bpf\_prog\_test\_run 自带的 benchmark 来重复空跑一段 bpf 程序,图中四段 tc 程序都循环运行 9999999 次,传输的 skb\_meta 是一个 80 字节的结构体,测试中没有在用户态读这些数据,所以如果塞满就直接扔掉。 结果如下: SchedCLS(perf\_test)#8: 85.696321ms…
含金量好高,由此可以用 perf stat -M 不断 drill down 最后在根源做 bt,应该是 IPC 优化的标准 profiling 吧。
当然前提是 on cpu,否则你会发现 sleep 1 的 IPC 高于 golang web server 但是不能说明前者的性能更好。实际上 python -mhttp.server 的 IPC 也高于 golang web server,这是因为多线程 IPC 叠加和解释执行的指令浪费。所以 IPC 单一指标其实并不能衡量一个进程性能,要小心考虑很多因素。
from Intel® 64 and IA-32 Architectures Optimization Reference Manual Volume 1
冲!
麻了, 不要用 bpftrace 做进程级 perf,因为
bpftrace \-e 'h:cycles:1000 /pid == 233/ {}
不是调用 syscall
perf\_event\_open(*perf\_event\_attr, 233, ...)
而是
perf\_event\_open(*perf\_event\_attr, \-1, ...)
全局 perf 然后在 bpf prog 里过滤 pid: if ((bpf_get_current_pid_tgid() >> 32) == target_pid)
所以我又老老实实滚回去用 c/ebpf 了- -
可能不正确的想法: perf 火焰图的可读性和信息量不如以下简单的 bpftrace↓
profile:hz:99 /pid == $1/
{
@on\_cpu\_stacks[ustack]++
}
熟悉火焰图的都知道要关注 plateau,至于火焰的高度不重要。这句话翻译一下就是关注栈顶,而不关注栈深。plateau 宽说明有更大比例的采样数据,这对应的就是 bpftrace 里的 @[ustack]++ 。
为什么火焰图反而更加不好?因为大火焰吸引注意力却没有意义, plateau 最重要但是又没有排序(即,谁是最宽的 plateau,谁是第二宽,谁是第三);而 bpftrace 自动排序把采样最多的栈最后输出,保证优先被看到。
perf report 的输出就更不好了,如果说火焰图吸引玩家去看大火焰,那 perf report 优先输出栈底 (call graph 的输出格式) 那更是反人类。看!栈!顶!
还有一个问题是 perf 采样自动同时采内核态栈+用户态栈,有时候这有用,但我发现更多时候可能没用?用户态进程的 profiling,当然玩家最关注的是哪些用户态程序语句造成了最多的 cpu 消耗,内核态此时纯干扰。(评论区专家意见: 使用 :u
)
(buf: perf 新手求轻喷)
cpython3.12 perf 支持其实还有一些问题,如果真的拉一个 python:3.12 镜像下来跑就会发现根本复现不了文档的 perf report,原因是默认的 cpython3.12 编译都不带 no-omit-frame-pointer,所以 perf 默认的 fp 回溯褒姒了。
幸好我精通 perf --help,加个 --call-graph dwarf 之后能复现文档结果了,开香槟!
但文档用例耍了小聪明,它使用了一个纯 python cpu 密集的代码,不 IO,不 libc,而在实际生产上就算你的 python3.12 是 --no-omit-frame-pointer 大概率也用不了,因为 cpython 动态链接的 libc 没有 --no-omit-frame-pointer,所以 perf 依然不能从 libc 回溯到 _PyEval_EvalFrameDefault,又褒姒了。
幸好我精通 perf --help,加个 --call-graph dwarf 之后又可以从 libc.so 回溯了,开香槟!
但是文档用例还耍了一个小聪明就是没有 syscall,所以 perf 采样全是采到用户态。真实世界的 python 大概率有 syscall,perf 若是采样到内核态,必须要 --no-omit-frame-pointer + --call-graph fp (有点苛刻,要自己编译 libc),否则只能看到所有的 syscall 都是从 _PyEval_EvalFrameDefault 调用来的 (这目测是因为 perf 对 --call-graph dwarf 的实现不完备,采到内核态时不拷贝用户态栈),毫无信息量,又褒姒了。
~~敬请关注 pycon china 2024 以获得真实世界任意版本 cpython perf 解决方案~~ 不要立这么奇怪的 flag,大概率那时我在***参加不了,事已至此还是先吃饭吧(
(有点心虚,希望此条没有事实性错误,有的话敬请调教)
学会了从内核态取用户态寄存器,总之在 ubuntu 2204 x64 上(!CONFIG_KASAN)用 bpftrace 的话说就是:
```
#define PAGE_SIZE (1<<12)
#define KASAN_STACK_ORDER 0
#define THREAD_SIZE_ORDER (2 + KASAN_STACK_ORDER)
#define THREAD_SIZE ((uint64)(PAGE_SIZE << THREAD_SIZE_ORDER))
#define TOP_OF_KERNEL_STACK_PADDING ((uint64)0)
{
$rsp = reg("sp");
$rip = reg("ip");
// kernel mode
if (!(reg("cs") & 3)) {
$task = (struct task\_struct *)curtask;
$\_\_ptr = (uint64)$task\->stack;
$\_\_ptr += THREAD\_SIZE \- TOP\_OF\_KERNEL\_STACK\_PADDING;
$pt\_regs = ((struct pt\_regs *)$\_\_ptr) \- 1;
$rsp = $pt\_regs\->sp;
$rip = $pt\_regs\->ip;
}
}
```
这一下就解锁一大堆技能,比如 perf 按照 pid 采样到内核态是很频繁的,此时直接用 pt_reg 拿到的都是内核态寄存器。用上面的方法从内核栈读取用户态的寄存器就能做用户态栈回溯和更多好玩的事情。
我又在云端幻想了~
https://t.me/WelcomeToTheDarkParade/595
这是因为 bpf_redirect_peer 和 bpf_direct 会导致不同的 skb->dev,从而 __netif_receive_skb_core() 检查 skb->dev->rx_handler 时执行不同的逻辑,如果足够幸运就会导致 skb->users++,进而导致在 ip_rcv_core() 里进入 if (skb_shared()) skb_clone()。
倒霉的是 skb_clone() 不会复制 skb->sk,所以 bpf_sk_assign 就丢失了,进入常规路由路径,死在 SKB_DROP_REASON_NO_SOCKET。
教训是 datapath 测试是 netdev 相关的,试图用容器模拟节点做 e2e 测试是不完备的。这本来是自然而然的结论,但我被 cilium e2e 测试洗脑了,觉得 kind 集群就足够模拟多节点了,幼稚!
这也是因为 cilium 用一对 cilium_host/cilium_net veth pair 作为 default routing target 而不是直接在 wan iface 搞事,所以从没撞到这些古怪的内核墙? 我又充满感恩了
Telegram
Welcome to the Black Parade
我觉得我又踩到内核 bug 了, bpf\_redirect\_peer + bpf\_assign\_sk 会导致 tcp\_rcv() 验证 sk 状态失败,具体根因暂时没时间细看,这破代码简直没法改,我真是太爱 linux 了***🐧*** (又给项目闯祸了抢救中然而修不好***😂***)
🔥 我們發布最好的 和加密貨幣項目
💯 投資前一定要做好自己的研究
☎️ 營銷聯繫人 @lgexpertise
🚀 官方合作伙伴: www.chimpzee.io https://coins.game
Last updated 3 Monate, 3 Wochen her