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}