服务器随机丢包但 ping 延迟正常的 perf record + bpftrace 抓包分析

12次阅读

perf record 不能直接抓到丢包动作,需配合 skb:kfree_skb 等 tracepoint 定位丢包环节;bpftrace 可动态过滤丢包条件;ping 与业务包路径分离,RPS 不均是常见原因;perf 与 bpftrace 联动须时间对齐,避免错失瞬时丢包。

服务器随机丢包但 ping 延迟正常的 perf record + bpftrace 抓包分析

perf record 能抓到丢包时的内核路径吗

不能直接抓到“丢包”这个动作本身,因为丢包往往发生在没有完整 skb 构建、或被 dev_kfree_skb / kfree_skb 立即释放的路径上,perf record 默认采样的是有回溯的用户态或内核函数入口,而很多丢包点(比如 drop_monitor 未启用时的 consume_skbnetif_receive_skb_core 中的 early drop)不触发常规采样点。

真正有用的是配合 perf record -e 'skb:consume_skb' -e 'skb:kfree_skb' -e 'net:net_dev_queue' -e 'net:netif_receive_skb' 这类 tracepoint 事件,它们在 skb 生命周期关键节点打点,能定位到哪个环节释放了本该转发的包。

  • 必须用 root 权限运行,否则 tracepoint 不可见
  • skb:kfree_skblocation 字段能指出丢包位置,比如 net/ipv4/ip_input.c:230 表示 IP 层校验失败丢弃
  • 避免用 -g(调用图),它会大幅降低采样精度,丢包分析重在事件频次和上下文,不在深度

bpftrace 怎么定位随机丢包的触发条件

靠静态打点不够,得用 bpftrace 动态过滤——重点不是“看到丢包”,而是“在丢包前一刻,哪些字段异常”。比如对 skb:kfree_skb 加条件:只打印那些 dst_ip 是目标服务器、且 reason == SKB_DROP_REASON_NOT_SPECIFIEDSKB_DROP_REASON_IP_INHDR 的样本。

bpftrace -e ' tracepoint:skb:kfree_skb /args->reason == 17 || args->reason == 5/ {   printf("DROP @ %s:%d, reason=%d, len=%d, proto=%dn",          str(args->location), args->location_line,          args->reason, args->len, args->protocol); }'

其中 reason == 17SKB_DROP_REASON_IP_INHDR(IP 头错误),reason == 5SKB_DROP_REASON_NOT_SPECIFIED(泛用型丢弃,常出现在驱动层)。注意:args->location 是地址,需用 str() 解析为符号名,否则输出一串数字。

  • 别依赖 pidcomm 过滤,丢包多发生在软中断上下文,comm 常是 ksoftirqd/0
  • count() 聚合后用 printf 打印,避免高频输出冲垮终端
  • 如果 location 全是 0x...,说明内核调试符号没加载,需安装 kernel-debuginfo

为什么 ping 延迟正常但业务包大量丢失

因为 ping(ICMP echo)走的是 icmp_rcvping_lookup 路径,而业务流量(如 TCP SYN)走 tcp_v4_rcvtcp_v4_do_rcv,两者在连接状态检查、early_demux、RPS 队列分发等环节完全独立。一个常见原因是 RPS 配置不均导致某 CPU 队列溢出,而 ICMP 包被 hash 到空闲队列,TCP 包却被持续打到已满队列,触发 netdev_max_backlog 丢弃。

  • /proc/net/snmpudp:Tcp: 段的 InCsumErrorsListenoverflowsEstabResets,比看 Drop: 更准
  • cat /sys/class/net/eth0/queues/rx-*/rps_cpus 看 RPS 是否只启用了部分 CPU,再用 cat /proc/interrupts | grep eth0 看硬中断是否集中
  • perf record -e 'syscalls:sys_enter_sendto' -e 'syscalls:sys_exit_sendto' 可确认用户态是否真发出了包,排除应用层写入阻塞

perf + bpftrace 联动分析的关键陷阱

最常踩的坑是时间窗口不同步:perf record 默认按时间采样,bpftrace 是事件驱动,两个工具各自运行时,看似同时抓包,实则事件可能错开几毫秒——丢包是瞬时行为,错过就无法关联。

  • 必须用 perf script 导出带时间戳的原始事件,再用 bpftrace 输出也加 strftime("%H:%M:%S", nsecs),最后用脚本按毫秒级对齐
  • 别信 timestamp 字段的绝对值,不同 CPU 的 TSC 可能漂移,优先用 nsecs(单调递增纳秒计数)做排序
  • 如果发现 bpftrace 抓到大量 SKB_DROP_REASON_SOCKET_Filter,先检查有没有其他 eBPF 程序(如 Cilium、Falco)在 filter 链上误删包,而不是立刻怀疑内核

丢包点越靠近硬件(如网卡驱动 igb_clean_rx_irq 中的 rx_desc->status & E1000_RXD_STAT_DD 未置位),越难用高层工具捕获;这时候得切到 ethtool -Srx_discards 或驱动私有统计,再决定是否要抓 PCIe TLP 层日志。

text=ZqhQzanResources