nx_site_monitor.nx source
↩ module page · 148 lines · 8341 B
1// nx_site_monitor.nx -- UPTIME + ALERTING (closes hosting_research gap #2 "uptime-alerting", 3/0 CONFIRMED:
2// best-in-class hosts alert on downtime + track uptime%; Nishi probes but never RECORDS or alerts on a
3// transition, so an outage is silent). Composes nx_health_probe: on each poll it classifies the up/down
4// TRANSITION per daemon (sm_alert) -- went-DOWN, went-HUNG (distinct + actionably different: HUNG needs a
5// kill, DOWN needs a start), or RECOVERED -- and tracks cumulative uptime. State persists in a TSV snapshot
6// so transitions are detected across runs; alerts append to a durable log the status page can surface.
7//
8// module: nishi-core.observability.site_monitor capability: DAEMON_ROBUSTNESS / observability
9import "nx_health_probe.nx" // hp_probe / HP_SERVING / HP_HUNG / HP_REFUSED
10const SM_MAGIC_262144: i64 = 262144
11const SM_MAGIC_262143: i64 = 262143
12const SM_MAGIC_8443: i64 = 8443
13const SM_MAGIC_7443: i64 = 7443
14const SM_MAGIC_8446: i64 = 8446
15const SM_MAGIC_8447: i64 = 8447
16const SM_MAGIC_8097: i64 = 8097
17const SM_MAGIC_18090: i64 = 18090
18
19const SM_NONE: i64 = 0
20const SM_WENT_DOWN: i64 = 1 // serving (or unknown-bad) -> refused/badresp
21const SM_WENT_HUNG: i64 = 2 // -> hung (up, not serving): the actionably-distinct failure
22const SM_RECOVERED: i64 = 3 // not-serving -> serving
23const SM_STATE: *u8 = "/mnt/c/Users/elder/nishi-core/nxc2/knowledge/status/uptime_state.tsv"
24const SM_LOG: *u8 = "/mnt/c/Users/elder/nishi-core/nxc2/knowledge/status/uptime_alerts.tsv"
25
26// classify the transition from prior verdict old_v to new verdict new_v (both HP_*; old_v==0 = never-seen).
27// First-ever sighting that is already SERVING is the baseline (no alert); first sighting that is down/hung
28// IS an alert (you want to know it came up broken). Any genuine state change is alerted, by the NEW state.
29func sm_alert(old_v: i64, new_v: i64) -> i64 {
30 if old_v == new_v { return SM_NONE }
31 if new_v == HP_SERVING {
32 if old_v == 0 { return SM_NONE } // first sight, already up -> baseline
33 return SM_RECOVERED
34 }
35 if new_v == HP_HUNG { return SM_WENT_HUNG }
36 return SM_WENT_DOWN // HP_REFUSED or HP_BADRESP
37}
38func sm_name(a: i64) -> *u8 {
39 if a == SM_WENT_DOWN { return "WENT_DOWN" as *u8 }
40 if a == SM_WENT_HUNG { return "WENT_HUNG" as *u8 }
41 if a == SM_RECOVERED { return "RECOVERED" as *u8 }
42 return "none" as *u8
43}
44func sm_uptime_permil(up: i64, total: i64) -> i64 { if total <= 0 { return 0 } return (up * 1000) / total }
45
46func sm_p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
47func sm_n(v: i64) -> i64 { let t: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m; sys_write(1,"-" as *u8,1)} var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} let b: *u8=sys_mmap(28); var i: i64=0; while i<k{b[i]=t[k-1-i];i=i+1} sys_write(1,b,k); return 0 }
48// write integer v into buf at off, return new off (shared by state + alert emitters)
49func sm_wri(buf: *u8, off: i64, v: i64) -> i64 {
50 let t: *u8 = sys_mmap(28); var m: i64 = v; var k: i64 = 0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1}
51 var o: i64 = off; var q: i64 = k-1; while q>=0 { buf[o]=t[q]; o=o+1; q=q-1 } return o
52}
53
54func sm_read(path: *u8, buf: *u8, cap: i64) -> i64 {
55 let fd: i64 = sys_openat_rd(path); if fd < 0 { return 0 }
56 var off: i64=0; var go: i64=1
57 while go==1 { if off>=cap {go=0} else { let r: i64=sys_read(fd, ((buf as i64)+off) as *u8, cap-off); if r<=0 {go=0} else {off=off+r} } }
58 sys_close(fd); return off
59}
60func sm_streq_n(buf: *u8, off: i64, end: i64, s: *u8) -> i64 { // does buf[off..tab] == s (NUL-terminated)?
61 var i: i64 = 0
62 while s[i] != (0 as u8) { if off+i >= end { return 0 } if buf[off+i] != s[i] { return 0 } i = i + 1 }
63 if off+i < end { if buf[off+i] != (9 as u8) { return 0 } } // must end at a tab
64 return 1
65}
66func sm_field_i(buf: *u8, off: i64, end: i64, f: i64) -> i64 { // integer value of the f-th tab field
67 var cur: i64 = 0; var i: i64 = off
68 while cur < f { if i >= end { return 0 } if buf[i] == (9 as u8) { cur = cur + 1 } i = i + 1 }
69 var v: i64 = 0
70 while i < end { let c: u8 = buf[i]; if c < (48 as u8) { i = end } else { if c > (57 as u8) { i = end } else { v = v*10 + ((c - (48 as u8)) as i64); i = i + 1 } } }
71 return v
72}
73// find daemon's prior line in the state buffer -> *lv/*up/*tot (0,0,0 if absent).
74func sm_find(buf: *u8, len: i64, daemon: *u8, lv: *i64, up: *i64, tot: *i64) -> i64 {
75 lv[0]=0; up[0]=0; tot[0]=0
76 var ls: i64 = 0; var i: i64 = 0
77 while i <= len {
78 var atend: i64 = 0
79 if i == len { atend = 1 }
80 if i < len { if buf[i] == (10 as u8) { atend = 1 } }
81 if atend == 1 {
82 if i > ls { if sm_streq_n(buf, ls, i, daemon) == 1 {
83 lv[0]=sm_field_i(buf, ls, i, 1); up[0]=sm_field_i(buf, ls, i, 2); tot[0]=sm_field_i(buf, ls, i, 3); return 1
84 } }
85 ls = i + 1
86 }
87 i = i + 1
88 }
89 return 0
90}
91
92// append a line to a file via read-existing + rewrite (no O_APPEND flag dependency).
93func sm_append(path: *u8, line: *u8, linelen: i64) -> i64 {
94 let cap: i64 = SM_MAGIC_262144; let buf: *u8 = sys_mmap(cap)
95 let n: i64 = sm_read(path, buf, cap - linelen - 1)
96 var i: i64 = 0; while i < linelen { buf[n+i] = line[i]; i = i + 1 }
97 let fd: i64 = sys_openat_wr(path, 0x1a4); if fd < 0 { return 0 }
98 sys_write(fd, buf, n + linelen); sys_close(fd); return 1
99}
100
101// process one daemon: probe -> classify -> alert(append) -> return updated up/tot via boxes; emit state line.
102func sm_one(daemon: *u8, port: i64, prior: *u8, plen: i64, statebuf: *u8, statep: *i64) -> i64 {
103 let lv: *i64 = sys_mmap(16) as *i64; let up: *i64 = sys_mmap(16) as *i64; let tot: *i64 = sys_mmap(16) as *i64
104 sm_find(prior, plen, daemon, lv, up, tot)
105 let v: i64 = hp_probe(port, 3)
106 let a: i64 = sm_alert(lv[0], v)
107 up[0] = up[0] + (v == HP_SERVING)
108 tot[0] = tot[0] + 1
109 // emit "daemon\tverdict\tup\ttotal\n" into the new state snapshot
110 var o: i64 = statep[0]
111 var di: i64 = 0; while daemon[di] != (0 as u8) { statebuf[o] = daemon[di]; o = o + 1; di = di + 1 }
112 statebuf[o] = 9 as u8; o = o + 1
113 let vb: *u8 = sys_mmap(8); var vk: i64 = 0; var vm: i64 = v; if vm==0{vb[0]=48 as u8;vk=1} while vm>0{vb[vk]=(48+(vm%10)) as u8;vm=vm/10;vk=vk+1}
114 var q: i64 = vk-1; while q>=0 { statebuf[o]=vb[q]; o=o+1; q=q-1 }
115 statebuf[o]=9 as u8; o=o+1
116 o = sm_wri(statebuf, o, up[0]); statebuf[o]=9 as u8; o=o+1
117 o = sm_wri(statebuf, o, tot[0]); statebuf[o]=10 as u8; o=o+1
118 statep[0] = o
119 // report + alert
120 sm_p(" " as *u8); sm_p(daemon); sm_p(" :" as *u8); sm_n(port); sm_p(" -> " as *u8); sm_p(hp_name(v) as *u8)
121 sm_p(" uptime=" as *u8); sm_n(sm_uptime_permil(up[0], tot[0])); sm_p("permil" as *u8)
122 if a != SM_NONE {
123 sm_p(" [ALERT " as *u8); sm_p(sm_name(a) as *u8); sm_p("]" as *u8)
124 let line: *u8 = sys_mmap(256); var li: i64 = 0
125 li = sm_wri(line, li, sys_now_realtime_sec()); line[li]=9 as u8; li=li+1
126 var ci: i64 = 0; while daemon[ci] != (0 as u8) { line[li]=daemon[ci]; li=li+1; ci=ci+1 }
127 line[li]=9 as u8; li=li+1
128 let an: *u8 = sm_name(a) as *u8; var ai: i64 = 0; while an[ai] != (0 as u8) { line[li]=an[ai]; li=li+1; ai=ai+1 }
129 line[li]=10 as u8; li=li+1
130 sm_append(SM_LOG, line, li)
131 }
132 sm_p("\n" as *u8)
133 return 0
134}
135
136func main() -> i64 {
137 sm_p("=== NISHI SITE MONITOR (uptime + alerting) ===\n" as *u8)
138 let prior: *u8 = sys_mmap(SM_MAGIC_262144); let plen: i64 = sm_read(SM_STATE, prior, SM_MAGIC_262143)
139 let sb: *u8 = sys_mmap(SM_MAGIC_262144); let sp: *i64 = sys_mmap(16) as *i64; sp[0] = 0
140 sm_one("sites-v2" as *u8, SM_MAGIC_8443, prior, plen, sb, sp)
141 sm_one("sni-router" as *u8,SM_MAGIC_7443, prior, plen, sb, sp)
142 sm_one("vroom" as *u8, SM_MAGIC_8446, prior, plen, sb, sp)
143 sm_one("translate" as *u8, SM_MAGIC_8447, prior, plen, sb, sp)
144 sm_one("torrent" as *u8, SM_MAGIC_8097, prior, plen, sb, sp)
145 sm_one("gallery" as *u8, SM_MAGIC_18090, prior, plen, sb, sp)
146 let fd: i64 = sys_openat_wr(SM_STATE, 0x1a4); if fd >= 0 { sys_write(fd, sb, sp[0]); sys_close(fd) }
147 sys_exit(0); return 0
148}