SRodi/hotspot-bpf

GitHub: SRodi/hotspot-bpf

一款基于 eBPF 的 Linux 进程实时诊断工具,通过关联 CPU、调度、内存与页面错误自动标记根因。

Stars: 13 | Forks: 1

# hotspot-bpf [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/SRodi/hotspot-bpf/badge)](https://scorecard.dev/viewer/?uri=github.com/SRodi/hotspot-bpf) **eBPF 性能透镜** — Linux 进程的实时根因诊断。 hotspot-bpf 在单一终端视图中关联 CPU 时间、调度器争用、页面错误压力和 RSS 增长。它不是简单地展示原始数字并让你自行解读,而是告诉你 **为什么** 一个进程变慢、被抢占或即将 OOM。 ![hotspot-bpf detecting an OOM-risk memory leak in real time](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/969d8f9042163724.gif) ## 检测内容 hotspot 会自动将每个可见进程分类为六种诊断之一 —— 基于一个采样窗口的启发式标签: | 诊断 | 含义 | |------|------| | **OOM risk** | RSS 持续增长 + 高页面错误率 | | **CPU-bound** | 饱和 CPU 核心且无内存压力 | | **Mem-thrashing** | 昂贵的页面错误或极高错误率但 CPU 较低 | | **Starved** | 经常被抢占,几乎得不到 CPU | | **Noisy neighbor** | 抢占其他进程并消耗大量 CPU | | **OK** | 未检测到异常 | 所有阈值均可通过 [YAML 配置](#custom-thresholds) 调整。 ## 快速开始 ### 从预构建版本安装 从 [Releases](https://github.com/srodi/hotspot-bpf/releases) 页面下载最新二进制文件: ``` curl -LO https://github.com/srodi/hotspot-bpf/releases/latest/download/hotspot-bpf-linux-amd64.tar.gz tar xzf hotspot-bpf-linux-amd64.tar.gz sudo ./hotspot-bpf-linux-amd64 -interval 5s -topk 5 ``` #### 验证发布签名 发布版本使用 [cosign](https://github.com/sigstore/cosign) 进行签名,采用免密钥(Sigstore)身份验证。验证方式如下: ``` cosign verify-blob \ --signature hotspot-bpf-linux-amd64.tar.gz.sig \ --certificate hotspot-bpf-linux-amd64.tar.gz.cert \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ --certificate-identity-regexp https://github.com/SRodi/hotspot-bpf/ \ hotspot-bpf-linux-amd64.tar.gz ``` 或使用 Sigstore 捆绑包: ``` cosign verify-blob \ --bundle hotspot-bpf-linux-amd64.tar.gz.bundle \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ --certificate-identity-regexp https://github.com/SRodi/hotspot-bpf/ \ hotspot-bpf-linux-amd64.tar.gz ``` ### 运行时要求 | 要求 | 说明 | |------|------| | **Linux 内核 ≥ 5.5** 且启用 BTF | `ls /sys/kernel/btf/vmlinux` 必须成功。建议 ≥ 5.8 以获得最佳 kprobe 兼容性。 | | **root** 或 `CAP_BPF` + `CAP_PERFMON` | 加载 eBPF 程序需要提升权限 | | x86_64 | ARM64 支持尚未可用 | ### 构建要求 | 工具 | 用途 | |------|------| | [Go 1.24+](https://go.dev/doc/install) | 构建 CLI | | [Clang / LLVM 15+](https://llvm.org/docs/GettingStarted.html) | 编译 eBPF C 到 BPF 字节码 | | [bpf2go](https://github.com/cilium/ebpf/tree/main/cmd/bpf2go) | 为 eBPF 对象生成 Go 绑定 | | [bpftool](https://bpftool.dev/) | (可选)从内核 BTF 重新生成 `vmlinux.h` | ### 构建与运行 ``` git clone https://github.com/srodi/hotspot-bpf.git cd hotspot-bpf # 安装构建依赖项(Debian/Ubuntu) sudo apt install clang llvm gcc go install github.com/cilium/ebpf/cmd/bpf2go@latest export PATH="$HOME/go/bin:$PATH" # 生成 BPF 绑定(vmlinux.h 已提交) go generate ./... # 运行 sudo go run ./cmd/hotspot -interval 5s -topk 5 ``` ## 相比 top/htop/perf 的优势 | 功能 | hotspot-bpf | top / htop | perf | |------|-------------|------------|------| | CPU、争用与故障在单一视图中 | ✅ | ❌ 需分别使用工具 | ❌ 需多个子命令 | | 受害者 ↔ 侵略者映射 | ✅ 显示谁抢占谁 | ❌ 仅显示总上下文切换 | 部分(perf sched) | | 每核饱和度检测 | ✅ 标记单核瓶颈 | ❌ 仅系统级占比 | ❌ | | 自动根因标签 | ✅ | ❌ 需手动解读 | ❌ | | RSS 增长趋势追踪 | ✅ 可检测随时间增长的内存泄漏 | ❌ 仅瞬时快照 | ❌ | | cgroup 感知过滤 | ✅ | 有限支持 | ✅ | | 零侵入 | ✅ 使用 eBPF 跟踪点 + kprobes | ✅ /proc 采样 | ✅ PMU / 跟踪点 | hotspot 不是 `perf` 的替代品 —— 它是一个 **快速分诊工具**,能在数秒内回答“当前哪里出问题了”,从而指导你进一步深入分析。 ## 工作原理 ``` Kernel BPF Programs BPF Maps Go Userspace ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────┐ │ tp_btf/ │──▶│ cpu_hotspot.c │──▶│ pid_stats │──▶│ cpu.Collector │ │ sched_switch │ │ (CPU time + │ │ cpu_contention │ │ │ │ (BTF raw TP) │ │ contention + │ │ (per-TGID) │ │ report.BuildProcMetrics│ │ │ │ core tracking) │ │ │ │ ├─ merge CPU + memory │ ├──────────────────┤ ├──────────────────┤ ├──────────────────┤ │ ├─ classify process │ │ handle_mm_fault │──▶│ memory_faults.c │──▶│ page_faults │──▶│ └─ track RSS trend │ │ (kprobe) │ │ (faults + RSS) │ │ (per-TGID) │ │ │ │ │ │ │ │ │ │ TUI (flicker-free) │ └──────────────────┘ └──────────────────┘ └──────────────────┘ └──────────────────────┘ ``` 所有 BPF Map 均以 **TGID**(进程 ID)为键,确保即使在多线程应用中 CPU 与内存数据也能正确合并。Map 在每个采样窗口后重置 —— 指标仅反映当前间隔,而非累计总量。 | 组件 | 文件 | 角色 | |------|------|------| | CPU 采集器 | `bpf/cpu_hotspot.c` | `tp_btf/sched_switch` → 纳秒级 CPU 时间、受害者/侵略者争用、CPU 核心 ID | | 内存采集器 | `bpf/memory_faults.c` | `handle_mm_fault` kprobe → 页面错误计数、内核态 RSS | | 采集器(Go) | `pkg/collector/` | 由 bpf2go 生成的 CO-RE 包装器;读取并重置 BPF Map | | 报告引擎 | `pkg/report/` | 合并 CPU 与内存统计,分类进程并跟踪 RSS 趋势 | | TUI | `cmd/hotspot/` | 无闪烁终端 UI,带颜色化诊断标签 | | 配置 | `pkg/config/` | 基于 YAML 的阈值配置,含注释默认值 | 📖 **[架构深度解析](docs/architecture.md)** · 📖 **[诊断指南](docs/diagnosis-guide.md)** ## CLI 参数 | 参数 | 默认值 | 描述 | |------|--------|------| | `-interval` | `5s` | 采样窗口持续时间 | | `-topk` | `10` | 每个表格区域显示的行数 | | `-hide-kernel` | `true` | 隐藏内核线程(kworker、ksoftirqd 等) | | `-cgroup-filter` | | 仅显示其 cgroup 包含此子字符串的进程 | | `-config` | | 指向 YAML 阈值配置文件的路径 | | `-generate-config` | | 将默认配置 YAML 打印到标准输出并退出 | ## 自定义阈值 所有诊断阈值均可配置。首先生成默认值作为起点: ``` sudo go run ./cmd/hotspot -generate-config > thresholds.yaml ``` 编辑后按需使用,并在运行时传入: ``` sudo go run ./cmd/hotspot -config thresholds.yaml -interval 5s ``` 未在文件中指定的值将保留编译时的默认值。详见 [`thresholds.yaml`](thresholds.yaml),其中包含每个参数的解释及调优方法。 ## 测试场景 每个诊断均可通过下方脚本复现。在 **多核机器**(8+ 核)上,单线程工作负载产生的系统级 CPU 占比很低(单核忙 ≈ 20 核主机上的 5%)。请使用测试配置以降低阈值。
多核机器的测试配置 另存为 `test-thresholds.yaml` —— **不适用于生产环境**(会产生误报): ``` oom: rss_mb: 200 rss_ratio: 0.02 faults_per_sec: 100 cpu_bound: cpu_percent: 3 noisy_neighbor: min_preempts_others: 20 min_cpu_percent: 2 starved: min_preempted: 20 max_cpu_percent: 5 mem_thrashing: moderate_faults_per_sec: 100 moderate_cost_per_fault: 0.01 severe_faults_per_sec: 500 severe_cost_per_fault: 0.05 high_faults_per_sec: 5000 max_cpu_percent: 10 ``` ``` sudo go run ./cmd/hotspot -config test-thresholds.yaml -interval 5s ```
CPU 密集型 —— 无内存压力的紧密循环 ``` yes > /dev/null & YES_PID=$! sleep 30 kill $YES_PID ``` **预期结果**:`CPU-bound` —— 每核 CPU 占比高,页面错误接近零,抢占极少。
Starved + Noisy neighbor —— 两个进程绑定到同一核心 ``` # Aggressor:在核心 0 上运行正常优先级的繁忙循环 taskset -c 0 bash -c 'while true; do :; done' & AGGRESSOR=$! # Victim:在同一核心上运行低优先级的繁忙循环 taskset -c 0 nice -n 19 bash -c 'while true; do :; done' & VICTIM=$! sleep 30 kill $AGGRESSOR $VICTIM ``` **预期结果**: - 受害者 → `Starved` —— 被抢占 100+ 次,CPU 占比极低 - 侵略者 → `Noisy neighbor` —— 反复抢占受害者
Mem-thrashing —— 通过 madvise 引发的小错误风暴 ``` python3 - << 'THRASH' import mmap, time, random size = 256 * 1024 * 1024 mm = mmap.mmap(-1, size, prot=mmap.PROT_READ | mmap.PROT_WRITE) while True: mm.madvise(mmap.MADV_DONTNEED) for _ in range(5000): offset = random.randint(0, size - 4096) & ~4095 mm[offset] = 65 time.sleep(0.1) THRASH ``` **预期结果**:`Mem-thrashing` —— 错误率极高(每秒 10,000+),CPU 占比低。触发内存错误等级。
OOM risk —— Python 内存泄漏(约 40 MB/s) ``` python3 - << 'EOF' import time x = [] while True: x.append(' ' * 10_000_000) time.sleep(0.25) EOF ``` **预期结果**:`OOM risk – memory growth` —— RSS 持续增长 + 高错误率。约 2– 个采样周期后出现(约 10–15 秒)。
### 快速参考 | 诊断 | 工作负载 | 关键信号 | 出现时机 | |------|----------|----------|----------| | CPU-bound | `yes > /dev/null` | 每核 CPU 占比高,无页面错误 | 1 个周期(5 秒) | | Starved | `nice -n 19` 受害者被抢占 | 高抢占率,低 CPU 占比 | 1 个周期(5 秒) | | Noisy neighbor | 普通优先级进程抢占受害者 | 高抢占次数 | 1 个周期(5 秒) | | Mem-thrashing | Python madvise 循环 | 极高错误率,低 CPU 占比 | 1 个周期(5 秒) | | OOM risk | Python 内存泄漏 | RSS 持续增长 + 高错误率 | 2–3 个周期 | ## 许可证 [MIT](LICENSE)
标签:API集成, BPF工具, CPU绑定, CPU调度, Docker镜像, ebpf, Linux性能诊断, OOM风险, Qt, RSS内存, YAML配置, 低开销, 内存抖动, 内存泄漏检测, 内核跟踪, 单二进制, 可观测性, 噪声邻居, 客户端加密, 容器与云原生, 性能分析, 性能瓶颈, 文档结构分析, 日志审计, 根因分析, 签名验证, 系统诊断, 资源监控, 阈值配置, 零依赖, 页错误, 饥饿进程