0

0

Python asyncio 事件循环的 eBPF 跟踪

冷炫風刃

冷炫風刃

发布时间:2026-02-25 19:33:16

|

525人浏览过

|

来源于php中文网

原创

ebpf 通过跟踪 epoll_wait 等系统调用定位 asyncio 事件循环卡点,需结合 python 启动时记录的 epoll_fd 进行精准过滤,并关注 timerfd、signalfd 等非网络 fd,推荐使用 libbpf + co-re 实现跨内核版本稳定追踪。

python asyncio 事件循环的 ebpf 跟踪

asyncio 事件循环卡住时怎么用 eBPF 看清它在等谁

eBPF 本身不理解 Python 的 asyncio 概念,它只能跟踪系统调用和内核事件。想看到事件循环“卡在哪”,得聚焦到它实际依赖的底层:主要是 epoll_wait(Linux)、kqueue(macOS)或 select(fallback)。一旦事件循环阻塞在这些系统调用里,eBPF 就能捕获到。

常见错误现象是服务 CPU 占用极低但请求不响应——这往往说明 asyncio.run()loop.run_forever() 正停在 epoll_wait 上,而没人往 fd 写数据、没定时器到期、也没新任务提交。

  • bpftool prog list 确认你的 tracepoint 是否已加载(比如 syscalls/sys_enter_epoll_wait
  • 优先跟踪 syscalls/sys_enter_epoll_waitsyscalls/sys_exit_epoll_wait,对比耗时,确认是否真卡住而非正常等待
  • 别试图 hook asyncio 的 Python 层函数(如 loop._run_once),eBPF 无法安全读取 CPython 解释器栈帧,容易 crash 或返回垃圾值
  • 注意 Python 进程可能 fork 出多个子进程(比如用 multiprocessing 启动 worker),eBPF 默认只跟踪指定 PID,需用 pid == 0 或动态过滤

如何让 eBPF 程序识别出这是 asyncio 的 epoll 实例

Linux 下 asyncio 默认用 epoll_create1(0) 创建实例,但 eBPF 没法直接判断某个 epoll_fd 是不是被 Python 用了。可行办法是结合用户态上下文:在 Python 启动时记录它的 epoll_fd 值,再让 eBPF 只对这个 fd 生效。

使用场景通常是调试线上服务,你没法改业务代码,但可以加一行启动日志:

立即学习Python免费学习笔记(深入)”;

医真AI+开放平台
医真AI+开放平台

医真AI+ 医学AI开放平台

下载
import asyncio
import os
loop = asyncio.get_event_loop()
# 打印 epoll fd(仅 Linux)
if hasattr(loop, '_selector') and hasattr(loop._selector, '_epoll'):
    print(f"EPOLL_FD={loop._selector._epoll._epollfd}", file=open("/tmp/asyncio-epoll-fd", "w"))
  • eBPF 程序里用 bpf_map_lookup_elem(&target_epoll_fds, &fd) 查表,只对已知 fd 做深度追踪
  • 不要硬编码 fd 值——进程重启后 fd 会变,必须每次启动重读 /tmp/asyncio-epoll-fd
  • 如果用 uvloop,它的 epoll fd 存在 loop._selector._epoll._fd,路径略有不同
  • Windows 不适用这套逻辑,asyncio 在 Windows 用 overlapped I/O,eBPF 无法跟踪

跟踪 await 点对应的文件描述符时容易漏掉什么

await 本身不产生系统调用,真正触发的是它背后对象的 __await__ 方法——比如 asyncio.StreamReader.read() 最终调用 recvfrom,而 asyncio.sleep() 则靠 timerfd_settime 驱动。eBPF 跟不到 await 关键字,但能抓到这些底层 fd 操作。

容易踩的坑是只盯着网络 fd,忽略 timerfd 和 signalfd:

  • asyncio.sleep()loop.call_later() 依赖 timerfd,需跟踪 syscalls/sys_enter_timerfd_settime
  • 如果你用 loop.add_signal_handler(),信号处理走 signalfd,也要单独 hook
  • HTTP 客户端(如 aiohttp)可能用 socketpair 做内部唤醒,这类 fd 很短命,eBPF 要开足够大的 perf buffer,否则丢事件
  • 避免用 bpf_trace_printk() 打印大量内容,它会严重拖慢 epoll_wait 返回,反而掩盖真实问题

为什么用 libbpf + CO-RE 比 BCC 更适合 asyncio 场景

BCC 编译期绑定内核头,而 asyncio 服务常跑在容器里,宿主机和容器内核版本可能不一致;libbpf + CO-RE(Compile Once – Run Everywhere)能在运行时适配结构体偏移,更稳。

性能上差异明显:BCC 的 Python 层要频繁调用 bpf_perf_event_read() 拉数据,而 libbpf 可以用 ringbuf 零拷贝传给用户态,对高吞吐 asyncio 服务更友好。

  • CO-RE 要求目标机器有 vmlinux.h(可通过 bpftool btf dump file /sys/kernel/btf/vmlinux format c 生成)
  • libbpf 加载程序时默认不验证 map 大小,务必手动检查 struct { __u64 fd; __u64 ts; } __attribute__((packed)) 这类结构体是否被编译器 padding
  • 别用 BCC 的 trace.py 一类脚本临时跟踪——它们启动慢、hook 粒度粗,容易错过毫秒级的 epoll 唤醒抖动
  • 如果用 pyperfperf record -e 'syscalls:sys_enter_*' 辅助验证,注意 perf event 和 eBPF tracepoint 的采样精度差异

最麻烦的其实是 Python 解释器自身的内存布局干扰:CPython 的 GIL、gc、以及 asyncio 对 PyThreadState 的修改,会让 eBPF 读取用户栈变得不可靠。所以别指望 eBPF 能告诉你 “卡在 await db.fetch() 这一行”,它能告诉你的上限是 “卡在 epoll_wait,当前注册了 3 个 socket fd 和 1 个 timerfd”。剩下的,得靠日志和 fd 对应关系反推。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

866

2023.07.31

python中的format是什么意思
python中的format是什么意思

python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

452

2024.06.27

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

365

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

200

2025.07.04

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

424

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

596

2023.08.10

golang map内存释放
golang map内存释放

本专题整合了golang map内存相关教程,阅读专题下面的文章了解更多相关内容。

77

2025.09.05

golang map相关教程
golang map相关教程

本专题整合了golang map相关教程,阅读专题下面的文章了解更多详细内容。

36

2025.11.16

batoto漫画官网入口与网页版访问指南
batoto漫画官网入口与网页版访问指南

本专题系统整理batoto漫画官方网站最新可用入口,涵盖最新官网地址、网页版登录页面及防走失访问方式说明,帮助用户快速找到batoto漫画官方平台,稳定在线阅读各类漫画内容。

127

2026.02.25

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.4万人学习

Django 教程
Django 教程

共28课时 | 4.5万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号