code wiki / _hdl_build / nx_perf_counters.nx

nx_perf_counters.nx source

↩ module page · 72 lines · 3661 B

1// nx_perf_counters.nx -- bits-up hardware performance counters via perf_event_open, 2// PURE NishiLang (no libc, no `perf` binary, no C). The STATE-INDEPENDENT energy 3// path: instruction / cycle / cache counts do NOT care whether the node is charging 4// or on AC -- they are the CPU's own activity, read straight from the silicon's 5// performance-monitoring unit. They feed a calibrated per-event energy model that 6// yields real joules on ANY hardware in ANY power state. (Battery-discharge was a 7// weak fallback; THIS is the S-class answer -- a real measurement everywhere.) 8// 9// Research (Cardinal #4, real sources): event-driven / PMC-based power modeling -- 10// Bellosa, "The Benefits of Event-Driven Energy Accounting," ACM SIGOPS EW 2000; 11// Isci & Martonosi, "Runtime Power Monitoring using PMCs," MICRO 2003; Bircher & 12// John, "Complete System Power Estimation using PMCs," ISPASS 2007; per-operation 13// energy figures from Horowitz, "Computing's Energy Problem," ISSCC 2014. 14// perf_event_open(2) is the Linux interface; the attr struct is built byte-exact. 15 16import "nx_syscalls.nx" 17 18@ifdef TARGET_X86_64 19const SYS_PERF_EVENT_OPEN: i64 = 298 20@endif 21@ifndef TARGET_X86_64 22const SYS_PERF_EVENT_OPEN: i64 = 241 23@endif 24 25const PERF_TYPE_HARDWARE: i64 = 0 26const PERF_TYPE_SOFTWARE: i64 = 1 27const PERF_COUNT_HW_CPU_CYCLES: i64 = 0 28const PERF_COUNT_HW_INSTRUCTIONS: i64 = 1 29// software events are KERNEL-EMULATED -- always available, even in a VM with no 30// PMU passthrough. TASK_CLOCK = nanoseconds of CPU time this task actually used: 31// a real, state-independent quantity (CPU time does not depend on charge state), 32// and energy = task_clock_ns * calibrated_active_power -> real joules anywhere. 33const PERF_COUNT_SW_CPU_CLOCK: i64 = 0 34const PERF_COUNT_SW_TASK_CLOCK: i64 = 1 35 36// Open a counter of (type, config) for THIS process (pid=0), any cpu (cpu=-1). The 37// perf_event_attr is 128 zeroed bytes: type(u32@0), size(u32@4)=128, config(u64@8), 38// flags(u64@40)=exclude_kernel|exclude_hv (0x60; required at perf_event_paranoid>=1, 39// disabled=0 so it counts immediately). Returns fd or <0 (-errno). 40func pc_open2(type: i64, config: i64) -> i64 { 41 let attr: *u8 = sys_mmap(128) 42 attr[0] = type as u8 // type @0 (u32) 43 attr[4] = 128 as u8 // size = sizeof(perf_event_attr) 44 attr[8] = config as u8 // config @8 (u64) 45 attr[40] = 0x60 as u8 // exclude_kernel(bit5) | exclude_hv(bit6); disabled=0 46 return __syscall(SYS_PERF_EVENT_OPEN, attr, 0, 0 - 1, 0 - 1, 0, 0) 47} 48 49// Convenience: a HARDWARE counter (instructions/cycles -- needs a real PMU). 50func pc_open(config: i64) -> i64 { return pc_open2(PERF_TYPE_HARDWARE, config) } 51 52// The portable counter: prefer hardware INSTRUCTIONS (best for energy modeling); 53// if the PMU is absent (VM), fall back to software TASK_CLOCK (CPU-time ns). 54// Writes the chosen (type,config) into out[0],out[1]. Returns fd or <0. 55func pc_open_best(out: *i64) -> i64 { 56 let hw: i64 = pc_open2(PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS) 57 if hw >= 0 { out[0] = PERF_TYPE_HARDWARE; out[1] = PERF_COUNT_HW_INSTRUCTIONS; return hw } 58 let sw: i64 = pc_open2(PERF_TYPE_SOFTWARE, PERF_COUNT_SW_TASK_CLOCK) 59 if sw >= 0 { out[0] = PERF_TYPE_SOFTWARE; out[1] = PERF_COUNT_SW_TASK_CLOCK; return sw } 60 return sw 61} 62 63// Read the 64-bit counter (read_format=0 -> a single little-endian u64). 64func pc_read(fd: i64) -> i64 { 65 let buf: *u8 = sys_mmap(8) 66 let n: i64 = sys_read(fd, buf, 8) 67 if n != 8 { return 0 - 1 } 68 var v: i64 = 0 69 var i: i64 = 0 70 while i < 8 { v = v | ((buf[i] as i64) << (i * 8)); i = i + 1 } 71 return v 72}