Count the same meters the power module's sampler reads (hq ADR 0255)
mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered

An APU's integrated graphics and Intel's second (MMIO) view of a package made a machine
report a power meter the sampler would not read, so its bar could only say not
measured.
This commit is contained in:
jochen
2026-10-08 14:44:55 +02:00
parent e41ab3a505
commit 41b3392789
2 changed files with 53 additions and 9 deletions
+17 -7
View File
@@ -24,7 +24,8 @@ const (
CapBattery = "battery"
// CapPowerMeter is something here that measures the power the machine, or part of it, draws:
// the machine's battery, the processor package's energy counter (powercap, Intel's and AMD's
// RAPL), or a graphics device that reports its own power.
// RAPL), or a discrete graphics device that reports its own power — the same meters the power
// module's sampler reads, so a machine never gets a block that can only say "not measured".
CapPowerMeter = "power-meter"
)
@@ -89,24 +90,33 @@ func (powerMeter) Name() string { return CapPowerMeter }
// reader's question — powercap's energy counter is root's to read, and the power module samples it
// as root and publishes only a rate.
func (powerMeter) Detect(context.Context) Verdict {
const how = "a system battery; /sys/class/powercap/*/name starting package; a graphics device's " +
"hwmon power1_average or power1_input; /proc/driver/nvidia/gpus"
const how = "a system battery; /sys/class/powercap/*/name starting package, each name once; a discrete " +
"graphics device's (mem_info_vram_vendor) hwmon power1_average or power1_input; /proc/driver/nvidia/gpus"
var found []string
for _, b := range systemBatteries() {
found = append(found, "battery "+b)
}
// Each package once by its name: Intel shows some twice, as intel-rapl:N and intel-rapl-mmio:N.
if entries, err := os.ReadDir(PowercapSource); err == nil {
seen := map[string]bool{}
for _, e := range entries {
dir := filepath.Join(PowercapSource, e.Name())
if strings.HasPrefix(readTrimmed(filepath.Join(dir, "name")), "package") {
if _, err := os.Stat(filepath.Join(dir, "energy_uj")); err == nil {
found = append(found, "processor package "+e.Name())
}
name := readTrimmed(filepath.Join(dir, "name"))
if !strings.HasPrefix(name, "package") || seen[name] {
continue
}
if _, err := os.Stat(filepath.Join(dir, "energy_uj")); err == nil {
seen[name] = true
found = append(found, "processor "+name)
}
}
}
if cards, err := filepath.Glob(filepath.Join(SeatSource, "card*", "device", "hwmon", "hwmon*")); err == nil {
for _, hw := range cards {
// Integrated graphics report the package the processor's counter already holds.
if _, err := os.Stat(filepath.Join(filepath.Dir(filepath.Dir(hw)), "mem_info_vram_vendor")); err != nil {
continue
}
for _, f := range []string{"power1_average", "power1_input"} {
if _, err := os.Stat(filepath.Join(hw, f)); err == nil {
found = append(found, "graphics "+strings.Split(strings.TrimPrefix(hw, SeatSource+"/"), "/")[0])
+36 -2
View File
@@ -49,7 +49,7 @@ func TestALaptopHasABatteryAndAMeter(t *testing.T) {
t.Fatalf("battery: %+v", b)
}
m := powerMeter{}.Detect(context.Background())
if !m.Present || m.Detail != "battery BAT1, processor package intel-rapl:0" {
if !m.Present || m.Detail != "battery BAT1, processor package-0" {
t.Fatalf("power meter: %+v", m)
}
}
@@ -79,13 +79,14 @@ func TestADesktopMeasuresItsProcessorAndItsGraphics(t *testing.T) {
write(t, dir+"/powercap/intel-rapl:0:0/name", "core")
write(t, dir+"/powercap/intel-rapl:0:0/energy_uj", "1")
write(t, dir+"/drm/card1/device/hwmon/hwmon4/power1_average", "8000000")
write(t, dir+"/drm/card1/device/mem_info_vram_vendor", "samsung")
write(t, dir+"/nvidia/0000:01:00.0/information", "x")
if b := (battery{}).Detect(context.Background()); b.Present {
t.Fatalf("a desktop has a battery: %+v", b)
}
m := powerMeter{}.Detect(context.Background())
if !m.Present || m.Detail != "processor package intel-rapl:0, graphics card1, graphics nvidia" {
if !m.Present || m.Detail != "processor package-0, graphics card1, graphics nvidia" {
t.Fatalf("power meter: %+v", m)
}
}
@@ -97,3 +98,36 @@ func TestAMachineWithNothingToReadHasNeither(t *testing.T) {
t.Fatalf("%+v %+v", b, m)
}
}
func TestAPackageShownTwiceIsOneMeterAndIntegratedGraphicsAreNone(t *testing.T) {
dir := sysfs(t)
write(t, dir+"/powercap/intel-rapl:0/name", "package-0")
write(t, dir+"/powercap/intel-rapl:0/energy_uj", "1")
write(t, dir+"/powercap/intel-rapl-mmio:0/name", "package-0")
write(t, dir+"/powercap/intel-rapl-mmio:0/energy_uj", "1")
// An APU's graphics: power reported, no memory of its own.
write(t, dir+"/drm/card1/device/hwmon/hwmon5/power1_average", "19000000")
m := powerMeter{}.Detect(context.Background())
if !m.Present || m.Detail != "processor package-0" {
t.Fatalf("power meter: %+v", m)
}
}
func TestOnlyIntegratedGraphicsMeasureNothing(t *testing.T) {
dir := sysfs(t)
write(t, dir+"/drm/card0/device/hwmon/hwmon1/power1_input", "5000000")
if m := (powerMeter{}).Detect(context.Background()); m.Present {
t.Fatalf("integrated graphics were taken for a meter: %+v", m)
}
}
func TestABatteryOfUnknownScopeIsTheMachines(t *testing.T) {
// Many laptops' firmware says nothing of scope; the kernel reports Unknown. Only Device is a
// peripheral's.
dir := sysfs(t)
write(t, dir+"/power_supply/BAT0/type", "Battery")
write(t, dir+"/power_supply/BAT0/scope", "Unknown")
if b := (battery{}).Detect(context.Background()); !b.Present || b.Detail != "BAT0" {
t.Fatalf("battery: %+v", b)
}
}