Tuguberk/napwatch
GitHub: Tuguberk/napwatch
一款用 Rust 构建的 macOS 终端电源诊断工具,帮助用户实时监控电池功耗、排查暗唤醒问题并直接管理电源设置。
Stars: 30 | Forks: 1
之所以开发这个工具,是因为发现一台 MacBook 的电量在一夜之间耗尽到了 0%,而这并非由于某个失控的应用程序,而是由 Power Nap 驱动的 “dark wakes”(暗唤醒)所导致。
napwatch 可以在一个屏幕中实时解答三个问题:
- 此刻到底是什么在消耗电量(进程、瞬时瓦特)?
- 机器是真的在睡眠,还是每隔几分钟就会悄悄唤醒自己?
- macOS 的哪些电源设置处于开启状态,我能否在不离开终端的情况下更改它们?
仅支持 macOS。使用 Rust 以及 [ratatui](https://github.com/ratatui/ratatui) + [crossterm](https://github.com/crossterm-rs/crossterm) 构建。
## 截图
**实时仪表盘** —— 包含显示瞬时瓦特数的电池仪表、唤醒事件流、按耗电量排名的进程表以及设置面板:
**进程详情** (`Enter`/`i`) —— 完整路径、父进程、launchd 标签、app bundle 信息:
|
**设置帮助** (`?`) —— 每个开关在开启和关闭时的具体作用:
|
## 目录
- [功能](#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, 网络流量审计, 通知系统