--- name: Core power-state probe type: architecture-map-page slice: 05-core-storage-hotkey-build last_verified: 2026/05/09 --- # Core power-state probe > **Where you are:** [Architecture map](../README.md) → [Core, Storage, Hotkey, Build](README.md) → Core power-state probe **Plain English summary.** Reports whether the machine is on AC or battery so callers can drop thread counts and skip GPU offload when energy matters more than throughput. Linux uses the documented `/sys/class/power_supply/` ABI. macOS and Windows return `Unknown` for now. ## At a glance - File: `crates/core/src/power.rs` (328 LOC, 124 of which are tests). - External deps: standard library only (sysfs is plain text). Tests use `tempfile` (dev-dep). - Public surface: `PowerState`, `parse_power_state_from_dir`, `probe_power_state`. Plus `with_override` and `force_clear_cache` / `force_set_cache` as `pub(crate)` test helpers. - Consumers: [`core-tuning.md`](core-tuning.md). The runtime-capabilities banner in slice 2 may also surface the state. ## What's in here ### `PowerState` — `crates/core/src/power.rs:15` ```rust pub enum PowerState { OnAc, OnBattery, Unknown } ``` `Unknown` is treated as `OnAc` by callers, preserving today's pre-clamp behaviour on platforms where the probe cannot fire. ### `parse_power_state_from_dir` — `crates/core/src/power.rs:40` Pure function. Walks a `/sys/class/power_supply/`-style directory and applies these rules (matching the kernel's documented sysfs ABI): 1. Any entry with `type` in {`Mains`, `USB`} and `online == 1` → `OnAc`. 2. Else any entry with `type == Battery` → `OnBattery`. 3. Else → `Unknown`. Top-level failures (missing dir, unreadable supply_dir) return `Unknown` without panicking. Per-entry failures are silently skipped. ### `probe_power_state()` — `crates/core/src/power.rs:115` Resolution order, highest to lowest priority: 1. **In-process test override** (set via `with_override`). Test-only, never compiled into release builds. 2. **`MAGNOTIA_POWER_STATE_OVERRIDE` env var** — `ac` | `battery` | `unknown`, case-insensitive. Used by the `thread_sweep.rs` integration tests. 3. **Linux:** `parse_power_state_from_dir("/sys/class/power_supply")`. 4. **macOS / Windows / other:** `Unknown`. Both override paths bypass the cache so tests always see the value they set. ### TTL cache — `crates/core/src/power.rs:75-99, 124-136` ```rust const POWER_STATE_TTL: Duration = Duration::from_secs(10); ``` Result is cached in a `Mutex>` for 10 seconds. Caching prevents the inference thread-tuning helper from calling sysfs on every inference call (~10 syscalls per probe; called every chunk). ### Test helpers — `crates/core/src/power.rs:88, 93, 188` - `force_clear_cache()` — `pub(crate)` — drops the cache slot. - `force_set_cache(state)` — `pub(crate)` — pre-populates the cache slot. - `with_override(state, body) -> R` — `pub(crate)` — sets `TEST_OVERRIDE` for the duration of `body`. Holds a dedicated `TEST_LOCK` so override-using unit tests run serially even when cargo runs the test binary multi-threaded. `OverrideGuard` resets the override on drop, so a panicking test body cannot leak stale state. All three helpers are `#[cfg(test)]`-gated and never compiled into release. ## Data flow / contract - `probe_power_state` is the only public entry point production callers use. - The cache TTL means a power-source change takes up to 10 s to take effect. Acceptable for thread tuning; not adequate for a UI battery indicator. - Cache mutex poisoning is treated as a panic via `.expect("power cache mutex poisoned")`. A panicking holder of this mutex is already a bug; the loud failure mode is on purpose. ## Tests 11 tests in `crates/core/src/power.rs:202-327`: - `power_state_variants_are_distinct` — sanity. - `parses_mains_online_as_on_ac` / `parses_battery_only_as_on_battery` / `parses_usb_pd_online_as_on_ac` — happy paths. - `parses_empty_dir_as_unknown` / `parses_missing_dir_as_unknown` / `parses_malformed_files_as_unknown_gracefully` — failure paths. - `override_drives_battery` / `override_drives_ac` / `override_drives_unknown` — in-process override. - `env_var_override_battery_via_set_var` — env-var override under the same `TEST_LOCK`. - `env_var_override_garbage_falls_through` — invalid env values fall through to the platform probe. - `ttl_cache_returns_cached_value_within_window` / `ttl_cache_clears_via_force_clear` — cache invariants. ## Watch-outs - **macOS and Windows return `Unknown` always.** Native probes (`IOPSGetProvidingPowerSourceType` on macOS, `GetSystemPowerStatus` on Windows) are deferred. Consumers must treat `Unknown` as `OnAc` or behaviour will silently halve thread counts on every non-Linux machine. - **Per-entry sysfs read failures are silent.** The `read_trimmed().unwrap_or_default()` pattern means a permission-denied `online` file in a `Mains` entry would read as the empty string and the supply would be skipped. On a stuck-AC laptop where Mains was the unreadable entry, the function would return `OnBattery`. Documented in the function's doc comment at `power.rs:31`. Sysfs entries are world-readable in practice. - **`TEST_OVERRIDE` is `static Mutex>`.** Process-global, test-only. Production builds do not compile it because of `#[cfg(test)]`. ## See also - [Inference thread tuning](core-tuning.md) — the only production caller. - [Hardware probe](core-hardware-probe.md) — sibling.