Files
mesh-catalog/modules/power
jochen 8bd17abd4e
mesh/repo-check fail: its merge-check.sh failed: long-running resources without health: 68
mesh/merge-gate fail: builds asus-zephyrus-g14, i3status-rust, power → ace, g14, novox, shanks; no bus step; a manifest the change touches fails the module…
mesh/delivery-group group feat/the-bar-takes-blocks delivering: 1 of 4 delivered
mesh/delivery superseded: a newer head of the same pull request
Count each processor package once, never read a sleeping card, and give no reading for a reset counter (hq ADR 0255)
Intel shows some packages twice (MSR and MMIO), an AMD card's sensors wake it, and a
counter reset after resume read as thousands of watts. The loop's cadence and the bar
script's staleness are now tested, and the sampler running on every machine is said.
2026-10-08 14:34:45 +02:00
..
2026-10-04 17:14:44 +02:00

power

A machine's power (novox/hq ADR 0211). This module holds node-power on every machine, servers included, because every machine boots and shuts down. It owns logind's power key and lid handling. It runs the code other modules contribute for the power moments, and it publishes the machine's power states on the bus.

What it owns

/etc/systemd/logind.conf.d/power.conf the power key and the lid, from this module's settings (below); logind is reloaded, never restarted
/etc/mesh-power/moments/<moment> for each moment, every module's code as the controller placed it (${shell:<moment>:<slot>})
/usr/local/lib/mesh-power/bin/power-moment the runner: each module's piece on its own, as root, with sh, bounded (MESH_POWER_BOUND, default 30 s); every outcome in the journal under mesh-power
five units mesh-power-after-boot, -before-shutdown (its stop is the shutdown, while the network is still up), -before-sleep, -after-wake, -supply
mesh-power-draw (process) samples the power drawn and publishes it to /run/mesh-power/draw for the bar (below)
/etc/udev/rules.d/90-mesh-power.rules a power supply's change starts mesh-power-supply, which runs on-mains or on-battery once per change of source

The moments, and how a module adds code

The moments are after-boot, before-sleep, after-wake, before-shutdown, on-mains and on-battery. A module declares a shell entry whose for names the moment, in the first, normal or last slot, with POSIX shell code. That entry depends on this seat (ADR 0210). The code runs as root. Code that needs the operator's session finds it itself, as the laptop module's touchpad reset does with runuser.

One rule for contributed code: the runner splits the placed file at lines of the form # <module>, which the controller writes before each module's piece. A comment line of the piece's own that is one lowercase word would split it too, so comments in contributed code use more than one word.

Code that must run inside the sleep transaction, such as the NVIDIA driver's own suspend and resume units, is not a contribution. Its module keeps its own drop-ins on the sleep services (ADR 0211, "What got harder").

The bar's battery and power draw (novox/hq ADR 0255)

The module holds the machine's power everywhere, so it gives the bar what is generic about power, on the machines that have it. Both are contributions to node-bar in the bar's own neutral terms, so any bar renders them, and each names the capability the node-engine reports for it:

block where what it shows
battery if-capability: battery (a Battery supply not scoped to a device) the charge, and the time left or to full
power draw if-capability: power-meter (a system battery, a powercap package counter, or a graphics device that reports its power) one line from bin/power-draw

The power draw says what it measured:

  • on battery, the battery's discharge: 18.2 W machine;
  • otherwise, the processor package's power from the kernel's powercap energy counter (RAPL, Intel and AMD), plus a discrete graphics device's own power where it reports one: 14.0 W CPU+GPU or 14.0 W CPU. That is part of the machine, and is called that part.
  • The machine at the wall needs a metered plug or a UPS and is not measured.

The energy counter stays root's. energy_uj is readable by root alone on purpose: read often by anyone, it leaks what the processor is doing (the PLATYPUS attack). So no permission is loosened. The module's process mesh-power-draw (power draw, as root) samples every 5 s and writes only the averaged rate, one line, to /run/mesh-power/draw (0644). The bar's script prints that line, and says power not measured when it is missing or older than 30 s. Graphics are counted honestly: an AMD device only when it has its own memory (an APU's integrated graphics report the whole package, which the counter already holds), and only while it is awake; an NVIDIA device through nvidia-smi only while it is awake, so the bar never wakes a sleeping GPU. Each package is counted once by its name: where Intel shows one twice (intel-rapl:N and intel-rapl-mmio:N), the first is read. A counter that gives more than 1000 W was reset, after a resume say, and that sample gives no reading.

The sampler runs wherever this module is assigned, servers included. A server has the energy counter too, so conditioning it on power-meter would not keep it off one, and the mesh has no way yet to condition a resource on another seat (the bar) being held on the machine. It costs one process reading a few kernel files every five seconds (novox/hq ADR 0255 §9).

Settings

handle-power-key, handle-lid-switch, handle-lid-switch-external-power and handle-lid-switch-docked, each one of logind's actions. They have no defaults, so the mesh's layer must be set before the first assignment. The mesh's layer carries logind's own defaults (poweroff, suspend, suspend, ignore). A machine's layer changes them: the laptop suspends on all four.

Events

event when
booted once per boot, not per restart of the runtime (the boot id is remembered)
sleeping before the machine sleeps: the module's watcher holds logind's delay lock, publishes, waits for the bus at most 3 s, and lets go
woke after waking, queued until the bus is reachable
shutting-down before a shutdown, as sleeping
on-mains, on-battery when the power source changes, on a machine with a Mains supply
battery-low at 10 % on battery, once per discharge

Each carries the time it happened. Events the bus did not take wait in order and go out when it answers again. A machine that said sleeping is asleep, not out of touch.

Tools

tool
power_state boot time, source and battery, lid, logind's settings, and the watcher's lock, last sleep and wake, and queue
power_hooks every moment's contributed pieces, with their module
power_history boots, sleeps, wakes and moment runs from the journal
power_run run one moment now, to test it (needs sudo -n)
power_check units, runner, moment files, one logind writer, no hand-placed sleep hooks, the lock held, nothing stuck before the bus

Migration

  • The laptop: its model module owned logind.conf.d/power.conf and a touchpad resume unit. Both move here: the file to this module, and the resume to an after-wake contribution.
  • The desktop: the predecessor left logind.conf.d/brightness.conf and /etc/systemd/system-sleep/xinput-reset-touchpad.sh. Move both aside after the first push; power_check names them while they remain.
  • The servers have no logind drop-ins and no sleep hooks today.

The lesson from the day this module was written belongs to the dbus module: a full upgrade restarted the system bus live on a workstation, and logins hung until a reboot.

Why the sleep hooks are wanted, not enabled. The host reads a one-shot unit that is not running and has not failed as having run and worked, so a hook unit waiting for a sleep could never be declared stopped. The first version tried that and was refused on its first apply. The two sleep units are therefore not services the mesh manages. They have no install section, and the sleep targets want them through this module's drop-ins (sleep.target.d, and suspend.target.d with its three siblings) — the same way the laptop module asks for the NVIDIA driver's sleep actions.

polkit is this module's package. logind gives a delay lock to a process that is not root only through polkit. A server without polkit refused the watcher with "Permission denied", and its sleeps and shutdowns would have gone unsaid. The module installs it; nothing else needed it there.