使用与活动监视器相同的 Window Server API 检测「未响应」进程。一条命令,即时结果。
brew tap fjh658/tap
brew install hung-detect或从源码构建:make build(需要 Xcode 命令行工具)
hung_detect # 检测 hung 进程(有则 exit 1)
hung_detect -l # 列出所有进程及状态
hung_detect --pid 913 # 查指定 PID
hung_detect --name Safari # 按名称查
hung_detect -m # 监控模式:持续监听状态变化
hung_detect --sample # 自动对 hung 进程采样
hung_detect -m --sample # 监控 + 自动采样- 与活动监视器同源 — 使用
CGSEventIsAppUnresponsive(SkyLight/LaunchServices 私有 API)。检测所有 LS 注册进程类型(Foreground、UIElement、BackgroundOnly)。 - 监控模式 — push+poll 双层检测,NDJSON 事件流输出。通过
CGSRegisterNotifyProc推送,poll 作为兜底。 - 内置诊断 — 自动对 hung 进程执行
/usr/bin/sample和/usr/sbin/spindump。--full支持系统级 spindump。 - MCP 服务器 — stdio JSON-RPC 服务,一条命令安装到 14 个 AI 客户端(Claude、Codex、Cursor 等)。
- 丰富元数据 — PID、PPID、用户、Bundle ID、架构、代码签名、沙盒状态、防睡眠、运行时长、SHA-256。
- 高性能 —
proc_listpids(比 sysctl 轻 162 倍)、LS 信息按 PID 生命周期缓存、每进程单次 sysctl、CGS 函数编译时链接。 - 通用二进制 —
arm64+x86_64,macOS 12+。
# 检测
hung_detect # 检测 hung 进程(有则 exit 1)
hung_detect -l # 列出所有 LS 注册进程
hung_detect --type foreground -l # 仅 Dock 应用
hung_detect --type gui -l # Dock + 菜单栏应用
hung_detect --json # JSON 输出
hung_detect --pid 1 # 查任意 PID(含非 LS 进程)
hung_detect --name Chrome # 按名称或 Bundle ID 搜索
# 监控模式
hung_detect -m # 持续监听 hung 状态变化
hung_detect -m --json | jq . # NDJSON 事件流
hung_detect -m --name Safari --interval 2 # 每 2 秒监控 Safari
# 诊断(自动 sample/spindump hung 进程)
hung_detect --sample # 检测 + 采样
hung_detect -m --sample # 监控 + 自动采样
sudo hung_detect -m --full # 监控 + 完整自动诊断
sudo hung_detect --full --spindump-duration 5 # 完整诊断(5 秒采集)
# MCP 服务器(AI 集成)
hung_detect --mcp-install # 一键安装到 14 个 AI 客户端
hung_detect --mcp # 启动 MCP 服务器hung_detect 内置 MCP(Model Context Protocol)服务器,AI 助手可以实时检测和监控未响应进程。
# 自动安装到所有检测到的 AI 客户端(Claude、Codex、Claude Code、Cursor、Windsurf 等)
hung_detect --mcp-install
# 或打印配置 JSON 手动设置
hung_detect --mcp-config
# 从所有客户端移除
hung_detect --mcp-uninstall--mcp-install 支持自动检测并配置:Claude Desktop、Codex、Claude Code、Cursor、Windsurf、Cline、Roo Code、Kilo Code、LM Studio、Gemini CLI、BoltAI、Warp、Amazon Q、VS Code。
MCP 服务器通过 stdio(JSON-RPC 2.0)提供 5 个工具:
| 工具 | 说明 |
|---|---|
scan |
扫描所有 LS 已知进程。可选参数:list、show_sha、foreground_only、type |
check_pid |
按 PID 检查指定进程的 hung 状态 |
check_name |
按名称或 bundle ID 查找进程(不区分大小写子串匹配) |
start_monitor |
启动后台监控,状态变化时推送通知 |
stop_monitor |
停止后台监控 |
监控启动后,服务器在状态变化时推送 notifications/message(MCP logging):
became_hung(级别:alert)— 进程变为未响应became_responsive(级别:info)— 之前 hung 的进程恢复响应process_exited(级别:info)— 被监控的进程已退出
- 传输层:stdio(stdin/stdout),不暴露网络端口
- 生命周期:AI 客户端以子进程方式启动
hung_detect --mcp;stdin 关闭后进程自动退出 - 线程模型:后台线程读 stdin,主线程运行 CFRunLoop 处理定时器和 AppKit,stdout 通过锁序列化
- 多实例:安全 —
CGSEventIsAppUnresponsive为只读调用,无资源竞争
# 启动 MCP 服务器(由 AI 客户端调用,通常不直接运行)
hung_detect --mcpJSON 输出
{
"version": "0.5.2",
"summary": { "total": 1, "not_responding": 1, "ok": 0 },
"processes": [{
"pid": 913, "name": "AlDente", "responding": false,
"bundle_id": "com.apphousekitchen.aldente-pro-setapp",
"arch": "arm64", "codesign_authority": "Developer ID Application: ...",
"sandboxed": false, "elapsed_seconds": 29033, "app_type": "Foreground"
}]
}检测:
--list,-l:列出所有匹配进程(默认仅显示未响应进程)。--sha:在表格输出中显示 SHA-256 列。--type <TYPE>:进程类型 —foreground、uielement、gui、background、lsapp(默认:lsapp)。--foreground-only:等同于--type foreground。--pid <PID>:按 PID 过滤(可重复)。--name <NAME>:按应用名或 bundle ID 过滤(可重复)。--json:输出 JSON(始终包含sha256字段)。--no-color:关闭 ANSI 颜色。-v,--version:显示版本。-h,--help:显示帮助。
MCP 服务器:
--mcp:以 MCP 服务器模式运行(JSON-RPC 2.0 over stdio)。--mcp-config:打印 MCP 服务器配置 JSON。--mcp-install:自动安装 MCP 配置到所有检测到的 AI 客户端。--mcp-uninstall:从所有 AI 客户端移除 MCP 配置。
监控:
--monitor,-m:持续监控模式(Ctrl+C 停止)。--interval <SECS>:监控轮询间隔(默认:3,最小:0.5)。
诊断:
--sample:对每个 hung 进程执行sample。--spindump:同时执行每进程 spindump(隐含--sample,需要 root)。--full:同时执行全量 spindump(隐含--spindump,需要 root)。- 适用范围:诊断参数同时适用于单次模式和监控模式(
-m)。 - 严格模式:
--spindump/--full在启动时会预检权限,权限不足直接失败退出。 - sudo 权限修复:使用
sudo运行时,输出目录和文件会回写为真实用户属主,不会留下 root 属主 dump。 --duration <SECS>:兼容旧参数,一次性设置所有诊断时长。--sample-duration <SECS>:sample采集时长(秒,默认:10,最小:1)。--sample-interval-ms <MS>:sample采样间隔(毫秒,默认:1,最小:1)。--spindump-duration <SECS>:每进程spindump采集时长(秒,默认:10,最小:1)。--spindump-interval-ms <MS>:每进程spindump采样间隔(毫秒,默认:10,最小:1)。--spindump-system-duration <SECS>:--full下全量spindump采集时长(秒,默认:10,最小:1)。--spindump-system-interval-ms <MS>:--full下全量spindump采样间隔(毫秒,默认:10,最小:1)。--outdir <DIR>:输出目录(默认:./hung_diag_<timestamp>)。
0:所有扫描/匹配进程都在响应。1:至少有一个进程未响应。2:参数错误或运行时错误。
本工具使用 macOS 私有 API:
- CGS 函数(
CGSMainConnectionID、CGSEventIsAppUnresponsive、CGSRegisterNotifyProc)— 通过 CGSInternal 头文件直接调用,编译时链接。 - LS 函数(
_LSASNCreateWithPid、_LSASNExtractHighAndLowParts、_LSCopyApplicationInformation)— 运行时通过dlsym解析,支持下划线变体回退。无公开头文件。
如果必须符号无法解析,程序以退出码 2 结束。
| 维度 | 活动监视器 | hung_detect | 状态 |
|---|---|---|---|
| hung 判定信号来源 | Window Server 私有信号 | 同样使用 CGS 信号链路(CGSEventIsAppUnresponsive) |
对齐 |
| hung 检测范围 | 仅 knownToLaunchServices |
同——仅检测 LS 注册进程 | 对齐 |
| 监控机制 | push + poll | push + poll,push 不可用时自动退化为 poll-only | 对齐 |
| 启动期 push 窗口处理 | 通过内部刷新快速收敛 | unknown PID push 会触发一次即时 rescan | 对齐 |
| push 回调作用范围 | 前台应用类型 | push 更新仅应用于前台应用类型 | 对齐 |
| 默认扫描范围 | 偏应用视角 | 默认全 LaunchServices 已知进程(可用 --type 过滤) |
扩展 |
| 输出形态 | 仅 GUI | 表格 + JSON + NDJSON 事件流 | 扩展 |
| 诊断采集能力 | 以手动流程为主 | 内置 sample / spindump / --full |
扩展 |
| spindump 权限策略 | 应用内部流程 | --spindump / --full 严格预检,失败即退出 |
有意差异 |
| sudo 产物属主 | 不适用 | 自动回写为真实用户属主 | 扩展 |
| 自动化集成 | 较弱 | 明确退出码与脚本化参数 | 扩展 |
| AI 工具集成 | 无 | 内置 MCP 服务器,支持扫描、监控和推送通知 | 扩展 |
MonitorEngine状态统一收敛到主队列(CFRunLoopRun+ 回调切主线程)管理。- 监控事件循环使用
CFRunLoopRun()(而非dispatchMain()),确保 CGS push 通知和 GCD 定时器事件在主 run loop 中处理。 - push 回调(
CGSRegisterNotifyProc)与定时轮询都写入同一份主线程状态,避免竞态。 - push 事件出现未知/过早 PID 时,会立即安排一次对账重扫,不等下一次轮询。
- 诊断任务在独立并发队列执行;同一 PID 的去重由小粒度锁保护。
- CGS 符号解析采用一次性加载,加载后只读,运行期不依赖可变共享状态。
- SHA-256 和代码签名信息均延迟计算,只对最终输出的行计算。
- 代码签名采用两阶段查询:先快速检查签名标志位识别 adhoc/unsigned,只有真正有证书的才做昂贵的
SecCodeCopySigningInformation(kSecCSSigningInformation)调用。 - SHA-256 和签名结果均按可执行路径缓存(NSCache),同一次运行中共享同一二进制的进程不重复查询(如 326 个进程 → 152 个唯一路径 → 174 次缓存命中)。
--json --list会比默认模式慢,因为需要输出并哈希所有匹配进程。
性能实测(326 进程,152 唯一路径):
| 模式 | 耗时 |
|---|---|
| 默认(仅 hung) | ~0.1s |
--name <APP> |
~0.09s |
--list 有缓存 |
~1.2s |
--list 无缓存 |
~1.4s (+12%) |
诊断功能已内置于 hung_detect。发现 hung 进程后可自动并行采集 sample 和 spindump 数据。
| 级别 | 参数 | 工具 | 需要 root |
|---|---|---|---|
| 1 | --sample |
每进程 sample |
否 |
| 2 | --spindump |
+ 每进程 spindump |
是 |
| 3 | --full |
+ 全量 spindump |
是 |
保存到 hung_diag_<timestamp>/(或 --outdir),以时间戳为文件名前缀:
hung_diag_20260214_142312/
├── 20260214_142312_AlDente_913.sample.txt
├── 20260214_142312_AlDente_913.spindump.txt
└── 20260214_142312_system.spindump.txt
诊断与监控模式联动 — 进程变为 hung 时自动触发诊断:
./hung_detect -m --sample # 自动 sample hung 进程
sudo ./hung_detect -m --full # 完整自动诊断
sudo ./hung_detect -m --full --spindump-duration 5 --spindump-system-duration 5 # 5 秒 spindump 的完整自动诊断
./hung_detect -m --sample --json | jq . # 以 NDJSON 流输出诊断事件- 诊断在状态切换到 hung(
responding -> not responding)时触发,不会每次轮询都触发。 - 监控启动时,已经处于 hung 的进程会立即触发一次诊断。
- 进程持续 hung 不会重复触发;需要先恢复为 responding,再次变 hung 才会再次触发。
- 每进程诊断(
sample/ 按 PID 的spindump)在同一 PID 诊断进行中会去重。 - 使用
--full时,每次 hung 触发还会启动一次全量spindump;即使该 PID 的每进程任务被去重,全量spindump仍可能执行。
示例:
responding -> not responding:--sample:1 次sample--sample --spindump:1 次sample+ 1 次每进程spindump--full:1 次sample+ 1 次每进程spindump+ 1 次全量spindump
responding -> not responding -> responding -> not responding:- 通常会触发两轮诊断
- 若第二次 hung 发生在同一 PID 第一轮未完成前,每进程任务可能因去重被跳过
Apache License 2.0,见 LICENSE。
