nx_perf_probe.nx source
↩ module page · 184 lines · 6437 B
1// nx_perf_probe.nx -- substrate performance counter primitive.
2//
3// Cardinal: feedback-systemic-injection-everything-zero-runtime-overhead.
4//
5// Same three-layered pattern as nx_bit_provenance / nx_race_telemetry:
6//
7// (1) Born-into-build: callers use nx_perf_tick_start / tick_end
8// (or the wrap_call helpers below).
9// (2) Retroactive: future self-host IR pass injects entry/exit
10// calls driven by `// nx_perf: budget_ns=...` annotations on
11// function definitions.
12// (3) Zero runtime cost when --strip perf:
13// NX_PERF_ACTIVE compile-folded to 0 + entire machinery DCE'd.
14//
15// Storage: ring buffer of (function_tag, start_ns, end_ns, iter)
16// records. No per-tick syscalls; nx_perf_now() uses
17// sys_clock_gettime_mono once per tick.
18//
19// API surface:
20// let tk: i64 = nx_perf_tick_start(NX_PERF_TAG_MATMUL)
21// // ... work to time ...
22// nx_perf_tick_end(tk) -- elapsed recorded
23//
24// nx_perf_drain(out: *i64, cap: i64) -> i64
25// nx_perf_n_records() -> i64
26// nx_perf_enable() / nx_perf_disable() / nx_perf_is_active()
27//
28// Each tick uses ONE clock syscall + one ring-buffer write. No
29// startup cost. No ledger allocation until first tick.
30
31import "syscalls.nx"
32const NX_MAGIC_4096: i64 = 4096
33const NX_MAGIC_1000000000: i64 = 1000000000
34const NX_MAGIC_1024: i64 = 1024
35
36static NX_PERF_ACTIVE: i64
37
38static NX_PERF_RING_PTR: i64
39static NX_PERF_RING_HEAD: i64
40static NX_PERF_RING_CAP: i64
41
42const NX_PERF_REC_SIZE: i64 = 32 // 4 i64 fields
43const NX_PERF_RING_BYTES: i64 = 32768 // 1024 records
44
45// Stack-pending tick state: a CALLER receives an opaque handle
46// from tick_start that they later pass to tick_end. Internally
47// the handle is just the record-slot index biased by +1 (so 0 is
48// reserved as "no tick"). The "start_ns" is written to the slot
49// at tick_start; tick_end fills "end_ns".
50//
51// Concurrent tick handles per record-slot via the ring's natural
52// append order (no nested-handle protection in V0 -- enforce
53// well-formed start/end pairs at caller).
54
55func nx_perf_init() -> i64 {
56 if NX_PERF_RING_PTR != 0 { return 0 }
57 let raw: *u8 = sys_mmap(NX_PERF_RING_BYTES)
58 if (raw as i64) == 0 { return 1 }
59 if (raw as i64) == 0 - 1 { return 2 }
60 NX_PERF_RING_PTR = raw as i64
61 NX_PERF_RING_HEAD = 0
62 NX_PERF_RING_CAP = NX_PERF_RING_BYTES / NX_PERF_REC_SIZE
63 NX_PERF_ACTIVE = 1
64 return 0
65}
66
67func nx_perf_enable() -> i64 { NX_PERF_ACTIVE = 1; return 0 }
68func nx_perf_disable() -> i64 { NX_PERF_ACTIVE = 0; return 0 }
69func nx_perf_is_active() -> i64 { return NX_PERF_ACTIVE }
70
71// Read monotonic clock. Returns ns since some unspecified epoch
72// (only differences are meaningful).
73func nx_perf_now_ns() -> i64 {
74 let ts_raw: *u8 = sys_mmap(NX_MAGIC_4096)
75 let ts: *i64 = ts_raw as *i64
76 let r: i64 = __syscall(113, 1, ts_raw as i64, 0, 0, 0, 0)
77 if r != 0 { return 0 }
78 return ts[0] * NX_MAGIC_1000000000 + ts[1]
79}
80
81// Begin a tick. Returns an opaque handle (slot+1; 0 means
82// telemetry was disabled). Caller must pair with nx_perf_tick_end.
83func nx_perf_tick_start(tag: i64) -> i64 {
84 if NX_PERF_ACTIVE == 0 { return 0 }
85 nx_perf_init()
86 if NX_PERF_RING_PTR == 0 { return 0 }
87
88 let slot: i64 = NX_PERF_RING_HEAD % NX_PERF_RING_CAP
89 let base: i64 = NX_PERF_RING_PTR + (slot * NX_PERF_REC_SIZE)
90 let rec: *i64 = base as *i64
91 rec[0] = tag
92 rec[1] = nx_perf_now_ns()
93 rec[2] = 0 // end_ns; filled by tick_end
94 rec[3] = NX_PERF_RING_HEAD // monotone seq for matching
95 NX_PERF_RING_HEAD = NX_PERF_RING_HEAD + 1
96 return slot + 1
97}
98
99// End a tick. Handle must be the value returned by tick_start.
100// Records elapsed ns in the record's end_ns slot. Returns 0 OK,
101// non-zero on malformed handle (caller bug).
102func nx_perf_tick_end(handle: i64) -> i64 {
103 if NX_PERF_ACTIVE == 0 { return 0 }
104 if handle <= 0 { return 0 }
105 let slot: i64 = handle - 1
106 if slot >= NX_PERF_RING_CAP { return 1 }
107 let base: i64 = NX_PERF_RING_PTR + (slot * NX_PERF_REC_SIZE)
108 let rec: *i64 = base as *i64
109 rec[2] = nx_perf_now_ns()
110 return 0
111}
112
113// Drain the ring into a caller-owned i64 array (4 i64s per record).
114// Returns records copied. Resets the head.
115func nx_perf_drain(out: *i64, max_records: i64) -> i64 {
116 if NX_PERF_RING_PTR == 0 { return 0 }
117 nx_perf_init()
118 var n: i64 = NX_PERF_RING_HEAD
119 if n > NX_PERF_RING_CAP { n = NX_PERF_RING_CAP }
120 if n > max_records { n = max_records }
121 var i: i64 = 0
122 while i < n {
123 let in_base: i64 = NX_PERF_RING_PTR + (i * NX_PERF_REC_SIZE)
124 let in_rec: *i64 = in_base as *i64
125 out[i * 4 + 0] = in_rec[0]
126 out[i * 4 + 1] = in_rec[1]
127 out[i * 4 + 2] = in_rec[2]
128 out[i * 4 + 3] = in_rec[3]
129 i = i + 1
130 }
131 NX_PERF_RING_HEAD = 0
132 return n
133}
134
135func nx_perf_n_records() -> i64 { return NX_PERF_RING_HEAD }
136
137// ---- self-test ---------------------------------------------------
138// expect_exit: 0
139
140func main() -> i64 {
141 if nx_perf_init() != 0 { return 1 }
142
143 // Disabled-by-default short-circuit: tick_start returns 0.
144 nx_perf_disable()
145 if nx_perf_tick_start(0xC0DE0001) != 0 { return 10 }
146 if nx_perf_n_records() != 0 { return 11 }
147
148 // Enable + measure 3 ticks.
149 nx_perf_enable()
150 let tk1: i64 = nx_perf_tick_start(0xC0DE0001)
151 if tk1 == 0 { return 20 }
152 let dummy_work: i64 = nx_perf_now_ns() // burn a few ns
153 if nx_perf_tick_end(tk1) != 0 { return 21 }
154
155 let tk2: i64 = nx_perf_tick_start(0xC0DE0002)
156 nx_perf_tick_end(tk2)
157 let tk3: i64 = nx_perf_tick_start(0xC0DE0003)
158 nx_perf_tick_end(tk3)
159
160 if nx_perf_n_records() != 3 { return 30 }
161
162 // Drain + verify.
163 let out_raw: *u8 = sys_mmap(NX_MAGIC_4096)
164 let out: *i64 = out_raw as *i64
165 let n: i64 = nx_perf_drain(out, NX_MAGIC_1024)
166 if n != 3 { return 40 }
167 if out[0] != 0xC0DE0001 { return 41 }
168 // out[1] = start_ns; out[2] = end_ns; both nonzero, end >= start
169 if out[1] == 0 { return 42 }
170 if out[2] < out[1] { return 43 }
171 if out[4] != 0xC0DE0002 { return 44 }
172 if out[8] != 0xC0DE0003 { return 45 }
173
174 // Head reset after drain.
175 if nx_perf_n_records() != 0 { return 50 }
176
177 // Disabled + ensure no growth.
178 nx_perf_disable()
179 let tk_disabled: i64 = nx_perf_tick_start(0xC0DE0099)
180 if tk_disabled != 0 { return 60 }
181 if nx_perf_n_records() != 0 { return 61 }
182
183 return 0
184}