Carry the power draw to the bar over the bus, not through a file (hq ADR 0260)

Modules talk over the bus. The power module's bundle puts each fresh reading of its own
sampler in its state draw, keyed by the machine; the bar's block shows that state, which
the bar's bundle watches under the read the mesh grants it. A new catalogue check refuses
a module naming another module's path under /run or /var/lib.
This commit is contained in:
jochen
2026-10-08 16:12:37 +02:00
parent e1b7fcd8c9
commit 76b63744e6
21 changed files with 697 additions and 73 deletions
+24 -14
View File
@@ -13,7 +13,7 @@ states on the bus.
| `/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) |
| `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
@@ -32,7 +32,7 @@ Code that must run *inside* the sleep transaction, such as the NVIDIA driver's o
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 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
@@ -40,8 +40,8 @@ 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` |
| 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:
@@ -51,16 +51,26 @@ The power draw says what it measured:
`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.
**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`).
**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