目录

ebpf-sysdiag

ebpf-sysdiag 是一个基于 libbpf CO-RE 的 Linux 系统异常观测与根因定位工具。它在短时间诊断窗口内按需加载 eBPF 程序,采集 CPU、I/O、内存、锁竞争和系统调用等异常证据,并输出 JSON、YAML 或 Markdown 报告。

项目定位是“按需运行的诊断工具”,不是常驻 APM agent,也不是 metrics exporter。它更适合在故障窗口、压测窗口或赛题复现窗口中保留可复核证据,帮助开发者和系统工程师判断问题方向。

本 README 面向第一次接触项目的开发者。赛题评审可先阅读 赛题报告评分映射,更深入的专题文档见 docs/README.md

核心功能

场景 能力 主要实现入口
CPU 识别 CPU 密集计算、busy loop、线程竞争和 runqueue 调度延迟 bpf/cpu_sched.bpf.csrc/analysis/detectors/cpu_detector.c
I/O 识别块设备延迟、热点文件 I/O 和 page-cache churn bpf/block_io.bpf.csrc/analysis/detectors/io_detector.c
内存 识别 OOM 风险、高内存占用、匿名页增长、回收压力、cache competition 等 bpf/memory.bpf.csrc/analysis/detectors/memory_detector.c
锁竞争 识别热点锁集中争用、临界区过大和锁粒度过粗 bpf/lock_futex.bpf.csrc/analysis/detectors/lock_detector.c
系统调用 识别高频 syscall、慢 syscall 和错误重试风暴 bpf/syscall.bpf.csrc/analysis/detectors/syscall_detector.c

诊断结果包含:

  • type:诊断类型,例如 cpu_intensive_computeblock_io_latencymemory_oom_risk
  • target:关联对象,例如 PID/comm、设备、文件、futex 地址或 syscall id
  • window:诊断窗口开始和结束时间
  • confidenceconfirmedhighly_suspectedpossibleneed_investigation
  • summaryroot_causeevidencesuggestions:人读摘要、疑似根因、证据链和后续建议

输出文本中仅 root_cause 使用中文;summarysuggestions 和 evidence 文本使用英文,Linux 专业术语和 target 中的原始标识保持原样。输出规则的完整说明见 docs/output_contract.md

环境与依赖

内核要求

默认构建使用 CO-RE,目标机器需要:

  • Linux 内核启用 BPF、tracepoint、BPF ringbuf 等基础能力
  • /sys/kernel/btf/vmlinux 可读
  • 目标场景需要的 tracepoint 或 raw tracepoint 存在

建议先运行:

tools/check_btf.sh
tools/detect_kernel_features.sh
make check-env

tools/check_btf.sh 会执行 bpftool feature probe kernel。如果普通用户权限不足,脚本会提示以 root 运行;这时可以使用 sudo tools/check_btf.sh 复核 BPF feature probe。

如果没有 BTF,可以尝试 NO_CORE=1 降级构建:

make clean
make NO_CORE=1

NO_CORE=1 依赖当前内核 tracepoint format 布局,只适合作为有限功能验证路径。正式使用仍建议优先使用 CO-RE。

用户态依赖

构建和运行主程序需要:

依赖 用途
clang / llvm 编译 BPF object
makegcc 或兼容 C 编译器 构建用户态程序
pkg-config 查找 libbpf 编译和链接参数
bpftool 生成 libbpf skeleton、探测 BPF 能力
libbpf 开发包 用户态加载 eBPF 程序
libelfzlib 开发包 libbpf 相关链接依赖
python3 e2e 断言和评测报告脚本

openKylin、Ubuntu、Debian 类系统可以使用项目脚本安装基础构建依赖:

scripts/install_deps_openkylin.sh

该脚本当前安装:

clang llvm make gcc pkg-config bpftool libbpf-dev libelf-dev zlib1g-dev linux-tools-common

测试和评测脚本还可能使用 fiostress-ngpidstat 等系统工具。它们不在 scripts/install_deps_openkylin.sh 的安装列表中,需要按目标发行版单独安装。

权限要求

加载 BPF 程序通常需要 root,或具备内核和发行版允许的 CAP_BPFCAP_PERFMONCAP_SYS_ADMIN 等能力组合。最直接的运行方式是:

sudo build/ebpf-sysdiag --scenario all --duration 60 --output json

项目目录结构

ebpf-sysdiag/
├── bpf/                         # eBPF 程序和 BPF 侧公共头文件
│   ├── cpu_sched.bpf.c           # CPU 调度和 runtime 采集
│   ├── block_io.bpf.c            # block I/O 和文件 I/O 采集
│   ├── memory.bpf.c              # 缺页、分配、回收和 OOM 采集
│   ├── lock_futex.bpf.c          # futex wait 采集
│   ├── syscall.bpf.c             # syscall latency 和 rate 采集
│   └── vmlinux/                  # `vmlinux.h` 生成位置
├── include/                      # 用户态公共结构、配置和接口头文件
├── src/
│   ├── main.c                    # CLI 入口和运行主循环
│   ├── config.c                  # 参数和扁平配置文件解析
│   ├── loader.c                  # libbpf skeleton 加载、attach、ringbuf 注册
│   ├── event_parser.c            # ringbuf 原始事件解析
│   ├── analysis/                 # 窗口聚合、证据、置信度和 detector
│   ├── output/                   # JSON、YAML、Markdown 输出
│   └── platform/                 # 架构和内核特性辅助代码
├── config/                       # 默认和单场景配置示例
├── tests/
│   ├── reproduce/                # 压力和异常复现脚本
│   ├── integration/              # 单场景集成测试脚本
│   ├── e2e/                      # 16 场景矩阵、负例基线和驱动回归
│   └── unit/                     # detector 边界条件单元回归
├── scripts/                      # 环境采集、依赖安装、开销测量、评测报告
├── tools/                        # BTF、bpftool、skeleton 和打包辅助脚本
├── docs/                         # 架构、规则、输出契约、性能、盲区等专题文档
├── Makefile                      # 推荐构建入口,包含 BPF skeleton 生成
├── CMakeLists.txt                # 用户态二进制辅助构建入口,需已有 skeleton 头
├── DESIGN.md                     # 设计目标和模块分层概览
└── TEST.md                       # 测试计划和复现指南

编译和安装

推荐构建方式

make check-env
make

构建产物:

build/ebpf-sysdiag
build/*.skel.h
bpf/vmlinux/vmlinux.h

Makefile 会通过 tools/resolve_bpftool.sh 查找可用的 bpftool。如果自动发现失败,可以显式指定:

BPFTOOL=/path/to/bpftool make

清理构建产物:

make clean

注意:make clean 会删除 build/ 和生成的 bpf/vmlinux/vmlinux.h

CMake 入口的用途

仓库包含 CMakeLists.txt,但它只在 skeleton 头文件已经存在时构建用户态二进制。完整 BPF 构建仍应优先使用:

make

可选安装

如果希望用固定路径运行测试二进制,可以手动安装构建产物:

sudo install -m 755 build/ebpf-sysdiag /usr/local/sbin/ebpf-sysdiag-test

安装后应确认测试路径与当前构建完全一致,避免误测旧二进制:

sha256sum build/ebpf-sysdiag /usr/local/sbin/ebpf-sysdiag-test

之后测试脚本可以设置:

BIN=/usr/local/sbin/ebpf-sysdiag-test
SUDO_CMD="sudo -n"

快速运行示例

查看支持的场景

build/ebpf-sysdiag --list-scenarios

当前输出为:

cpu
io
memory
lock
syscall

检查配置解析

build/ebpf-sysdiag --dry-run --config config/default.yaml

当前默认配置的 dry-run 输出形如:

dry-run ok: scenarios=0x1f duration=60 output=0 sample_interval_ms=100

采集全部场景

sudo build/ebpf-sysdiag --scenario all --duration 60 --output json

采集单个场景

sudo build/ebpf-sysdiag --scenario cpu --duration 30 --output markdown
sudo build/ebpf-sysdiag --scenario io --duration 30 --output json
sudo build/ebpf-sysdiag --scenario memory --duration 30 --output json
sudo build/ebpf-sysdiag --scenario lock --duration 30 --output yaml
sudo build/ebpf-sysdiag --scenario syscall --duration 30 --output json

组合场景和过滤

sudo build/ebpf-sysdiag --scenario cpu,syscall,lock --duration 60 --output json
sudo build/ebpf-sysdiag --scenario syscall --pid 1234 --duration 20
sudo build/ebpf-sysdiag --scenario lock --comm nginx --duration 20

--comm 使用 Linux task comm,长度受 TASK_COMM_LEN 限制;项目内按 16 字节处理。

写入报告文件

sudo build/ebpf-sysdiag \
  --scenario all \
  --duration 60 \
  --output json \
  --output-file report.json

CLI 参数

build/ebpf-sysdiag --help 当前输出的参数为:

参数 说明 默认值
--scenario LIST 场景列表:cpu,io,memory,lock,syscall,all,支持逗号分隔 all
--duration SEC 采集时长,单位秒 60
--config PATH 读取扁平 key: value 配置文件
--output FORMAT 输出格式:jsonyamlmarkdown json
--output-file PATH 把报告写入文件;不设置则写到 stdout
--pid PID 只采集指定 PID 不限制
--comm NAME 只采集指定 task comm 不限制
--sample-rate N 每 N 个匹配事件保留 1 个 1
--sample-interval-ms N ringbuf poll 和资源检查间隔 100
--max-rss-mb N 用户态 RSS 保护线 200
--max-cpu-percent N 用户态 CPU 预算目标 5
--dry-run 只解析配置,不加载 BPF false
--list-scenarios 打印支持的场景 false
-h--help 打印帮助并退出 false

命令行参数会覆盖配置文件中的同名设置。

配置文件

配置文件是简单的扁平 key: value 格式,不是完整 YAML 解析器。支持注释和空行。当前解析器会忽略未知配置键,非法场景会回退到 all,未知输出格式会回退到 JSON;修改配置后应先运行 --dry-run。默认配置见 config/default.yaml

常用配置示例:

duration_sec: 60
scenario: all
output: json
sample_interval_ms: 100
sample_rate: 1
max_rss_mb: 200
max_cpu_percent: 5

cpu_runq_delay_threshold_us: 5000
io_block_latency_us_p99: 500
memory_page_fault_rate: 1000
memory_rss_bytes: 402653184
lock_futex_wait_us_p99: 10000
syscall_latency_us_p99: 5000

当前解析器支持的配置键:

配置键 说明
duration_sec 采集时长
scenario 启用场景
output 输出格式
pid PID 过滤
comm task comm 过滤
sample_interval_ms ringbuf poll 和资源检查间隔
sample_rate BPF 侧采样率
max_rss_mb 用户态 RSS 保护线
max_cpu_percent 用户态 CPU 预算目标
cpu_runq_delay_threshold_us CPU runqueue 延迟阈值,单位 us
cpu_runq_delay_us_p99 CPU runqueue 延迟阈值兼容别名
cpu_context_switch_rate CPU 上下文切换速率阈值
cpu_usage_percent CPU runtime 占比阈值
cpu_min_runtime_us CPU runtime 最小阈值
cpu_busy_loop_min_runtime_us busy loop 运行片阈值
cpu_thread_contention_switch_rate 线程竞争上下文切换阈值
cpu_thread_contention_threads 线程竞争活跃线程数阈值
io_block_latency_us_p99 块 I/O 延迟阈值,单位 us
io_min_count I/O 最小事件数阈值
io_queue_depth_threshold 队列深度阈值
io_device_util_percent 设备利用率阈值
io_random_ratio_percent 随机 I/O 比例阈值
io_small_io_ratio_percent 小 I/O 比例阈值
io_small_request_bytes 小 I/O 请求大小阈值
io_severe_latency_us 严重 I/O 延迟阈值
io_hot_file_ratio_percent 热点文件 I/O 占比阈值
io_hot_file_min_ops 热点文件最小操作数
io_cache_churn_min_events page-cache churn 事件数阈值
memory_page_fault_rate 用户态缺页速率阈值
memory_alloc_rate BPF 侧分配过滤阈值
memory_alloc_bytes 窗口累计分配字节阈值
memory_rss_bytes 目标进程 RSS 阈值
memory_reclaim_events reclaim 事件数阈值
memory_available_percent 系统可用内存百分比阈值
lock_futex_wait_us_p99 futex 等待阈值,单位 us
lock_min_wait_count futex 最小等待次数
syscall_latency_us_p99 syscall 延迟阈值,单位 us
syscall_rate syscall 调用速率阈值
syscall_rate_per_sec syscall 调用速率阈值兼容别名

单场景配置示例位于 config/cpu.yamlconfig/io.yamlconfig/memory.yamlconfig/lock.yamlconfig/syscall.yaml

核心架构与执行流程

模块架构

flowchart LR
    CLI["命令行与配置\nsrc/main.c + src/config.c"] --> Loader["BPF 加载器\nsrc/loader.c"]
    Loader --> Collectors["BPF 采集器\nbpf/*.bpf.c"]
    Collectors --> Ringbuf["BPF 环形缓冲区\nevents_rb"]
    Ringbuf --> Parser["事件解析器\nsrc/event_parser.c"]
    Parser --> Engine["窗口分析引擎\nsrc/analysis/engine.c"]
    Engine --> Detectors["诊断插件\nsrc/analysis/detectors/*.c"]
    Detectors --> Output["报告输出\nsrc/output/*.c"]

执行流程

sequenceDiagram
    participant User as 用户
    participant CLI as 命令行与配置
    participant Loader as libbpf 加载器
    participant BPF as BPF 程序
    participant Engine as 分析引擎
    participant Output as 输出层

    User->>CLI: 执行 build/ebpf-sysdiag --scenario ...
    CLI->>CLI: 解析命令行和 config/default.yaml
    CLI->>Loader: 下发场景、过滤条件和阈值
    Loader->>BPF: 打开、加载并挂载所选 skeleton
    Loader->>BPF: 更新 config_map
    BPF-->>Engine: 提交经过过滤、采样和阈值判断的 ringbuf 事件
    Engine->>Engine: 聚合窗口指标
    Engine->>Engine: 执行诊断规则
    Engine->>Output: diag_result[]
    Output-->>User: 输出 JSON / YAML / Markdown

运行时的关键取舍:

  • BPF 侧先做 PID/comm 过滤、采样和阈值判断,减少无效事件进入用户态。
  • 每个场景有独立 BPF object,--scenario 只加载需要的探针。
  • 用户态按窗口聚合,再由 detector 输出可复核的 evidence。
  • 资源守护逻辑会检查诊断进程自身 RSS 和 CPU 预算,避免工具成为新的故障源。

更详细的架构说明见 docs/architecture.mddocs/bpf_maps.mdDESIGN.md

测试方法

测试前建议先完成:

make check-env
make
make unit-test
build/ebpf-sysdiag --dry-run --config config/default.yaml
build/ebpf-sysdiag --list-scenarios

make unit-test 当前运行内存 4 例、锁竞争 6 例和系统调用 7 例,共 17 个 detector 边界用例。

不需要 BPF 权限的测试驱动回归:

bash tests/e2e/test_run_full_matrix_harness.sh
bash tests/e2e/test_negative_harness.sh

单场景复现

场景 压力脚本 观测命令 期望类型族
CPU tests/reproduce/cpu_runqueue_delay.sh sudo build/ebpf-sysdiag --scenario cpu --duration 20 --output json cpu_intensive_computecpu_thread_contentioncpu_busy_loopcpu_sched_latency
I/O tests/reproduce/io_latency_fio.sh sudo build/ebpf-sysdiag --scenario io --duration 20 --output json block_io_latencyfile_io_hotspotpage_cache_churn
内存 tests/reproduce/memory_pressure.sh sudo build/ebpf-sysdiag --scenario memory --duration 20 --output json memory_oom_riskmemory_high_usage_pressurememory_anon_growth
锁竞争 tests/reproduce/lock_contention.sh sudo build/ebpf-sysdiag --scenario lock --duration 20 --output json lock_hot_locklock_large_criticallock_coarse_grained
系统调用 tests/reproduce/syscall_storm.sh sudo build/ebpf-sysdiag --scenario syscall --duration 20 --output json syscall_ratesyscall_slow_readsyscall_error_retry

集成测试

tests/integration/test_cpu_sched.sh
tests/integration/test_block_io.sh
tests/integration/test_memory.sh
tests/integration/test_lock.sh
tests/integration/test_syscall.sh

端到端矩阵和负例基线

完整矩阵覆盖 16 个子场景,断言配置见 tests/e2e/expected_matrix.yaml

sudo -v
LOAD_DUR=30 DIAG_DUR=20 BUILD=0 ASSERT=1 \
  BIN=./build/ebpf-sysdiag tests/e2e/run_full_matrix.sh

脚本必须最终输出 MATRIX_ASSERT_OK cases=16,且每个 case 同时满足 diag_rc=0 load_rc=0。正式稳定性结论要求同一提交在每台目标机连续通过 3 轮完整矩阵,并额外通过负例基线。

提交 7dffba6 的当前规则验证、原始矩阵 JSON 和负例 JSON 见 功能验证证据。证据按提交号归档;历史版本的三轮结果不能代替修改 detector 后的当前版本验证。

负例基线默认使用不存在的 comm 过滤,检查误报控制:

sudo -v
BUILD=0 BIN=./build/ebpf-sysdiag tests/e2e/run_negative_baseline.sh

评测报告和性能开销

下面这些 Makefile 测试目标默认使用 SUDO_CMD=sudo -n。运行它们前需要 root、免密 sudo,或按需设置 TEST_BINSUDO_CMD 等变量。

make smoke-test
make e2e-test
make negative-test
make overhead-test
make eval-report

也可以直接运行脚本:

scripts/run_evaluation_suite.sh
MODE=idle scripts/measure_overhead.sh
MODE=io REPEAT=7 DURATION=65 DIAG_DUR=60 WARMUP=5 scripts/measure_overhead.sh

clean 提交 9d24fa5 的双机 7 轮测量结果:

主机 IOPS 中位变化 P99 中位变化 I/O 态最大 RSS
zh21 -0.12% +1.18% 9376 KB
openKylin -3.88% +4.52% 9512 KB

性能结果只对应该受测提交和 I/O 模式,并受机器、内核、设备、后台负载和 fio 参数影响。逐轮绝对 IOPS/P99、ratio 范围、环境和原始 JSON 见 正式性能证据;方法和解释边界见 docs/performance.mdTEST.md

常见问题

找不到 /sys/kernel/btf/vmlinux

现象可能是:

missing /sys/kernel/btf/vmlinux

处理方式:

  1. 确认当前运行内核启用了 BTF。
  2. 确认 /sys/kernel/btf/vmlinux 可读。
  3. 只做有限功能验证时,可以尝试 make NO_CORE=1

bpftool 存在但不可用

某些系统里的 /usr/sbin/bpftool 可能是包装器,并不一定能匹配当前内核。项目默认会调用 tools/resolve_bpftool.sh 查找可用二进制。

可以手工指定:

BPFTOOL=/path/to/real/bpftool make

运行时提示需要 root 或 capability

加载 BPF 程序通常需要 root 或合适的 capability。优先使用:

sudo build/ebpf-sysdiag --scenario all --duration 60 --output json

results 是空数组

空结果不一定表示失败。它可能表示:

  • 采集窗口内没有指标越过阈值
  • 压力没有覆盖诊断窗口
  • --pid--comm 过滤条件过窄
  • 目标路径没有经过当前探针
  • 阈值过高或采样率设置过稀

可先尝试拉长 --duration,去掉过滤条件,或单独运行目标场景。

结果指向 ebpf-sysdiag 自己

在 syscall 场景下,工具自身的 poll、等待或文件写入也可能被观测到。排查业务进程时建议加 --pid--comm 过滤。

已知限制

  • futex 诊断能定位等待者、等待时间和热点 futex 地址,但默认不能直接定位锁 owner。
  • Block I/O 的设备级 request 不总能映射回文件路径。
  • 当前 target 主要使用宿主机 PID/comm,尚未完整输出 container id 或 cgroup path。
  • 当前未采集内核或用户调用栈,复杂根因仍需结合 perftrace-cmdstrace 等工具复核。
  • ARM64 和 RISC-V 保留构建适配路径,但当前真实验证主要覆盖 x86_64。
  • NO_CORE=1 依赖 tracepoint format,不保证跨内核稳定。

更多边界和交叉验证建议见 docs/blind_spots.mddocs/diagnosis_rules.md

后续开发计划

优先级建议:

  1. 增加 JSON Schema 校验,长期约束输出字段稳定性。
  2. 扩展 CPU 和 I/O detector 的边界单元测试,并继续补充锁和 syscall 的降噪边界。
  3. 在结果中加入 cgroup/container hint,提升容器场景归因能力。
  4. 扩展性能矩阵,覆盖 CPU、内存、锁和 syscall 压力下的开销。
  5. 增加 futex owner 栈或用户态栈聚合,提升锁竞争根因定位精度。
  6. 在 ARM64/RISC-V openKylin 或主流发行版上完成真实构建和冒烟验证。

文档导航

文档 内容
docs/README.md 文档阅读路径和职责划分
DESIGN.md 设计目标和模块分层
TEST.md 测试计划、复现场景和验收标准
docs/architecture.md 运行链路、模块边界和数据生命周期
docs/diagnosis_rules.md 五类异常的规则、证据和误判边界
docs/output_contract.md JSON/YAML/Markdown 输出规则与兼容性约定
docs/bpf_maps.md BPF map 职责、生命周期和容量风险
docs/compatibility.md 内核、权限、架构和发行版兼容性
docs/performance.md 开销控制策略和测量方法
docs/blind_spots.md 已知盲区和交叉验证建议
docs/contest_report.md 面向赛题评审的完整报告
docs/scoring_matrix.md 评分项到实现、测试和证据的映射

License

项目使用 LICENSE 中声明的许可证。BPF 程序声明为 Dual BSD/GPL,以满足内核 helper 和加载要求。

关于
115.1 MB
邀请码
    Gitlink(确实开源)
  • 加入我们
  • 官网邮箱:gitlink@ccf.org.cn
  • QQ群
  • QQ群
  • 公众号
  • 公众号

版权所有:中国计算机学会技术支持:开源发展技术委员会
京ICP备13000930号-9 京公网安备 11010802047560号