What it does
jolt is a terminal-based battery and power monitor for macOS and Linux. It shows charge percentage, time remaining, per-component power draw (CPU, GPU, ANE), process energy impact, and historical trends — all in a ratatui TUI with theme support.
Why I starred it
macOS tells you battery percentage and a vague time estimate. It does not tell you that your GPU is pulling 8W because Chrome decided to use hardware acceleration on a YouTube tab. jolt does. It reads actual power metrics from Apple's IOReport framework and the SMC (System Management Controller), breaks them down by component, and lets you kill the offending process right from the TUI.
The background daemon angle is what pushed it over the edge for me. It records samples to SQLite, aggregates hourly and daily stats, tracks charge/discharge sessions, and even forecasts remaining runtime based on your actual usage patterns rather than whatever Apple's algorithm guesses.
How it works
The architecture is a Rust workspace with four crates: cli (the TUI and daemon), platform (OS-specific battery and power abstractions), protocol (daemon wire format), and theme.
The most interesting part lives in crates/platform/src/macos/power.rs. Instead of shelling out to pmset or reading /sys/class/power_supply, jolt links directly against Apple's private IOReport dylib:
#[link(name = "IOReport", kind = "dylib")]
extern "C" {
fn IOReportCopyChannelsInGroup(
a: CFStringRef, b: CFStringRef,
c: u64, d: u64, e: u64,
) -> CFDictionaryRef;
fn IOReportCreateSubscription(
a: *const c_void, b: CFMutableDictionaryRef,
c: *mut CFMutableDictionaryRef, d: u64, e: *const c_void,
) -> IOReportSubscriptionRef;
fn IOReportCreateSamples(...) -> CFDictionaryRef;
fn IOReportCreateSamplesDelta(...) -> CFDictionaryRef;
}
It subscribes to the "Energy Model" channel group, takes two samples with a time delta between them, then calls IOReportCreateSamplesDelta to get the difference. The delta iteration in calculate_power_from_delta classifies channels by name — anything containing "gpu" goes to GPU power, "cpu" or "pacc" to CPU, "ane" to the neural engine, and a handful of others (amcc, dcs, dram, isp) to a catch-all package total. The energy values come in millijoules, microjoules, or nanojoules depending on the channel, and energy_to_watts at power.rs:420 handles the conversion:
fn energy_to_watts(value: i64, unit: &str, duration_ms: u64) -> Option<f32> {
let val = value as f32;
let duration_sec = duration_ms as f32 / 1000.0;
match unit {
"mJ" => val / 1_000.0 / duration_sec,
"uJ" => val / 1_000_000.0 / duration_sec,
"nJ" => val / 1_000_000_000.0 / duration_sec,
_ => return None,
}
}
For total system power, it also opens the SMC through IOKit and reads the PSTR key — the same register that powermetrics uses. The Smc struct in the same file handles the IOKit handshake: it finds the AppleSMCKeysEndpoint service, opens a connection, and reads 4-byte FourCC keys via IOConnectCallStructMethod. A nice detail — it caches KeyInfo lookups in a HashMap so repeated reads skip the info query.
Power readings get smoothed over a 5-sample rolling window (SMOOTHING_SAMPLE_COUNT) before surfacing them to the UI. There is a warmup phase: the is_warmed_up flag stays false until at least 3 samples are collected, which the UI uses to show a "warming up" indicator.
Battery data comes from the starship-battery crate (a maintained fork of rust-battery that fixes macOS-specific issues), enriched with ioreg -rn AppleSmartBattery output for fields like amperage, charger wattage, and daily min/max state-of-charge that the battery crate doesn't expose.
The daemon (cli/src/daemon/server.rs) runs as a tokio async server on a Unix socket. It spawns a RefreshWorker on a dedicated thread for the blocking IOKit calls, communicates via std::sync::mpsc, and pushes snapshots to connected subscribers. Historical data goes into SQLite through Recorder, and the Aggregator (cli/src/data/aggregator.rs) rolls raw samples into hourly stats, daily stats, and daily cycle reports — including partial cycle counts and time spent above 80% SoC.
The forecast module (cli/src/data/forecast.rs) averages power draw over discharging samples only, calculates remaining Wh from current percentage and battery capacity, and divides. It caps predictions at 24 hours and requires at least 10 samples before producing a forecast. Straightforward math, but more honest than the OS estimate because it uses your actual historical draw instead of an instant reading.
Using it
brew install jordond/tap/jolt
jolt
The TUI launches with battery status, power breakdown, and a process list sorted by energy impact. Press K to kill a process, g to toggle graphs, t for themes.
For scripting, jolt pipe streams JSON:
jolt pipe -s 3 -i 2000 -c
That gives you 3 compact JSON samples at 2-second intervals — useful for piping into jq or feeding into a dashboard.
The daemon collects data in the background:
jolt daemon start
jolt history top -p week -l 10
Rough edges
The IOReport API is undocumented and private. Apple could break it any macOS release. Intel Macs get battery data but no power metrics at all — the IOReport Energy Model group only exists on Apple Silicon.
Linux support requires RAPL permissions, which means either running as root or configuring capability flags. The README links to a setup guide, but it is a friction point.
The channel name matching in calculate_power_from_delta is string-based — channel_lower.contains("gpu"), channel_lower.contains("cpu"). If Apple adds a new channel that contains "cpu" in its name but represents something else, the numbers would be wrong silently. A fallback path exists using sysinfo CPU usage to estimate power, but it is a rough heuristic (linear interpolation between 2W and 17W).
No integration tests. The test suite in main.rs covers CLI parsing and CSV escaping, and forecast.rs has unit tests for the prediction logic. But the IOKit, SMC, and daemon code paths are untested — understandable given the hardware dependency, but worth knowing.
Bottom line
If you are on an Apple Silicon Mac and want to know where your watts are going, jolt is the best terminal option I have seen. The direct IOKit integration gives it data that pmset and Activity Monitor do not surface, and the daemon with SQLite history makes it useful beyond a quick glance.
