Per-app battery consumption for any past time window on macOS. Ask "over the last hour, which apps ate my 13%?" — and get a real answer.
Window : 2026-06-04 13:02 → 14:02 (1.0h, 60 samples)
Battery: 84% → 71% consumed 13%
Avg draw: 7.8W (CPU 3.1W · GPU 1.9W · ANE 0.1W · display/other 2.7W)
Per-app share of that 13% [measured subsystem power]
4.81% ████████░░░░░░░░░░░░░ Google Chrome
2.95% █████░░░░░░░░░░░░░░░░ Xcode
2.10% ████░░░░░░░░░░░░░░░░░ Display/DRAM/other hardware (system)
1.20% ██░░░░░░░░░░░░░░░░░░░ Slack
0.74% █░░░░░░░░░░░░░░░░░░░░ (everything else)
macOS can't tell you this. Activity Monitor shows a live snapshot; System Settings shows device-level history with no per-app split (unlike iOS). battracker records continuously so you can ask about any window after the fact.
- 🔍 Per-app battery % for any window:
--last 1h,--last 45m,--from "09:00" --to "12:30". - 📊 Total % consumed is measured truth — read straight from battery levels; charging is reported separately, never netted against drain.
- ⚡ Three attribution modes, always labeled: measured subsystem power (best), Apple Energy Impact (Intel fallback), CPU-time proxy (no-sudo fallback).
- 🛠 Background logger via
launchd, one sample/minute, zero cost while the lid is closed. - 🕰 Backfill battery history from the system PowerLog DB so totals work for windows before you installed it.
- 🔬
--detailadds per-app CPU/GPU ms/s, disk bytes, network bytes, wakeups. - 🤖 Agent-friendly:
--json(with live, killable PIDs per app) and--paths(raw log locations + format) so an AI can drive a battery-manager bot. - 🔒 100% local — plain text files under
~/.battery-tracker/data/. No network, no telemetry, no third-party deps. - 🧩 Stdlib Python 3 + POSIX shell. Nothing to compile.
- 🚫 Not a live monitor — for real-time menubar stats use stats. battracker answers historical questions.
- 🚫 Not a magic per-app meter for the past — per-app split only exists for windows where the logger was running.
- 🚫 Not exact physics — the per-app split is an honest attribution model, not lab-grade measurement. The report says which mode produced it.
- 🚫 Not a battery-health/charge-limit tool (no SMC writes, ever — that path causes the AlDente PowerLog-bloat bug).
- 🚫 Not cross-platform — macOS only; tuned for Apple Silicon, with an Intel fallback.
- 🚫 Not a cloud service — it never phones home.
macOS gives you no way to answer "in this arbitrary window, how much battery went where, per app?" Every existing tool stops short:
| Tool | What it does | Why it's not enough |
|---|---|---|
| Activity Monitor | Live Energy Impact + 12h average | Snapshot only; no stored per-app timeline |
| System Settings → Battery | 24h/10d battery-level history | Device-level only; no per-app split (iOS has it, macOS doesn't) |
| exelban/stats | Excellent live monitor | "Top processes" is a live snapshot, not stored history |
| lzt1008/powerflow | Logs whole-device power to SQLite | Tells you when drain spiked, not which app |
powermetrics (Apple) |
Per-process energy over an interval | Only prospectively — can't reconstruct the past |
| PowerLog DB | System battery-level history | On macOS holds no reliable per-app energy |
Nothing stores retroactive per-app energy on macOS. Something has to record continuously. So I built it.
✅ Use this if you:
- 💻 Live on a MacBook and want to know what's actually draining it
- 🧑💻 Are comfortable in a terminal and running
sudoonce for the full-power mode - 🔋 Want honest, labeled attribution rather than a made-up single number
- 🗄 Care that your power data stays 100% local
❌ Don't use this if you:
- 🖥 Want a pretty menubar GUI (use stats instead)
- ⏱ Need real-time, sub-second monitoring
- 🪟 Are on Windows or Linux
- 🔬 Expect lab-grade per-app wattage to three decimals
| Layer | Tech |
|---|---|
| Report CLI | Python 3 (stdlib: plistlib, sqlite3, argparse) |
| Logger / installer | POSIX sh |
| Scheduling | launchd (LaunchAgent + optional root LaunchDaemon) |
| Data sources | pmset, ps, powermetrics, system PowerLog SQLite DB |
| Storage | Plain text files in ~/.battery-tracker/data/ |
| Dependencies | None (no pip, no compile) |
| Requirement | Why | How to verify |
|---|---|---|
| macOS | pmset/powermetrics/PowerLog are macOS-only |
sw_vers |
| Python 3 | Runs the report | python3 --version |
| Apple Silicon (recommended) | Mode A measured power needs per-subsystem mW | uname -m → arm64 |
sudo (optional) |
Tier 2 powermetrics + PowerLog import need root | sudo -v |
| Let the logger run ≥5 min | Per-app split needs ≥2 samples in the window | launchctl list | grep battery-tracker |
On Intel Macs, Mode A is unavailable; battracker falls back to Mode B (Apple Energy Impact) automatically.
Big picture
every 60s (launchd, never wakes a sleeping Mac)
│
├─ Tier 1 (no sudo) ─ pmset ──► battery.log (epoch,pct,state)
│ ps ──────► procs.log (per-proc cputime)
│
└─ Tier 2 (root) ─ powermetrics ─► energy.log (per-subsystem mW + per-task)
battracker --last 1h
│
├─ battery.log ──► TOTAL % consumed (measured truth)
└─ energy.log / procs.log ──► per-app SPLIT, scaled to the total
Mode A › B › C (best available, labeled)
In one paragraph. A launchd job samples once a minute: battery level + state from pmset, cumulative per-process CPU time from ps, and — if you opted into the root daemon — a 5-second powermetrics hardware sample. The report reads battery levels to compute the measured total consumed, then picks the best available model to split that total across apps: measured subsystem power (Mode A), Apple Energy Impact (Mode B), or CPU-time deltas (Mode C). The split is scaled so its parts sum to the measured total.
Why this architecture works
- 📐 Total is measured, split is modeled — the report never blurs the two, and always prints which mode it used.
- 🔌
StartIntervalnever wakes a sleeping Mac — cost while the lid is closed is zero; awake cost is ~0.5%/day for full mode, ~0.04 Wh/day for Tier 1. - 🧱 Unattributable power gets its own line — ANE and display/DRAM/other are shown explicitly, never silently smeared across your apps.
- 🗂 Plain text, one dir — no database service, trivially greppable, trivially deletable.
- 🛡 Defensive parsing — powermetrics key names vary by macOS/hardware; the parser uses candidate lists and skips bad samples instead of crashing.
Things you can configure
# in the Tier 2 LaunchDaemon environment (com.battery-tracker.energy.plist)
SAMPLE_EVERY_N_MIN = 1 # raise to 5 to drop battery cost to ~0.1%/day- USAGE.md — install, every CLI flag, energy-cost table, acceptance tests, troubleshooting.
- Honesty notes on the attribution model live in USAGE.md and in
battracker.py's header.
Working end-to-end on Apple Silicon (macOS 26). Modes A/B/C, PowerLog backfill, cycle history, and HTML export are implemented and tested. Roadmap: --watch live mode polish, richer SVG charts. Issues and PRs welcome.