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}