Files
mesh-catalog/modules/power/README.md
T
jochen 4f6cbd2197 Follow bar values so a dead watch speaks, date them by their reading, and widen the path check (hq ADR 0260)
A watch that died left the block silent, a removed block kept its value, and a value
frozen at its source looked fresh because its file was new. The follower now rereads a
silent value and says so, drops removed blocks, and dates each file by the reading's
own time. The path check takes /etc and /usr/local/lib in, ignores what a container sees
inside itself, and lists the five findings standing today, which may only go.
2026-10-08 16:12:37 +02:00

134 lines
8.5 KiB
Markdown

# 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 for the module's bundle, which puts it on the bus as `power.draw` (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, ADR 0260)
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` | the bar's own battery block: the bar reads the kernel's power-supply class itself, not anything of this module's |
| power draw | `if-capability: power-meter` | this module's state `power.draw` for the machine, over the bus |
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.
**Over the bus, not through a file.** Modules talk over the bus. The energy counter is root's to read,
on purpose: read often by anyone, it leaks what the processor is doing (the PLATYPUS attack), so no
permission is loosened. Two parts of this module carry the reading:
1. the process `mesh-power-draw` (`power draw`, as root) samples every 5 s and leaves the averaged rate
in `/run/mesh-power/draw`, this module's own directory, read by nothing but this module;
2. the module's bundle, which the runtime launches, puts each fresh reading in its state `draw`, keyed by
the machine's name: `{"text", "watts", "measures", "at"}`. A value lives 30 s (`ttl-seconds`). The
state is `per-machine`: each machine's instance is granted its own machine's key and no other.
**State, not events, by bus load.** A reading is the current value and is replaced every five seconds;
nobody needs the ones before. A key per machine keeps one value, so the bus holds at most one reading
per machine, and a bar starting later reads it at once. An event every five seconds would keep each one
in a stream. On four machines the puts are under one message a second.
The bar's holder is granted the read by the mesh, because this module offers the state in its block; it
names no module. 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. 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.