# π»π macpow β Real-time power tree TUI for Apple Silicon
[](https://github.com/k06a/macpow/actions/workflows/ci.yml)
[](https://crates.io/crates/macpow)
[](https://github.com/k06a/homebrew-tap)
[](LICENSE)
[](https://github.com/k06a/macpow)
Real-time power consumption monitor for Apple Silicon Macs (M1βM5+).
<p align="center">
<img src="./screenshot.png" width="75%" alt="macpow screenshot">
</p>
**macpow** reads directly from macOS hardware interfaces β IOReport, SMC, IORegistry, CoreAudio, and Mach/kernel APIs β to show per-component power draw, temperatures, frequencies, CPU utilization, and per-process energy attribution. No sudo required.
### Legend
| `0.123 W` | Measured power (direct hardware reading) |
| `β0.123 W` | Estimated power (model-based calculation) |
| `β€0.123 W` | Upper-bound power estimate |
| `βΈ` | Pinned resource (sparkline chart visible) |
| `ββββββββββ` | CPU core utilization bar (filled = busy) |
| `37Β°C` | Fresh temperature reading |
| `~37Β°C` | Stale temperature (sensor read failed, showing last known value) |
| `pendingβ¦` | Data source still initializing |
| `[dead]` | Process has exited (energy total preserved) |
| **Bold white** | Section headers and measured values |
| <span style="color:green">Green</span> | Low power (< 1W) or info-only rows |
| <span style="color:goldenrod">Yellow</span> | Moderate power (1β5W) |
| <span style="color:orange">Orange</span> | High power (5β10W) |
| <span style="color:red">Red</span> | Very high power (> 10W) |
| Gray | Dimmed/inactive items |
## Features
- **SoC breakdown** β CPU (E/P cores with per-core power, utilization bars, temperatures), GPU, ANE, DRAM, GPU SRAM, Media Engine, Camera (ISP), Fabric β all from IOReport Energy Model
- **CPU utilization** β per-core usage % with visual bars from Mach `host_processor_info`
- **Real frequencies** β CPU and GPU MHz from DVFS voltage-states tables, not percentages
- **Temperatures** β per-component and per-core from SMC sensors (CPU, GPU, ANE, DRAM, SSD, Battery); universal bank-based key mapping for all Apple Silicon generations (M1βM5+, including Ultra dual-die); stale value caching with `~` indicator when sensors temporarily read invalid
- **Memory** β used/total GB via `host_statistics64` Mach API
- **Display** β brightness, panel class (SDR/HDR/XDR), refresh rate / ProMotion, live HDR-active indicator from `AppleARMBacklight` IOReport `DPB factor`, plus IOReport SoC display controller and external display power via DISPEXT
- **Keyboard** β backlight brightness and estimated power via IORegistry PWM
- **Battery** β voltage, amperage, charge %, time remaining, temperature, drain/charge rate
- **SSD** β model, interconnect (Apple Fabric/PCIe), power estimation from IORegistry disk counters
- **Peripherals** β Thunderbolt/PCIe (IOReport measured), Ethernet (link speed, per-interface traffic), WiFi (signal/mode/channel, per-interface traffic), Bluetooth devices with battery levels, USB devices (speed/power/I/O counters)
- **Per-process energy** β dynamically-sized top processes by session energy (from `proc_pid_rusage`), with per-process disk I/O rates, network traffic (via nettop), RAM footprint, dead process detection
- **Fans** β RPM and cubic power model per fan
- **Collapsible tree** β fold/unfold with arrows, `+`/`-` for all
- **Sparkline charts** β pin any resource with Space, inline 1-line history column at wide terminals
- **Time-based SMA** β toggle 0s/5s/10s smoothing window
- **Latency control** β toggle UI refresh rate: 500ms / 2s / 5s
- **Mouse support** β click to select rows
- **JSON mode** β pipe structured data for scripts and dashboards
- **No sudo** β runs entirely with user-level permissions
## Install
### With cargo
```bash
cargo install macpow
```
### From source
```bash
git clone https://github.com/k06a/macpow.git
cd macpow
cargo build --release
./target/release/macpow
```
### Homebrew
```bash
brew tap k06a/tap
brew install macpow
```
### With pixi (conda-forge)
```bash
pixi global install macpow
# execute without installing
pixi exec macpow
```
## Usage
```
macpow # TUI mode (default)
macpow --json # JSON output to stdout
macpow --interval 500 # Set sampling interval in ms (default: 250)
macpow --dump # Dump IOReport channel names (diagnostics)
macpow --dump-smc # Dump every SMC key with type and value (diagnostics)
```
### Keybindings
| `q` / `Esc` | Quit |
| `Up` / `Down` / `j` / `k` | Move cursor |
| `Left` / `Right` / `h` | Collapse / expand tree node |
| `+` / `=` | Expand all nodes |
| `-` | Collapse all nodes |
| `Space` | Pin/unpin resource chart |
| `a` | Cycle SMA window: 0s / 5s / 10s |
| `l` | Cycle refresh interval: 250ms / 500ms / 1s / 2s |
| `r` | Reset all totals and min/max |
| `PgUp` / `PgDn` | Scroll by 10 rows |
| `Home` | Jump to top |
| Mouse click | Select row |
All letter keys work on any keyboard layout (QWERTY, Russian, Dvorak, etc).
## Architecture
Each data source runs in its own thread, updating shared metrics at its own pace. The TUI renders at the configured interval without blocking on slow sources.
```
+------------------+---------------------------------------------+
| IOReport | SoC power (Energy Model), |
| | CPU/GPU frequencies (DVFS residency) |
| SMC | System power (PSTR), display backlight |
| | (PBwo on M5/Neo, PDBR on M1-M4), |
| | adapter (PDTR), WiFi (wiPm), temps, fans |
| IORegistry | Battery, display brightness, keyboard PWM, |
| | USB devices, SSD model, disk I/O counters |
| CoreAudio | Volume level, mute state |
| Mach API | Per-CPU utilization ticks, memory stats |
| proc_pid_rusage | Per-process billed energy |
| getifaddrs | Network traffic byte counters |
| CoreWLAN/pmset | WiFi info, Bluetooth devices |
| IOPMAssertions | Power assertions, audio playback detection |
+------------------+---------------------------------------------+
```
### Power measurements vs estimates
| CPU, GPU, ANE, DRAM | IOReport | Direct energy measurement (mJ/uJ/nJ deltas) |
| Media Engine, Camera (ISP) | IOReport | Direct energy measurement (AVE + MSR, ISP) |
| Fabric (AMCC, DCS, FAB, AFR) | IOReport | Direct energy measurement |
| Thunderbolt/PCIe | IOReport | Direct energy measurement (PCIe ports + controllers) |
| Display backlight | SMC PBwo (M5 Pro/Max/Neo, A18) / PDBR (M1-M4 XDR) | Direct power rail measurement |
| Display controller | IOReport DISP/DISPEXT | Direct energy measurement (SoC + external) |
| Power adapter | SMC PDTR | Direct power delivery measurement |
| System total | SMC PSTR | Direct power rail measurement |
| Battery | IORegistry | V * I calculation |
| Per-process | Kernel | `ri_billed_energy` from rusage_info_v4 |
| Per-process disk I/O | Kernel | `ri_diskio_bytesread/written` from rusage_info_v4 |
| Per-process memory | Kernel | `ri_phys_footprint` from rusage_info_v4 |
| Per-process network | nettop | Cumulative bytes per process (~18ms subprocess) |
| CPU utilization | Mach API | `host_processor_info` tick deltas |
| Memory | Mach API | `host_statistics64` (active + inactive + wired + compressor pages) |
| Keyboard | IORegistry PWM | Duty cycle * 0.5W max |
| Fans | SMC RPM | Cubic model: (RPM/RPM_max)^3 * 1W |
| Audio | CoreAudio + IOPMAssertions | Idle 0.05W + volume^2 * 1W |
| WiFi | SMC wiPm | Direct power measurement |
| Bluetooth | pmset | Fixed per device type (0.01-0.05W) |
| SSD | IORegistry counters | I/O utilization: 0.03-2.5W |
| Ethernet | getifaddrs | Link detection, speed, per-interface traffic (data only) |
| Network | getifaddrs | Per-interface byte counters for Ethernet and WiFi |
| USB | IORegistry PowerOutDetails | Per-port power measurement (Watts/PDPowermW) |
### IOReport channel naming (multi-die support)
IOReport channel names vary between single-die and multi-die (Ultra) chips. The parser handles both generically:
```
Single-die (M1/M2/M3/M4 base/Pro/Max):
CPU Stats: ECPU0, PCPU10 β digit suffix
Energy Model: EACC_CPU0, PACC0_CPU5 β _CPU suffix
Blocks: ISP, DRAM, ANE, DISP β bare names
Multi-die (M1/M2/M3 Ultra):
CPU Stats: DIE_0_ECPU_CPU0, DIE_1_PCPU1_CPU3 β DIE_N_ prefix + _CPU suffix
Energy Model: DIE_0_EACC_CPU0, DIE_1_PACC1_CPU3 β DIE_N_ prefix + _CPU suffix
Blocks: ISP0_0, DRAM0_1, ANE0_0 β per-die suffix
```
Two design rules keep this forward-compatible with future chips:
1. **`strip_die_prefix`** generically removes `DIE_{N}_` so downstream parsers always see the same base format
2. **`starts_with`** matching for block power handles any suffix Apple may add (e.g. `ISP` matches `ISP`, `ISP0_0`, `ISP0_1`, etc.)
If a new chip isn't detected correctly, run `macpow --dump` to see the raw IOReport channel names.
## Requirements
- macOS 12+ (Monterey or later)
- Apple Silicon (M1, M2, M3, M4, M5 β any variant)
- Rust 1.70+
## Release checklist
```bash
# 1. Bump version
vim Cargo.toml # update version = "X.Y.Z"
# 2. Build and verify
cargo build --release
cargo fmt --check # formatting clean
# Clippy: same flags as .github/workflows/ci.yml (plain `cargo clippy -D warnings` is stricter and fails)
cargo clippy -- -D warnings -A clippy::field_reassign_with_default -A clippy::manual_c_str_literals -A clippy::manual_clamp -A clippy::manual_range_contains -A clippy::missing_safety_doc -A clippy::needless_range_loop
cargo test # all tests pass
# 3. Update Homebrew badge in README.md
# Change: homebrew-vX.Y.Z in the badge URL
# 4. Commit, tag, push
git add -A
git commit -m "Bump version to X.Y.Z"
git tag vX.Y.Z
git push origin main --tags
# CI will auto-create GitHub Release with binary
# 5. Publish to crates.io
cargo publish --dry-run
cargo publish
# 6. Update Homebrew tap (via PR to trigger bottle building)
cd ../homebrew-tap
git checkout -b update-macpow-X.Y.Z
git add Formula/macpow.rb
git commit -m "Update macpow to X.Y.Z"
git push origin update-macpow-X.Y.Z
# Create PR, wait for CI to build bottles, then add label "pr-pull"
# publish.yml will upload bottles and merge the PR
```
## Contributing
Before submitting a PR, please run:
```bash
cargo fmt --check # code formatting
# Clippy: must match CI (see .github/workflows/ci.yml)
cargo clippy -- -D warnings -A clippy::field_reassign_with_default -A clippy::manual_c_str_literals -A clippy::manual_clamp -A clippy::manual_range_contains -A clippy::missing_safety_doc -A clippy::needless_range_loop
cargo test # unit + integration tests
cargo build --release # final build check
```
All four must pass with zero errors. Clippy needs the flags above so local runs match CI; without them, stable Clippy may report project-wide warnings that CI intentionally allows (FFI / Objective-C style).
### Collecting diagnostics
If per-core temperatures are missing or incorrect on your Mac, please open an issue with:
```bash
macpow --dump > dump.txt # IOReport channel names
macpow --dump-smc > smc.txt # all SMC keys (incl. power rails)
macpow --json > metrics.json # full metrics (Ctrl+C after ~15s)
system_profiler SPHardwareDataType | head -10 # chip model
```
This helps add support for new Apple Silicon variants.
## License
[MIT](LICENSE)