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}