Skip to content

Latest commit

 

History

History
303 lines (230 loc) · 13.9 KB

File metadata and controls

303 lines (230 loc) · 13.9 KB

hung_detect 🔍 — macOS「未响应」检测器

🇺🇸 English | 🇨🇳 简体中文

Platform Homebrew License

使用与活动监视器相同的 Window Server API 检测「未响应」进程。一条命令,即时结果。

hung_detect 检测到 hung 进程

安装

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 服务器

🤖 MCP 服务器(AI 集成)

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 已知进程。可选参数:listshow_shaforeground_onlytype
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 --mcp

📊 输出示例

JSON 输出
{
  "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"
  }]
}
监控架构

Monitor mode architecture

⚙️ CLI 参数

检测:

  • --list, -l:列出所有匹配进程(默认仅显示未响应进程)。
  • --sha:在表格输出中显示 SHA-256 列。
  • --type <TYPE>:进程类型 — foregrounduielementguibackgroundlsapp(默认: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:参数错误或运行时错误。

🔒 私有 API 说明

本工具使用 macOS 私有 API:

  • CGS 函数CGSMainConnectionIDCGSEventIsAppUnresponsiveCGSRegisterNotifyProc)— 通过 CGSInternal 头文件直接调用,编译时链接。
  • LS 函数_LSASNCreateWithPid_LSASNExtractHighAndLowParts_LSCopyApplicationInformation)— 运行时通过 dlsym 解析,支持下划线变体回退。无公开头文件。

如果必须符号无法解析,程序以退出码 2 结束。

🔎 hung_detect vs Activity Monitor

维度 活动监视器 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,只有真正有证书的才做昂贵的 SecCodeCopySigningInformationkSecCSSigningInformation)调用。
  • 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 进程后可自动并行采集 samplespindump 数据。

三级诊断

级别 参数 工具 需要 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