witr:用一条命令回答"这个进程为什么在跑"
阅读时间: 大约 14 分钟
witr:用一条命令回答”这个进程为什么在跑”

线上排障时最常见的问题不是”有哪些进程”,而是”这个东西为什么在这”。ps、top、lsof、ss、systemctl、docker ps 各自暴露状态与元数据,把”为什么”留给用户自己跨工具拼接。witr(why-is-this-running,作者 Pranshu Parmar,Go 编写,Apache-2.0 协议,GitHub: pranshuparmar/witr)把这件事做成一条命令:给定进程名、PID、端口、容器或文件,直接输出”谁启动了它”的因果链。本文基于官方 README、官方 Agent 技能说明与平台兼容矩阵做一手分析。
一、背景动机:状态工具回答不了”why”
官方 Purpose 一节把问题讲得很直接:系统上运行的任何东西——进程、服务、占用端口的程序——都有一个起因,而这个起因往往是间接的、不明显的,或分散在 supervisor、容器、服务管理器、shell 等多层之间。现有工具展示 what(是什么),witr 负责解释 where it came from、how it was started、what chain of systems is responsible for it existing right now。
官方把成功标准(Success Criteria)写成四条:用户能在数秒内回答”why is this running”;减少对多个工具拼接的依赖;压力场景下输出仍可理解;事故期间用户敢信任它。作者还写了一篇 Medium 创作故事,并在 Hacker News 有讨论串(id 46392910)。
二、核心概念:一切问题都归约到 PID
witr 的 Core Concept 只有一句话:everything as a process question。端口、服务、容器、命令最终都映射到 PID,一旦定位到 PID,witr 就构建一条因果链回答四个问题:正在跑什么、怎么启动的、什么在维持它运行、它属于哪个上下文。
查询入口全部可重复使用、可混用:
| 参数 | 查询对象 |
|---|---|
| 位置参数(无 flag) | 进程/服务名,默认子串模糊匹配,-x/--exact 精确匹配 |
-p, --pid | 指定 PID |
-o, --port | 端口号 |
-f, --file | 被进程打开/持有的文件 |
-c, --container | 容器名、镜像、命令、compose 项目/服务名或至少 4 字符的 ID 前缀 |
输出模式包括默认叙述式、--short(只给祖先链一行)、--tree(树形,含最多 10 个子进程并高亮目标)、--json、--env(环境变量)、--warnings、--verbose(内存、IO、打开文件、socket 状态)。官方示例中,witr node 的 Why It Exists 一节输出 systemd (pid 1) → PM2 v5.3.1: God (pid 5034) → node (pid 14233),并附 Source(pm2-deploy.service)、Unit File、Working Dir、Git Repo 与 Sockets。
三、TUI:四个标签页的实时面板
不带参数或加 -i 启动交互模式(TUI),官方描述为实时终端仪表盘,含 Processes、Ports、Containers、Locks 四个标签页,自动刷新节奏自适应(起始 3 秒,负载增大后退避)。

- Processes:实时、可排序、可筛选的进程列表,侧栏显示高亮进程的祖先树;
- Ports:监听/全部端口(
a切换),属主进程挂在侧栏;隐藏属主时显示它运行在哪个用户名下(需 sudo 才能看到进程本身); - Containers:跨 Docker、Podman、nerdctl、K8s/crictl、Incus、LXC、LXD、FreeBSD jails 统一列表,含挂载、网络与 compose 元数据;
- Locks:全系统文件锁(Linux 用
/proc/locks,macOS/FreeBSD 由 lsof/fstat 推导),a合并显示所有有意思的打开 fd。
进程动作(Kill/Terminate/Pause/Resume/Renice)仅 Unix 可用,且发送信号前会在确认框里点名确切进程。官方技能说明反复强调:witr 只读,自己从不停止或杀死任何东西,停进程要交给对应的管理器(systemctl、docker stop、pm2 stop 等)。
四、工程契约:退出码与 JSON 为脚本和 Agent 设计
witr 为 CI、监控与 AI 编码 Agent 设计了机器可读接口。退出码(多个目标时取最严重者):
| 退出码 | 含义 |
|---|---|
| 0 | 干净:找到进程,无警告 |
| 1 | 找到进程但有警告(注意:这不是失败) |
| 2 | 未找到(无匹配;root/Windows 下端口无进程持有也返回此码) |
| 3 | 权限不足(建议 sudo 重试) |
| 4 | 输入非法或匹配歧义(无目标的输出模式也走此码) |
| 5 | 内部错误 |
| 6 | 找到进程但无法追溯起因(Windows 常规情况下不使用此码) |
--json 是一份显式契约:字段名、含义、类型稳定,未来版本只增不删;字段顺序、空白与人读文案(Error/Note/Description/警告措辞)不在契约内,要求调用方匹配字段与退出码而不是匹配句子;每个输出形状都有测试钉住全部字段,误改名会让构建失败。官方还专门写了一节”Using witr with AI Coding Agents”,给出可贴进 AGENTS.md/CLAUDE.md 的片段,并提供 Claude Code 插件与通用 Agent Skill。
五、警告项:官方主动标出的风险信号
输出的 Warnings 区是 best-effort 的非阻塞观察,官方列出的类别包括:以 root 运行;非 root 进程带危险 capability(如 CAP_SYS_ADMIN);监听公网接口(0.0.0.0 / ::);重启次数超阈值;RSS 内存超过 1GB;已运行超过 90 天;二进制文件已被删除;库注入迹象(LD_PRELOAD、DYLD_*);原始父进程已退出导致无法追溯。
关于”父进程已退出”,官方没有强行把领养者记为启动者,而是用 ? (original parent exited) 或 ? (parent pid N exited) 明确标断;找不到可信 Source 时报 unknown 而不是猜测——这与”best-effort detection with explicit uncertainty”的输出原则一致。
六、平台支持:纠正”只支持 Linux”的说法
koala-oss 早期条目称 witr”目前只支持 Linux”,但当前 main 分支 README 的平台矩阵已覆盖四系统,只是深度不同:
| 能力 | Linux | macOS | Windows | FreeBSD |
|---|---|---|---|---|
| 按名/PID/端口/文件/容器选择 | ✅ | ✅ | ✅ | ✅ |
| 环境变量 | ✅ | ⚠️ | ⚠️ | ✅ |
| 文件锁 / Locks 标签页 | ✅ | ✅ | ❌ | ✅ |
| tmux/screen 会话识别 | ✅ | ✅ | ❌ | ✅ |
| 调度识别(timer/launchd) | ✅ | ✅ | ❌ | ❌ |
| 重启次数 | ✅ | ⚠️ | ⚠️ | ❌ |
| Snap/Flatpak 识别 | ✅ | ❌ | ❌ | ❌ |
| capability 警告 | ✅ | ❌ | ❌ | ❌ |
| 进程动作(信号/renice) | ✅ | ✅ | ❌ | ✅ |
实现路径上,Linux 走 /proc 全量支持(x86_64、arm64、loong64);macOS 用 ps/lsof/sysctl/pgrep,SIP 下即使 sudo 也看不全系统进程;Windows 直接调原生 Win32 API(ToolHelp32、PSAPI、Service Control Manager),不派生 PowerShell 或 WMI,启动快、没有 Get-CimInstance 卡顿,但查看其他用户/系统服务需管理员终端;FreeBSD 用 procstat/ps/lsof。
七、客观分析:优势与局限
优势:
- 把因果链作为一等输出,区别于只给快照的状态工具,事故现场可直接读;
- 查询入口统一:名/PID/端口/文件/容器可混用、可重复,JSON + 退出码契约适合脚本与 Agent;
- 跨平台广度超出一般排障小工具,Windows 走原生 API 而非 WMI 套壳;
- 只读、不猜测:追不到就标 unknown/ParentExited,不伪造因果;
- 分发成熟:静态单二进制,覆盖 apt(Ubuntu 26.04+、Debian sid)、Homebrew、Conda、AUR、Winget、npm、FreeBSD Ports、Chocolatey、Scoop 等,也可
go install。
局限(官方自述与矩阵可核实):
- 非 root 信息不完整:Linux 下无 root 只能看到端口属于哪个用户,看不到进程本身;macOS 受 SIP 限制;Windows 需管理员;
- Windows 是二等公民:无文件锁、无 tmux/screen、无调度识别、无进程动作,“起因未知”是常态而非异常(退出码 6 不用于 Windows);
- 容器查询依赖运行时 CLI 在 PATH,Docker Desktop/VM 里进程不可见时只能给 Note 级报告;
- TUI 必须有终端:脚本、管道、CI 里无目标调用会以退出码 4 退出并要求给目标;
- 警告阈值(1GB RSS、90 天、重启次数)是内置经验值,官方未给出阈值可调的说明,不同规模机器可能误报或漏报;
- 截至抓取时仓库显示约 22.7k stars、882 forks、643 commits,项目迭代很快,平台矩阵与默认行为可能随版本变化。
八、适用 / 不适用场景
适合: 端口被占(EADDRINUSE)时一键定位属主与 supervisor;排查残留 dev server、僵尸/失控进程;在 systemd/launchd/容器/cron/PM2 多层之间确认服务来源;把排障指令写进 Agent 说明让 AI 用 JSON + 退出码判断。
不适合: 持续监控与告警(witr 是按需查询工具,不是 daemon);Windows 上做文件锁分析或信号管理;需要它自动修复(设计上刻意不做);非授权提权场景(信息缺口只能靠 sudo/管理员补)。