Tuguberk/napwatch

GitHub: Tuguberk/napwatch

一款用 Rust 构建的 macOS 终端电源诊断工具,帮助用户实时监控电池功耗、排查暗唤醒问题并直接管理电源设置。

Stars: 30 | Forks: 1

napwatch — a terminal UI for diagnosing and controlling macOS power/battery behavior

License Platform Built with Rust Homebrew tap Stars Last commit

之所以开发这个工具,是因为发现一台 MacBook 的电量在一夜之间耗尽到了 0%,而这并非由于某个失控的应用程序,而是由 Power Nap 驱动的 “dark wakes”(暗唤醒)所导致。

napwatch 可以在一个屏幕中实时解答三个问题: - 此刻到底是什么在消耗电量(进程、瞬时瓦特)? - 机器是真的在睡眠,还是每隔几分钟就会悄悄唤醒自己? - macOS 的哪些电源设置处于开启状态,我能否在不离开终端的情况下更改它们? 仅支持 macOS。使用 Rust 以及 [ratatui](https://github.com/ratatui/ratatui) + [crossterm](https://github.com/crossterm-rs/crossterm) 构建。 ## 截图 **实时仪表盘** —— 包含显示瞬时瓦特数的电池仪表、唤醒事件流、按耗电量排名的进程表以及设置面板:

napwatch dashboard

**进程详情** (`Enter`/`i`) —— 完整路径、父进程、launchd 标签、app bundle 信息: napwatch process detail popup **设置帮助** (`?`) —— 每个开关在开启和关闭时的具体作用: napwatch settings help popup
## 目录 - [功能](#features) - [安装](#install) - [用法](#usage) - [快捷键](#keybindings) - [调试参数](#debug-flags) - [数据来源](#how-it-gets-its-data) - [项目结构](#project-layout) - [背景](#background) ## 功能 ### 电池 - 百分比、AC/电池状态、充电状态,以及 macOS 自带的剩余时间估算。 - **瞬时功耗**:真实的瓦特数和 %/小时,基于 `ioreg` 的 `InstantAmperage`/`Voltage`/`AppleRawMaxCapacity` 计算得出 —— 无需等待,从第一次轮询开始即为准确数值。(早期版本曾尝试通过滚动窗口内的整体百分比变化来推算速率,这意味着需要等待 3 分钟才能获得可信的数值;当前方法取代了它。) ### 唤醒统计 + 实时睡眠/唤醒流 - 自上次启动以来的累计睡眠 / 暗唤醒 / 用户唤醒计数(`pmset -g stats`)。 - 随时更新的实时睡眠/唤醒事件流,数据源自 `pmset -g log`,按类型(Sleep / DarkWake / Wake)进行颜色区分,并附带清理过的原因字符串。这是最初调查的直接成果:关闭 Power Nap 并观察 `DarkWake` 条目停止出现。 - `pmset -g log` 总是转储*整个*历史记录(没有 since/tail 标志),当日志包含多天的条目时,耗时超过 1 秒,因此它以较慢的频率(约 16 秒)进行轮询,而不是每次 tick 都轮询。在启动时,事件流会用最后 5 个历史事件作为初始数据,而不是重放整个日志。 ### 耗电量最高的进程 - 根据 `top -o power` 的能量影响分数进行排名。 - `top -l 1`(单次采样)读取到的所有进程值都是 0.0,因为功率是一个需要两次采样之间产生差值的速率 —— 本应用运行 `top -l 2` 并只保留第二次的采样结果。 - **导航**:`↑`/`↓` 或 `j`/`k`。选择会追踪进程的 PID 而非其行索引,因此即使表格在每次轮询时重新排序,它也会保持在同一个进程上。 - **详情视图**(`Enter` 或 `i`):完整的可执行文件路径、PID/PPID 及父进程名称、用户、CPU%/内存%、运行时间、如果由 launchd 管理则显示 launchd 标签,并且 —— 如果是 app bundle —— 还会显示显示名称/bundle identifier/版本(直接通过 `plutil` 从 `Info.plist` 读取,不像 `mdls` 那样依赖 Spotlight 索引)。 - **终止**(`K`):在 y/n 确认提示后发送 SIGTERM。 - **调整优先级**(`+`/`=` 降低优先级,`-` 提高优先级):以 ±1 进行调整,限制在有效的 `-20..=19` 范围内。 ### 设置 可切换项:Power Nap(`p`)、网络访问唤醒(`w`)、低电量模式(`l`)、待机(`s`)、TCP Keepalive(`t`)。只读项:磁盘睡眠 / 显示器睡眠(数字,非布尔值)。 按 `?` 可在应用内查看关于每项设置在开启与关闭时具体作用的说明。 ### 其他功能 - 状态消息(切换确认、错误)显示在底部,并在 4 秒后自动清除,而不是一直停留在那里。 - sudo 权限仅在启动时请求一次(在备用屏幕打开之前,因此密码提示是一个标准的终端提示),并且凭证每 60 秒在后台刷新一次 —— 切换、终止和调整优先级在会话中途永远不需要再次提示。如果 sudo 不可用,应用仍会以只读模式运行:禁用切换功能,但终止/调整优先级仍然适用于您自己的进程(只是不能用于其他用户拥有的进程)。 ## 安装 ### Homebrew(推荐) ``` brew install Tuguberk/napwatch/napwatch ``` 从源码构建(Homebrew 会将 `rust` 作为构建依赖项引入),因此首次安装需要一两分钟的时间。 ### 从源码安装 需要 Rust(推荐使用 `rustup`:`curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh`)。 ``` git clone https://github.com/Tuguberk/napwatch.git cd napwatch cargo install --path . ``` 这会将 `napwatch` 二进制文件安装到 `~/.cargo/bin`(请确保该路径包含在您的 `PATH` 中)。 ## 用法 ``` napwatch ``` 首次启动时会要求您输入一次密码,以启用设置切换以及跨用户的终止/调整优先级功能。如果选择否或让其失败,应用依然会运行,只是无法使用这些功能。 ## 快捷键 | 按键 | 操作 | |---|---| | `q` / `Esc` | 退出(或关闭任何打开的弹窗) | | `↑`/`↓`, `j`/`k` | 在进程表中移动选择 | | `Enter` / `i` | 显示所选进程的详情弹窗 | | `K` | 终止所选进程(需要输入 `y` 确认) | | `+` / `=` | 降低所选进程的优先级(nice +1) | | `-` | 提高所选进程的优先级(nice -1) | | `p` | 切换 Power Nap | | `w` | 切换网络访问唤醒(Wake-on-LAN) | | `l` | 切换低电量模式 | | `s` | 切换待机 | | `t` | 切换 TCP Keepalive | | `?` | 切换设置帮助弹窗 | ## 调试参数 这些参数会完全跳过 TUI,并将原始数据打印到 stdout —— 非常适合用于检查数据源的解析是否正确: ``` napwatch --once # one-shot dump of battery/wake/settings/top-processes napwatch --detail # full detail lookup for a single PID napwatch --wake-log # parse pmset -g log and print the last 10 events ``` ## 数据来源 这里没有任何东西与私有 API 交互 —— 全都是标准的 macOS 命令行工具,通过调用 shell 并解析其输出: | 来源 | 用途 | |---|---| | `pmset -g batt` | 电池百分比、充电状态、剩余时间 | | `pmset -g stats` | 累计睡眠/暗唤醒/用户唤醒计数 | | `pmset -g log` | 睡眠/唤醒事件历史(用于实时事件流) | | `pmset -g` / `pmset -a` | 读取和写入电源设置(Power Nap、Wake-on-LAN 等) | | `ioreg -rn AppleSmartBattery` | 获取实时瓦特数值所需的瞬时电流/电压/容量 | | `top -l 2 -o power` | 单个进程的能量影响排名 | | `ps` | 进程详情(父进程、用户、CPU/内存、运行时间、nice 值) | | `plutil -extract` | 从 `Info.plist` 中提取的 app bundle 名称/标识符/版本 | | `launchctl list` | 将 PID 映射到其 launchd 任务标签 | | `renice` / `kill` | 更改进程优先级和终止进程 | ## 项目结构 ``` src/ ├── main.rs entry point, terminal setup/teardown, event loop, keybindings ├── app.rs App state, background polling thread, status-message TTL ├── power.rs all the shell-out + parsing logic for the data sources above ├── actions.rs sudo handling, and the mutating actions (toggle/kill/renice) └── ui.rs ratatui rendering — gauges, tables, popups ``` ## 背景 这最初源于一次偶然的调查,为了弄清为什么一台 MacBook 在合上盖子放了几天后,电量会降至 0%。`pmset -g stats` 找出了罪魁祸首:在大约一周的时间里,**发生了 1,342 次暗唤醒,而真正的用户唤醒只有 6 次** —— 机器不分昼夜地大约每 15 分钟就会自动唤醒一次,用于 Power Nap 驱动的邮件/iCloud/日历同步以及 FileVault 健康检查;尽管每次单独的唤醒只持续几秒钟,但这在许多闲置的日子里累积起来就是巨大的电量消耗。napwatch 应运而生,将那次一次性的 `pmset`/`top`/`ioreg` 调查转变为一个持续监控该现象的工具,让您无需离开终端即可采取相应措施。

基于 MIT 许可证 —— 详见 LICENSE

标签:Rust, 可视化界面, 电池诊断, 电源管理, 终端UI, 网络流量审计, 通知系统