Battery health monitor & hardware flight recorder for Linux laptops.
batmon is a zero-dependency, low-overhead background daemon that continuously records power, thermal, and system telemetry to local SQLite databases.
It operates as a high-frequency flight recorder, capturing hardware metrics every second to preserve the exact state of the machine in the event of a crash or kernel panic, while simultaneously maintaining a permanent, downsampled historical log for tracking long-term component wear and battery degradation.
batmon captures telemetry using two distinct tiers:
┌───────────────────────────────────────────────┐
│ batmon Daemon (Bun) │
└───────┬───────────────────────────────┬───────┘
│ (Every 1 sec) │ (Every 60 sec)
▼ ▼
┌──────────────────────────────┐ ┌──────────────────────────────┐
│ debug.db (Flight Log) │ │ battery.db (Historical DB) │
├──────────────────────────────┤ ├──────────────────────────────┤
│ • 1s sample resolution │ │ • 60s sample resolution │
│ • SQLite WAL + sync=NORMAL │ │ • SQLite WAL + sync=NORMAL │
│ • Auto-pruned (last 6 hours) │ │ • Permanent wear records │
│ • Crash & panic forensics │ │ • Cycle count & degradation │
└──────────────────────────────┘ └──────────────────────────────┘
-
High-Frequency Flight Recorder (
debug.db):
Records every 1 second directly to SQLite using WAL mode (PRAGMA synchronous = NORMAL). Coalesced by the Linux kernel page cache, it consumes negligible power (<15 mW) while ensuring that during hard lockups, thermal throttling, or kernel panics, the crucial minutes leading up to the failure are safely preserved on disk for post-mortem forensics (with at most ~2–5s uncommitted in kernel page cache during sudden hard power cuts). Auto-prunes older records on a rolling window (default: 6 hours). -
Long-Term Historical Telemetry (
battery.db):
Records downsampled samples every 60 seconds. Tracks long-term battery degradation, design wear capacity, and software-integrated cycle count over months and years.
| Category | Metric | Source | Description |
|---|---|---|---|
| Electrical & Power | voltage_v |
sysfs (battery) | Instantaneous battery rail voltage (V) |
power_w |
sysfs (battery) | Discharge / charge rate (Watts) | |
charge_pct |
sysfs (battery) | Current state of charge (%) | |
energy_wh |
sysfs (battery) | Remaining energy (Wh) | |
energy_full_wh |
sysfs (battery) | Current full charge capacity (Wh) | |
energy_design_wh |
sysfs (battery) | Factory nominal design capacity (Wh) | |
voltage_design_v |
sysfs (battery) | Factory design voltage (V) | |
is_charging |
sysfs (battery) | Charge state boolean | |
| Thermal Environment | cpu_temp_c |
sysfs (hwmon) |
CPU package / core temperature (e.g. AMD Tctl / Intel Package id) (°C) |
gpu_temp_c |
sysfs (hwmon) |
GPU temperature (e.g. AMD edge / Intel package) (°C) | |
nvme_temp_c |
sysfs (hwmon) |
NVMe composite temperature (°C) | |
battery_temp_c |
sysfs (battery / hwmon) |
Battery sensor temperature (if present) | |
| Clock & SoC Power | cpu_freq_mhz |
sysfs (cpufreq) / /proc
|
Instantaneous CPU clock frequency (MHz) |
gpu_power_w |
sysfs (hwmon) |
AMD APU / GPU package power (PPT via amdgpu) (Watts) | |
gpu_pct |
sysfs (DRM) | GPU compute / shader utilization (%) | |
| System Load | cpu_pct |
/proc/stat |
Global CPU utilization (%) |
mem_pct |
/proc/meminfo |
Global Memory utilization (%) | |
load1 |
/proc/loadavg |
1-minute system load average | |
top_processes |
/proc/[pid]/stat |
Top 5 aggregated process groups by 1s CPU delta (JSON) | |
| Health & Wear | health_pct |
sysfs | Full charge capacity vs design capacity (%) |
cycle_count |
sysfs | Hardware cycle count (if reported by BMS) | |
estimated_cycle_count |
Integrator | Calculated cycle count via energy throughput ( |
|
| Runtime Estimates | time_to_empty_s |
UPower D-Bus | Smoothed discharge runtime estimate (seconds) |
time_to_full_s |
UPower D-Bus | Smoothed charge completion estimate (seconds) |
- Auto-detects
energy_*(µWh) vscharge_*(µAh) battery drivers. - Low-Overhead Native Reads: All CPU, memory, clock, GPU, thermal, and process metrics are gathered directly via Linux kernel VFS interfaces (
/procand/sys) and standard POSIX process accounting (~5–8 ms execution per sample cycle) with zero child processes or external daemons. See empirical evaluations on Kernel VFS vs. Glances and Sysfs Hwmon vs. lm-sensors for detailed benchmark results. - Automatic Migrations: Database schema updates and column additions are handled seamlessly and automatically on startup using SQLite's native
user_versiontracking with zero manual migration steps required.
sqlite3 ~/.local/share/batmon/debug.db "
SELECT ts, power_w, voltage_v, cpu_freq_mhz, cpu_temp_c, gpu_power_w, cpu_pct, top_processes
FROM samples
ORDER BY id DESC
LIMIT 30;"sqlite3 ~/.local/share/batmon/battery.db "
SELECT ts, charge_pct, health_pct, cycle_count, estimated_cycle_count, energy_full_wh, energy_design_wh
FROM samples
ORDER BY id DESC
LIMIT 10;"sqlite3 ~/.local/share/batmon/debug.db "
SELECT ts, power_w, cpu_temp_c, top_processes
FROM samples
WHERE power_w > 30.0
ORDER BY id DESC
LIMIT 5;"batmon features a stateful alerting engine with deadband hysteresis, debouncing, and priority escalation to prevent notification storms from flapping sensors:
-
High Battery Temp Warning: Alert when battery temp
$\ge 45^\circ\text{C}$ (Critical at$50^\circ\text{C}$ with contextual cooling advice; re-arms below$42^\circ\text{C}$ /$47^\circ\text{C}$ ). -
Charging While Hot (Heat-Soak): Alert when charging while CPU
$\ge 85^\circ\text{C}$ (re-arms below$80^\circ\text{C}$ ). -
Charge Limits: Reminders to unplug at
$\ge 80%$ (re-arms below$75%$ ) and plug in at$\le 20%$ (Critical at$\le 10%$ suppresses normal low alert; re-arms above$25%$ ). - Over-Voltage Charging: Alert when charging voltage exceeds 15% above design voltage (re-arms at or below 10% above design voltage).
-
Battery Health Degradation: Warning when full capacity drops below
$80%$ of factory design (re-arms above$82%$ ).
Note: The current alert rules focus on battery protection, because by the time the voltage or power really drops, the system will be shutting down anyway. The idea is to prevent these issues from happening in the first place, not to detect them after the fact. The flight recorder is there to capture the data in case something does happen.
- Linux with systemd (Fedora, Ubuntu, Debian, Arch, etc.)
-
Bun runtime (
$\ge 1.3$ ) -
libnotify/notify-send(optional, for desktop notifications):sudo dnf install libnotify # Fedora/RHEL sudo apt install libnotify-bin # Ubuntu/Debian
-
sqlite3CLI (optional, for querying databases):sudo dnf install sqlite
git clone https://github.com/InvictusNavarchus/batmon.git
cd batmon
./install.sh
# or using bun:
bun run install-serviceThe installer will:
- Copy the application to
~/.local/share/batmon/src/. - Configure and start a
systemduser service (batmon.service). - Run an initial test verification.
# Check service status
systemctl --user status batmon.service
# View live logs
journalctl --user -u batmon.service -f
# Run a one-off diagnostic sample
bun run src/index.ts --oneshotRun unit tests and typechecks using Bun:
# Run test suite
bun test
# Run typechecker
bun run typecheck./uninstall.sh
# or using bun:
bun run uninstall-service(Databases in ~/.local/share/batmon/ are preserved upon uninstall).
- Battery cell-level data (individual cell voltages, internal impedance, BMS balancing status) is not available through the Linux
power_supplysysfs interface and cannot be collected. - VRM rail voltages and transient events below ~1 s are not exposed by the kernel on most laptop hardware. The 1-second flight recorder can catch sustained voltage sag but not microsecond-scale transients.
- The
battery_temp_csensor is absent on many laptops. When unavailable, battery thermal protection relies on ambient correlation with CPU/GPU temperatures. mem_pctandload1are recorded for forensic completeness but are rarely primary indicators of hardware failure.