code wiki / (root) / nx_perf_lib.nx

nx_perf_lib.nx source

↩ module page · 209 lines · 10491 B

1// nx_perf_lib.nx -- FRAME-BUDGET TELEMETRY, the AAA way, as ONE sovereign ruler (GE32 + GE15, 2026-09-02). 2// 3// Operator: "evaluate at minimum the way AAA teams do -- every frame is a budget that cannot be overspent; 4// frame time and frametime CONSISTENCY, spikes, p50/p95/p99". The estate measured all of that in page 5// JavaScript (?perf=1: p50/p95/p99/jank/worst) and NOTHING read it (gameengine GE15). This lib moves the 6// arithmetic into NishiLang so the ENGINE (wasm), the served HUD and the pacing referee gate all compute the 7// same numbers from the same window -- one ruler, three consumers -- and the page is left with a door call 8// and a display line, which is the interop-only law. 9// 10// WASM-COMPATIBLE BY CONSTRUCTION: no allocation, no syscalls, no floating point. The caller hands in a 11// region of PF_WORDS i64 (an engine arena offset, or a native mmap) and every function is a pure operation 12// over it. Integer milliseconds throughout; the page rounds the rAF interval before it crosses. 13// 14// WINDOW vs CUMULATIVE, deliberately both: a ring buffer reports the RECENT, not the WORST (estate law), so 15// the header carries cumulative counters (frames ever, worst ever, over-budget ever) beside the PF_N-frame 16// window the percentiles are computed over. Percentiles use the nearest-rank method over a 0..PF_CLAMP_MS 17// histogram (rank = ceil(permil x n / 1000)). pf_push preserves legacy capped input; pf_push_raw retains 18// stalls beyond the histogram and resolves their ranks from the raw ring. An EMPTY window answers PF_UNOBSERVED (-1) to every statistic -- a check that reads 0 19// frames as 0 ms would pass on the empty set. 20// 21// COMPOSES nx_frame_budget (the estate's per-frame budget ruler, microseconds): the budget in ms for a target 22// fps is fb_budget_us(fps)/1000 and fits/headroom are fb_fits/fb_headroom_us -- not re-derived here. 23// license_tier: ORIGINAL No hw writes (Rule 26). 24import "_hdl_build/nx_frame_budget.nx" 25 26const PF_N: i64 = 256 // the window: ~4.3 s at 60 Hz, the same order as the page's 600-frame ?perf probe at 10 s 27const PF_HDR: i64 = 8 // header words before the samples (layout below) 28const PF_CLAMP_MS: i64 = 1000 // a frame longer than a second is recorded AT the clamp (and still counts as worst) 29const PF_HBINS: i64 = 1001 // histogram bins 0..PF_CLAMP_MS inclusive 30const PF_WORDS: i64 = 1265 // PF_HDR + PF_N + PF_HBINS -- the region a caller must provide (8 + 256 + 1001) 31const PF_UNOBSERVED: i64 = 0 - 1 32const PF_PERMIL: i64 = 1000 33const PF_US_PER_MS: i64 = 1000 34const PF_SPIKE_MULT: i64 = 2 // the shipped-industry jank definition the page already used: a frame above 2x the window median 35// header slots 36const PF_H_COUNT: i64 = 0 // samples in the window (<= PF_N) 37const PF_H_HEAD: i64 = 1 // next write slot 38const PF_H_TOTAL: i64 = 2 // frames ever pushed 39const PF_H_WORST: i64 = 3 // worst frame ever (ms, clamped) 40const PF_H_OVER: i64 = 4 // frames ever over the budget handed to pf_push 41const PF_H_SUM: i64 = 5 // sum of ms ever pushed (clamped values) 42const PF_H_BUDGET: i64 = 6 // the budget (ms) the last push was judged against 43const PF_H_RESERVED: i64 = 7 44// referee verdicts (three-state: a referee that cannot see must abstain, never acquit) 45const PF_GREEN: i64 = 0 46const PF_RED: i64 = 1 47const PF_ABSTAIN: i64 = 3 48 49func pf_words() -> i64 { return PF_WORDS } 50func pf_window() -> i64 { return PF_N } 51func pf_clamp_ms() -> i64 { return PF_CLAMP_MS } 52 53func pf_init(r: *i64) -> i64 { 54 var i: i64 = 0 55 while i < PF_HDR + PF_N { r[i] = 0; i = i + 1 } 56 return 0 57} 58// SELF-HEAL: a header that cannot be right (a count past the window, a head past the ring) means the region 59// was never initialised or was clobbered; reset rather than index past the ring. Reported through the total 60// (which restarts at 0), never hidden. 61func pf_valid(r: *i64) -> i64 { 62 if r[PF_H_COUNT] < 0 { return 0 } 63 if r[PF_H_COUNT] > PF_N { return 0 } 64 if r[PF_H_HEAD] < 0 { return 0 } 65 if r[PF_H_HEAD] >= PF_N { return 0 } 66 return 1 67} 68func pf_push(r: *i64, ms: i64, budget_ms: i64) -> i64 { 69 var m: i64 = ms 70 if m > PF_CLAMP_MS { m = PF_CLAMP_MS } 71 return pf_push_raw(r, m, budget_ms) 72} 73// Additive entry point: keep legacy callers' cap while enabling truthful raw-frame consumers. 74func pf_push_raw(r: *i64, ms: i64, budget_ms: i64) -> i64 { 75 if pf_valid(r) == 0 { pf_init(r) } 76 var m: i64 = ms 77 if m < 0 { m = 0 } 78 r[PF_HDR + r[PF_H_HEAD]] = m 79 r[PF_H_HEAD] = (r[PF_H_HEAD] + 1) % PF_N 80 if r[PF_H_COUNT] < PF_N { r[PF_H_COUNT] = r[PF_H_COUNT] + 1 } 81 r[PF_H_TOTAL] = r[PF_H_TOTAL] + 1 82 if m > r[PF_H_WORST] { r[PF_H_WORST] = m } 83 r[PF_H_SUM] = r[PF_H_SUM] + m 84 r[PF_H_BUDGET] = budget_ms 85 if budget_ms > 0 { if m > budget_ms { r[PF_H_OVER] = r[PF_H_OVER] + 1 } } 86 return r[PF_H_COUNT] 87} 88func pf_n(r: *i64) -> i64 { return r[PF_H_COUNT] } 89func pf_total(r: *i64) -> i64 { return r[PF_H_TOTAL] } 90func pf_worst_ever(r: *i64) -> i64 { if r[PF_H_TOTAL] == 0 { return PF_UNOBSERVED } return r[PF_H_WORST] } 91func pf_over(r: *i64) -> i64 { return r[PF_H_OVER] } 92func pf_budget(r: *i64) -> i64 { return r[PF_H_BUDGET] } 93// mean over everything ever pushed, in tenths of a ms so the HUD can print one decimal without floats 94func pf_mean_x10(r: *i64) -> i64 { if r[PF_H_TOTAL] == 0 { return PF_UNOBSERVED } return r[PF_H_SUM]*10 / r[PF_H_TOTAL] } 95// the i-th OLDEST sample in the window (0 = oldest), PF_UNOBSERVED past the count 96func pf_sample(r: *i64, i: i64) -> i64 { 97 let n: i64 = r[PF_H_COUNT] 98 if i < 0 { return PF_UNOBSERVED } 99 if i >= n { return PF_UNOBSERVED } 100 var start: i64 = r[PF_H_HEAD] - n 101 if start < 0 { start = start + PF_N } 102 return r[PF_HDR + (start + i) % PF_N] 103} 104// histogram of the window into the tail of the region (rebuilt per query: PF_N adds, PF_HBINS clears) 105func pf_hist(r: *i64) -> i64 { 106 let h: i64 = PF_HDR + PF_N 107 var b: i64 = 0 108 while b < PF_HBINS { r[h + b] = 0; b = b + 1 } 109 let n: i64 = r[PF_H_COUNT] 110 var i: i64 = 0 111 while i < n { 112 var v: i64 = r[PF_HDR + i] 113 if v > PF_CLAMP_MS { v = PF_CLAMP_MS } 114 if v >= 0 { if v <= PF_CLAMP_MS { r[h + v] = r[h + v] + 1 } } 115 i = i + 1 116 } 117 return n 118} 119// Only queried when the requested rank lands in the overflow bin. Search values, not milliseconds, 120// so a long suspend interval never implies a loop proportional to the pause duration. 121func pf_tail_rank(r: *i64, rank: i64) -> i64 { 122 let n: i64 = r[PF_H_COUNT] 123 var lo: i64 = PF_CLAMP_MS 124 var hi: i64 = lo 125 var i: i64 = 0 126 while i < n { if r[PF_HDR+i] > hi { hi = r[PF_HDR+i] }; i = i + 1 } 127 while lo < hi { 128 let mid: i64 = lo + (hi-lo)/2 129 var below: i64 = 0 130 i = 0 131 while i < n { if r[PF_HDR+i] <= mid { below = below + 1 }; i = i + 1 } 132 if below >= rank { hi = mid } else { lo = mid + 1 } 133 } 134 return lo 135} 136// nearest-rank percentile of the window: the smallest ms value whose cumulative count reaches 137// ceil(permil x n / 1000). p50 of 1..100 is 50, p99 is 99, p100 is 100. 138func pf_pct(r: *i64, permil: i64) -> i64 { 139 let n: i64 = pf_hist(r) 140 if n <= 0 { return PF_UNOBSERVED } 141 var rank: i64 = (permil * n + PF_PERMIL - 1) / PF_PERMIL 142 if rank < 1 { rank = 1 } 143 if rank > n { rank = n } 144 let h: i64 = PF_HDR + PF_N 145 var cum: i64 = 0 146 var b: i64 = 0 147 while b < PF_HBINS { 148 cum = cum + r[h + b] 149 if cum >= rank { if b == PF_CLAMP_MS { return pf_tail_rank(r, rank) }; return b } 150 b = b + 1 151 } 152 return PF_CLAMP_MS 153} 154func pf_worst(r: *i64) -> i64 { return pf_pct(r, PF_PERMIL) } 155// frames in the window above PF_SPIKE_MULT x the window median -- the jank count 156func pf_spikes(r: *i64) -> i64 { 157 let n: i64 = r[PF_H_COUNT] 158 if n <= 0 { return PF_UNOBSERVED } 159 let med: i64 = pf_pct(r, 500) 160 let bar: i64 = med * PF_SPIKE_MULT 161 var c: i64 = 0 162 var i: i64 = 0 163 while i < n { 164 if r[PF_HDR + i] > bar { c = c + 1 } 165 i = i + 1 166 } 167 return c 168} 169// ABSOLUTE long-frame axis, beside the RELATIVE jank multiple above. The W3C Long Animation Frames standard 170// defines a long frame as one whose duration reaches 50 milliseconds and discards timing below it (ref 171// ge-loaf on the gameengine board); the field judges a web page on that absolute bar regardless of our own 172// budget, so the window counts it in the same units. UNOBSERVED on an empty window, like every statistic here. 173const PF_LONG_FRAME_MS: i64 = 50 174func pf_long(r: *i64) -> i64 { 175 let n: i64 = r[PF_H_COUNT] 176 if n <= 0 { return PF_UNOBSERVED } 177 var c: i64 = 0 178 var i: i64 = 0 179 while i < n { 180 if r[PF_HDR + i] >= PF_LONG_FRAME_MS { c = c + 1 } 181 i = i + 1 182 } 183 return c 184} 185func pf_spikes_permil(r: *i64) -> i64 { 186 let n: i64 = r[PF_H_COUNT] 187 if n <= 0 { return PF_UNOBSERVED } 188 return pf_spikes(r) * PF_PERMIL / n 189} 190// ---- the budget half composes the incumbent ruler ---- 191func pf_budget_ms(fps: i64) -> i64 { if fps <= 0 { return PF_UNOBSERVED } return fb_budget_us(fps) / PF_US_PER_MS } 192func pf_fits(ms: i64, budget_ms: i64) -> i64 { return fb_fits(ms * PF_US_PER_MS, budget_ms * PF_US_PER_MS) } 193 194// ---- THE PACING REFEREE (GE15): three-state, bands as permil OF THE BUDGET so one conf serves every display ---- 195// p50 <= budget x p50_permil/1000, p95 <= budget x p95_permil/1000, p99 <= budget x p99_permil/1000, 196// jank permil <= jank_permil_max. ABSTAINS below min_n samples: a pacing check that passes on zero frames is 197// the passes-on-the-empty-set defect wearing a deploy costume. Returns PF_GREEN / PF_RED / PF_ABSTAIN; the 198// failing conjunct is written into why[0] (1 p50, 2 p95, 3 p99, 4 jank, 0 none) so the message can name it. 199func pf_referee(r: *i64, budget_ms: i64, min_n: i64, p50_permil: i64, p95_permil: i64, p99_permil: i64, jank_permil_max: i64, why: *i64) -> i64 { 200 why[0] = 0 201 let n: i64 = r[PF_H_COUNT] 202 if n < min_n { return PF_ABSTAIN } 203 if budget_ms <= 0 { return PF_ABSTAIN } 204 if pf_pct(r, 500) * PF_PERMIL > budget_ms * p50_permil { why[0] = 1; return PF_RED } 205 if pf_pct(r, 950) * PF_PERMIL > budget_ms * p95_permil { why[0] = 2; return PF_RED } 206 if pf_pct(r, 990) * PF_PERMIL > budget_ms * p99_permil { why[0] = 3; return PF_RED } 207 if pf_spikes_permil(r) > jank_permil_max { why[0] = 4; return PF_RED } 208 return PF_GREEN 209}