code wiki / _hdl_build / nx_energy_probe.nx

nx_energy_probe.nx source

↩ module page · 138 lines · 6254 B

1// nx_energy_probe.nx -- REAL-hardware energy probe, HARDWARE-AGNOSTIC. 2// 3// The gate-activity model (nx_gate_energy) gives a PORTABLE energy currency that 4// runs on any hardware. This probe is the other half: read the REAL energy the 5// NODE actually burns, using whatever sensor THIS silicon exposes, and degrade 6// gracefully when none exists -- never fabricating a joule we didn't measure. 7// 8// Source priority (best real measurement first), detected at runtime: 9// RAPL -- x86 package energy counter via /sys (microjoules, accumulating). Real. 10// PSU -- battery fuel-gauge. The GARDEN-SENSOR / laptop-on-battery case. HONEST 11// RULE: a battery only attributes the CPU's energy while DISCHARGING -- 12// on AC the wall adapter powers the CPU and the pack charges separately, 13// so a charging battery CANNOT measure the workload. And energy_now is 14// uWh-coarse (a short workload moves it by 0), so we integrate 15// instantaneous power_now (uW) x measured time (us) -> real microjoules. 16// I2C -- an INA219/INA260 current-sense chip on an I2C bus (/dev/i2c-*): the 17// canonical embedded power sensor. Detected; the INA219 register read 18// streams in as a later SEED piece (honest: not faked here). 19// CYCLE -- rdtsc/rdcycle: ALWAYS available, every target (the compiler lowers 20// __rdtsc to rdcycle on RV64/ARM). NOT a sad fallback: the cycle COUNT is 21// itself CHARGE-INDEPENDENT, and nx_energy_model turns it into real joules 22// via a calibrated fJ/cycle -> energy is measurable in ANY power state. 23// 24// IMPORTANT (operator: "only measuring when not charging is not S-class"): the 25// S-class path does NOT depend on the battery. RAPL and an INA219 rail sensor are 26// state-independent; nx_energy_model + nx_perf_counters give real joules from 27// charge-independent activity counters (cycles/instructions) on ANY node in ANY 28// power state. The battery-discharge path is only a calibration source, demoted 29// below the state-independent ones. Cardinal #14: a missing sensor is never a 30// failure -- fall through to the universal charge-independent estimator. 31// 32// CITATIONS (real): RAPL -- Intel SDM Vol.3B; Khan et al., ACM TOMPECS 2018. Battery 33// -- Linux power_supply class (Documentation/power/power_supply_class.rst, GPL). 34// Rail sensor -- TI INA219 datasheet (SBOS448). Activity->energy -- Horowitz, ISSCC 35// 2014; Bircher & John, ISPASS 2007. 36 37import "nx_syscalls.nx" 38 39const ENERGY_SRC_NONE: i64 = 0 40const ENERGY_SRC_RAPL: i64 = 1 41const ENERGY_SRC_PSU: i64 = 2 42const ENERGY_SRC_I2C: i64 = 3 43const ENERGY_SRC_CYCLE: i64 = 4 44 45func ep_file_readable(path: *u8) -> i64 { 46 let fd: i64 = sys_openat_rd(path) 47 if fd < 0 { return 0 } 48 sys_close(fd) 49 return 1 50} 51 52// Read the leading decimal integer from a small sysfs text file. -1 if unreadable. 53func ep_read_decimal_file(path: *u8) -> i64 { 54 let fd: i64 = sys_openat_rd(path) 55 if fd < 0 { return -1 } 56 let buf: *u8 = sys_mmap(64) 57 let n: i64 = sys_read(fd, buf, 63) 58 sys_close(fd) 59 if n <= 0 { return -1 } 60 var v: i64 = 0 61 var i: i64 = 0 62 var any: i64 = 0 63 while i < n { 64 let c: i64 = buf[i] as i64 65 if c < 48 { i = n } 66 else { if c > 57 { i = n } 67 else { v = v * 10 + (c - 48); any = 1; i = i + 1 } } 68 } 69 if any == 0 { return -1 } 70 return v 71} 72 73// First byte of a sysfs file (e.g. status: 'D'=Discharging, 'C'=Charging). -1 if absent. 74func ep_read_first_char(path: *u8) -> i64 { 75 let fd: i64 = sys_openat_rd(path) 76 if fd < 0 { return -1 } 77 let buf: *u8 = sys_mmap(8) 78 let n: i64 = sys_read(fd, buf, 1) 79 sys_close(fd) 80 if n <= 0 { return -1 } 81 return buf[0] as i64 82} 83 84// is ANY battery present (BAT0/BAT1)? 85func ep_psu_present() -> i64 { 86 if ep_file_readable("/sys/class/power_supply/BAT0/power_now" as *u8) == 1 { return 1 } 87 if ep_file_readable("/sys/class/power_supply/BAT1/power_now" as *u8) == 1 { return 1 } 88 return 0 89} 90// is a battery DISCHARGING (status starts with 'D' = 68)? only then can it attribute CPU energy. 91func ep_psu_discharging() -> i64 { 92 if ep_read_first_char("/sys/class/power_supply/BAT0/status" as *u8) == 68 { return 1 } 93 if ep_read_first_char("/sys/class/power_supply/BAT1/status" as *u8) == 68 { return 1 } 94 return 0 95} 96// instantaneous discharge power in microwatts; -1 if absent. 97func ep_psu_power_uw() -> i64 { 98 let p0: i64 = ep_read_decimal_file("/sys/class/power_supply/BAT0/power_now" as *u8) 99 if p0 >= 0 { return p0 } 100 return ep_read_decimal_file("/sys/class/power_supply/BAT1/power_now" as *u8) 101} 102 103// Detect the best USABLE real energy source on THIS node. 104func ep_detect() -> i64 { 105 if ep_file_readable("/sys/class/powercap/intel-rapl:0/energy_uj" as *u8) == 1 { return ENERGY_SRC_RAPL } 106 if ep_psu_discharging() == 1 { if ep_psu_power_uw() >= 0 { return ENERGY_SRC_PSU } } 107 if ep_file_readable("/dev/i2c-1" as *u8) == 1 { return ENERGY_SRC_I2C } 108 return ENERGY_SRC_CYCLE 109} 110 111// Accumulating counter read (RAPL microjoules / CYCLE cycles). PSU uses the power 112// path instead, so returns -1 here. 113func ep_read(src: i64) -> i64 { 114 if src == ENERGY_SRC_RAPL { return ep_read_decimal_file("/sys/class/powercap/intel-rapl:0/energy_uj" as *u8) } 115 if src == ENERGY_SRC_PSU { return -1 } 116 if src == ENERGY_SRC_I2C { return -1 } 117 return __rdtsc() 118} 119 120func ep_src_name(src: i64) -> *u8 { 121 if src == ENERGY_SRC_RAPL { return "RAPL x86 package -- microjoules, REAL" as *u8 } 122 if src == ENERGY_SRC_PSU { return "battery fuel-gauge, discharging -- power_now x time, REAL" as *u8 } 123 if src == ENERGY_SRC_I2C { return "I2C INA219 current sensor -- present (driver piece pending)" as *u8 } 124 if src == ENERGY_SRC_CYCLE { return "cycle counter rdtsc/rdcycle -- universal speed proxy" as *u8 } 125 return "none" as *u8 126} 127 128// RAPL/PSU yield real joules; CYCLE/I2C(here) do not. 129func ep_is_joule_source(src: i64) -> i64 { 130 if src == ENERGY_SRC_RAPL { return 1 } 131 if src == ENERGY_SRC_PSU { return 1 } 132 return 0 133} 134// PSU is measured by power x time (not an accumulating delta). 135func ep_is_power_source(src: i64) -> i64 { 136 if src == ENERGY_SRC_PSU { return 1 } 137 return 0 138}