code wiki / (root) / nx_gauge_lib.nx

nx_gauge_lib.nx source

↩ module page · 161 lines · 7661 B

1// nx_gauge_lib.nx -- CE9 (codeeffectiveness sm_gauge_alarm): THE ONE RULER for a GAUGE HEARTBEAT -- written by the beat 2// that measures a gauge, read by the /compare emitter that renders it, and driven in-process by its gate, so the writer 3// and the reader cannot disagree about what STALE, BLIND or ABSENT mean: one classifier, one file shape, one renderer. 4// 5// THE STAMP is the estate's dead-man heartbeat shape -- `ts=<epoch>` FIRST, the way nx_http_probe_lib.hp_write_stamp 6// writes it and nx_cron_watch reads it -- followed by the gauge fields on the same line: 7// ts=<epoch> gauge=<n> axes_known=<k> axes_total=<t> cadence_s=<c> 8// Truncate-written in ONE sys_write: a reader that catches a partial write sees a shorter ts, i.e. an OLDER stamp, which 9// fails in the safe direction. hp_write_stamp is not composed only because it carries ts alone and a gauge needs its 10// axes beside it; the SHAPE is identical so the cron dead-man's switch can watch this file with no change. 11// 12// STATES (ga_state), decided in this order so the most alarming truth wins: 13// ABSENT no readable stamp: nothing has measured, or the writer died before its first beat 14// STALE age > max_age, or age < 0 (a future stamp is clock skew: UNOBSERVABLE, never fresh) 15// BLIND axes_known < axes_total: an axis abstained, and the number beside it acquits nothing 16// FRESH inside max_age with every axis observed 17// max_age is DERIVED from the writer's cadence (GA_MISSED_BEATS beats): one missed beat is tolerated, the second is 18// blindness. ga_render prints the gauge VALUE only when the state is FRESH -- a stale gauge reads STALE on the page and 19// never as zero, which is the rung's accept rule. A stamp whose cadence field is absent judges STALE by construction. 20// license_tier: ORIGINAL No hw writes. 21import "nx_sovjson_lib.nx" 22import "nx_syscalls.nx" 23 24const GA_STAMP_PROD: *u8 = "knowledge/status/seat_gauge.stamp" 25const GA_LINE: i64 = 512 26const GA_MODE644: i64 = 420 27const GA_NL: i64 = 10 28const GA_SP: i64 = 32 29const GA_EQ: i64 = 61 30const GA_D0: i64 = 48 31const GA_D9: i64 = 57 32const GA_WORD: i64 = 8 33const GA_MISSED_BEATS: i64 = 2 // derived bar: the second missed beat is blindness, the first is tolerated 34// states 35const GA_FRESH: i64 = 0 36const GA_STALE: i64 = 1 37const GA_BLIND: i64 = 2 38const GA_ABSENT: i64 = 3 39// read-back slots (every slot is -1 until a field is parsed, so an absent field can never read as zero) 40const GA_F_TS: i64 = 0 41const GA_F_GAUGE: i64 = 1 42const GA_F_KNOWN: i64 = 2 43const GA_F_TOTAL: i64 = 3 44const GA_F_CADENCE: i64 = 4 45const GA_F_SLOTS: i64 = 8 46 47func ga_max_age(cadence_s: i64) -> i64 { return cadence_s * GA_MISSED_BEATS } 48 49// `key=` at the line start or after a space; the unsigned integer after it, or -1 when the key or its digits are absent 50func ga_field(q: *u8, n: i64, key: *u8) -> i64 { 51 let kl: i64 = sj_vlen(key) 52 var i: i64 = 0 53 while i + kl < n { 54 var at: i64 = 0 55 if i == 0 { at = 1 } else { if q[i - 1] == (GA_SP as u8) { at = 1 } } 56 if at == 1 { 57 var m: i64 = 0 58 var eq: i64 = 1 59 while m < kl { if q[i + m] != key[m] { eq = 0; m = kl } else { m = m + 1 } } 60 if eq == 1 { if q[i + kl] == (GA_EQ as u8) { 61 let s: i64 = i + kl + 1 62 var e: i64 = s 63 var go: i64 = 1 64 while go == 1 { 65 if e >= n { go = 0 } else { 66 let c: i64 = q[e] as i64 67 if c < GA_D0 { go = 0 } else { if c > GA_D9 { go = 0 } else { e = e + 1 } } 68 } 69 } 70 if e == s { return 0 - 1 } 71 return sj_atoi_span(q, s, e) 72 } } 73 } 74 i = i + 1 75 } 76 return 0 - 1 77} 78 79// one line, one write; returns the bytes written or -1 (a stamper that cannot write is as blind as one that never ran) 80func ga_stamp_write(path: *u8, now: i64, gauge: i64, known: i64, total: i64, cadence_s: i64) -> i64 { 81 let row: *u8 = sys_mmap(GA_LINE) 82 var o: i64 = sj_cat(row, 0, "ts=" as *u8); o = sj_catn(row, o, now) 83 o = sj_cat(row, o, " gauge=" as *u8); o = sj_catn(row, o, gauge) 84 o = sj_cat(row, o, " axes_known=" as *u8); o = sj_catn(row, o, known) 85 o = sj_cat(row, o, " axes_total=" as *u8); o = sj_catn(row, o, total) 86 o = sj_cat(row, o, " cadence_s=" as *u8); o = sj_catn(row, o, cadence_s) 87 row[o] = GA_NL as u8; o = o + 1 88 let fd: i64 = sys_openat_wr(path, GA_MODE644) 89 if fd < 0 { return 0 - 1 } 90 let wr: i64 = sys_write(fd, row, o) 91 sys_close(fd) 92 if wr != o { return 0 - 1 } 93 return wr 94} 95 96// fills f[GA_F_*]; returns 1 when a positive ts was read, 0 otherwise (absent and malformed both judge ABSENT) 97func ga_stamp_read(path: *u8, f: *i64) -> i64 { 98 var z: i64 = 0 99 while z < GA_F_SLOTS { f[z] = 0 - 1; z = z + 1 } 100 let ln: *i64 = sys_mmap(16) as *i64 101 let b: *u8 = sys_read_file(path, ln) 102 if (b as i64) == 0 { return 0 } 103 if ln[0] <= 0 { return 0 } 104 f[GA_F_TS] = ga_field(b, ln[0], "ts" as *u8) 105 f[GA_F_GAUGE] = ga_field(b, ln[0], "gauge" as *u8) 106 f[GA_F_KNOWN] = ga_field(b, ln[0], "axes_known" as *u8) 107 f[GA_F_TOTAL] = ga_field(b, ln[0], "axes_total" as *u8) 108 f[GA_F_CADENCE] = ga_field(b, ln[0], "cadence_s" as *u8) 109 if f[GA_F_TS] <= 0 { return 0 } 110 return 1 111} 112 113// THE CLASSIFIER: ABSENT > STALE > BLIND > FRESH 114func ga_state(now: i64, ts: i64, max_age: i64, known: i64, total: i64) -> i64 { 115 if ts <= 0 { return GA_ABSENT } 116 let age: i64 = now - ts 117 if age < 0 { return GA_STALE } 118 if age > max_age { return GA_STALE } 119 if known < total { return GA_BLIND } 120 return GA_FRESH 121} 122func ga_state_name(st: i64) -> *u8 { 123 if st == GA_FRESH { return "FRESH" as *u8 } 124 if st == GA_STALE { return "STALE" as *u8 } 125 if st == GA_BLIND { return "BLIND" as *u8 } 126 return "ABSENT" as *u8 127} 128 129// THE RENDERER: the gauge value appears ONLY when FRESH; every other state names itself and its reason 130func ga_render(d: *u8, p0: i64, st: i64, age: i64, max_age: i64, gauge: i64, known: i64, total: i64) -> i64 { 131 var p: i64 = sj_cat(d, p0, ga_state_name(st)) 132 if st == GA_FRESH { 133 p = sj_cat(d, p, " gauge=" as *u8); p = sj_catn(d, p, gauge) 134 p = sj_cat(d, p, " axes=" as *u8); p = sj_catn(d, p, known); p = sj_cat(d, p, "/" as *u8); p = sj_catn(d, p, total) 135 p = sj_cat(d, p, " age_s=" as *u8); p = sj_catn(d, p, age) 136 return p 137 } 138 if st == GA_STALE { 139 p = sj_cat(d, p, " age_s=" as *u8); p = sj_catn(d, p, age) 140 p = sj_cat(d, p, " max_age_s=" as *u8); p = sj_catn(d, p, max_age) 141 p = sj_cat(d, p, " (the last reading is history, not a level: the gauge value is withheld)" as *u8) 142 return p 143 } 144 if st == GA_BLIND { 145 p = sj_cat(d, p, " axes=" as *u8); p = sj_catn(d, p, known); p = sj_cat(d, p, " of " as *u8); p = sj_catn(d, p, total) 146 p = sj_cat(d, p, " (an axis abstained: the reading cannot acquit, the gauge value is withheld)" as *u8) 147 return p 148 } 149 p = sj_cat(d, p, " (no stamp: nothing has measured this gauge)" as *u8) 150 return p 151} 152 153// the whole decision from a path: fills f, returns the state. cadence_s <= 0 means read it from the stamp; a stamp 154// without one judges STALE by construction (max_age of a negative cadence is negative, so every age exceeds it). 155func ga_judge(path: *u8, now: i64, cadence_s: i64, f: *i64) -> i64 { 156 let ok: i64 = ga_stamp_read(path, f) 157 if ok == 0 { return GA_ABSENT } 158 var cad: i64 = cadence_s 159 if cad <= 0 { cad = f[GA_F_CADENCE] } 160 return ga_state(now, f[GA_F_TS], ga_max_age(cad), f[GA_F_KNOWN], f[GA_F_TOTAL]) 161}