code wiki / _hdl_build / nx_apm_collect.nx
nx_apm_collect.nx source
↩ module page · 349 lines · 21145 B
1// nx_apm_collect.nx -- SOVEREIGN NAS-side INTERNAL-HTTP concurrency collector (the hardware-up APM layer).
2//
3// WHY (the gap nx_apm_sweep cannot fill): nx_apm_sweep probes nishifamily.com over the FULL public TLS
4// chain from the workstation. That is the right "what a browser sees" view, but each probe pays an ~800ms
5// Nishi TLS handshake whose cost is CPU-bound on this 21-workstream-contended workstation, so conc>1
6// latency is handshake-noise, not server truth -> it cannot cleanly measure a daemon's CONCURRENCY health.
7// This organ runs ON THE NAS (where the daemons live) and probes their plaintext loopback ports DIRECTLY
8// over HTTP/1.1 with "Connection: close" -- no TLS, no contention -> the latency it measures IS the daemon.
9//
10// It fires `conc` SIMULTANEOUS cold probes per target (= conc browser tabs), with a per-probe SO_RCVTIMEO
11// backstop so a truly-hung socket can never wedge the collector forever (rule #14, graceful degradation).
12// It distinguishes GREEN / AMBER(stall>2s) / RED(timeout|err) / DOWN(connection refused) -- the last is the
13// liar-kill: a daemon that is ABSENT must never read as healthy. Targets are DATA-DRIVEN (rule #11) from
14// knowledge/status/apm_targets.tsv; results append to a durable time-series knowledge/status/apm_collect.tsv.
15//
16// Single responsibility (rule #9): HTTP latency/concurrency only. Per-daemon CPU/mem is nx_perf_sentinel's
17// job already; the dashboard rung joins the two time-series. Proven by nx_apm_collect_gate (mock daemons).
18//
19// usage: nx_apm_collect (reads knowledge/status/apm_targets.tsv)
20// license_tier: ORIGINAL
21import "nx_syscalls.nx"
22import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host
23const AC_MAGIC_65536: i64 = 65536
24const AC_MAGIC_1024: i64 = 1024
25
26const AC_STALL_MS: i64 = 2000
27const AC_TIMEOUT_S: i64 = 22 // > the measured ~20s :8791 wedge, so we still capture it
28const AC_TARGETS: *u8 = "knowledge/status/apm_targets.tsv"
29const AC_METRICS: *u8 = "knowledge/status/apm_collect.tsv"
30
31// per-probe verdicts
32const AC_V_OK: i64 = 0
33const AC_V_SOCK: i64 = 1
34const AC_V_DOWN: i64 = 2 // connect refused -> daemon absent
35const AC_V_SEND: i64 = 3
36const AC_V_TIMEOUT: i64 = 4 // no bytes before SO_RCVTIMEO
37
38// per-target result codes (returned by ac_burst; the gate asserts on these)
39const AC_R_GREEN: i64 = 0
40const AC_R_AMBER: i64 = 1
41const AC_R_RED: i64 = 2
42const AC_R_DOWN: i64 = 3
43
44// ===== leaf helpers (decimal/string/io) ==========================
45func ac_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
46func ac_w(s: *u8) -> i64 { return sys_write(1, s, ac_slen(s)) }
47func ac_atoi(s: *u8) -> i64 {
48 var v: i64 = 0; var i: i64 = 0
49 while s[i] != (0 as u8) { let c: i64 = s[i] as i64; if c >= 48 { if c <= 57 { v = v*10 + (c-48) } } i = i + 1 }
50 return v
51}
52// append decimal v into buf at off; returns new off
53func ac_n(buf: *u8, off: i64, v: i64) -> i64 {
54 var o: i64 = off; var m: i64 = v
55 if m < 0 { buf[o] = 45 as u8; o = o + 1; m = 0 - m }
56 let t: *u8 = sys_mmap(24); var k: i64 = 0
57 if m == 0 { t[0] = 48 as u8; k = 1 }
58 while m > 0 { t[k] = (48 + (m - (m/10)*10)) as u8; m = m / 10; k = k + 1 }
59 var i: i64 = 0; while i < k { buf[o + i] = t[k - 1 - i]; i = i + 1 }
60 return o + k
61}
62// append C-string s into buf at off; returns new off
63func ac_s(buf: *u8, off: i64, s: *u8) -> i64 { var o: i64 = off; var i: i64 = 0; while s[i] != (0 as u8) { buf[o] = s[i]; o = o + 1; i = i + 1 } return o }
64func ac_wn(v: i64) -> i64 { let b: *u8 = sys_mmap(24); let e: i64 = ac_n(b, 0, v); return sys_write(1, b, e) }
65func ac_wpad(v: i64, w: i64) -> i64 {
66 let b: *u8 = sys_mmap(24); let e: i64 = ac_n(b, 0, v)
67 var pad: i64 = w - e; while pad > 0 { sys_write(1, " " as *u8, 1); pad = pad - 1 }
68 return sys_write(1, b, e)
69}
70func ac_sort(a: *i64, n: i64) -> i64 {
71 var i: i64 = 1
72 while i < n {
73 let kv: i64 = a[i]; var j: i64 = i - 1; var go: i64 = 1
74 while go == 1 { if j >= 0 { if a[j] > kv { a[j+1] = a[j]; j = j - 1 } else { go = 0 } } else { go = 0 } }
75 a[j+1] = kv; i = i + 1
76 }
77 return 0
78}
79// 16-byte sockaddr_in for a.b.c.d:port
80func ac_addr(out: *u8, a: i64, b: i64, c: i64, d: i64, port: i64) -> i64 {
81 out[0] = 2 as u8; out[1] = 0 as u8
82 out[2] = ((port >> 8) & 0xff) as u8; out[3] = (port & 0xff) as u8
83 out[4] = a as u8; out[5] = b as u8; out[6] = c as u8; out[7] = d as u8
84 var i: i64 = 8; while i < 16 { out[i] = 0 as u8; i = i + 1 }
85 return 16
86}
87// "GET <path> HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"
88func ac_build_get(req: *u8, path: *u8) -> i64 {
89 var p: i64 = ac_s(req, 0, "GET " as *u8)
90 p = ac_s(req, p, path)
91 p = ac_s(req, p, " HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n" as *u8)
92 return p
93}
94// Small bounded file read (NOT sys_read_file -- that mmaps 4 GiB/call and would balloon parent VA).
95func ac_read_small(path: *u8, buf: *u8, cap: i64) -> i64 {
96 let fd: i64 = sys_openat_rd(path)
97 if fd < 0 { return 0 - 1 }
98 var off: i64 = 0; var go: i64 = 1
99 while go == 1 {
100 if off >= cap { go = 0 }
101 else { let r: i64 = sys_read(fd, buf + off, cap - off); if r <= 0 { go = 0 } else { off = off + r } }
102 }
103 sys_close(fd)
104 return off
105}
106// copy one tab/newline-delimited field [pos..) into out; stop[0]=0 tab,1 newline,2 eof; returns next pos.
107func ac_field(buf: *u8, n: i64, pos: i64, out: *u8, outcap: i64, stop: *i64) -> i64 {
108 var i: i64 = pos; var w: i64 = 0; var done: i64 = 0
109 while done == 0 {
110 if i >= n { stop[0] = 2; done = 1 }
111 else { let c: i64 = buf[i] as i64
112 if c == 9 { stop[0] = 0; i = i + 1; done = 1 }
113 else { if c == 10 { stop[0] = 1; i = i + 1; done = 1 }
114 else { if w < outcap - 1 { out[w] = buf[i]; w = w + 1 } i = i + 1 } } }
115 }
116 out[w] = 0 as u8
117 return i
118}
119
120// ===== one internal probe ========================================
121// connect 127.0.0.1:port, GET path, drain to close (bounded by SO_RCVTIMEO=timeout_secs).
122// LATENCY = time to FIRST byte (ttfb) -- the real server-think time, IMMUNE to keep-alive/idle-close
123// artifacts (a daemon that sends fast then holds the socket open shows fast ttfb + slow total). Drains to
124// CLOSE (never closes early) so it can't SIGPIPE-kill a SIGPIPE-naive daemon.
125// out[0]=bytes, out[1]=verdict, out[2]=total ms (to close). Returns ttfb ms (or total if NO byte = hang).
126func ac_probe(port: i64, path: *u8, timeout_secs: i64, ip: i64, out: *i64) -> i64 {
127 out[0] = 0; out[1] = AC_V_OK; out[2] = 0
128 let t0: i64 = sys_now_ms()
129 let fd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0)
130 if fd < 0 { out[1] = AC_V_SOCK; return 0 }
131 sys_set_socket_timeout(fd, timeout_secs) // backstop: a hung daemon can't wedge us forever
132 let sa: *u8 = sys_mmap(16); ac_addr(sa, (ip>>24)&0xff, (ip>>16)&0xff, (ip>>8)&0xff, ip&0xff, port) // ip packed a.b.c.d (0x7f000001=loopback)
133 if nx_connect_bounded(fd, sa, 16, NX_CONN_DEFAULT_MS) < 0 { sys_close(fd); let td: i64 = sys_now_ms() - t0; out[1] = AC_V_DOWN; out[2] = td; return td }
134 let req: *u8 = sys_mmap(512); let rl: i64 = ac_build_get(req, path)
135 let wr: i64 = sys_write(fd, req, rl)
136 if wr != rl { sys_close(fd); let ts: i64 = sys_now_ms() - t0; out[1] = AC_V_SEND; out[2] = ts; return ts }
137 let buf: *u8 = sys_mmap(AC_MAGIC_65536)
138 var total: i64 = 0; var ttfb: i64 = 0 - 1; var keep: i64 = 1
139 while keep == 1 {
140 let r: i64 = sys_read(fd, buf, AC_MAGIC_65536) // reuse buffer, drain to close (SIGPIPE-safe)
141 if r <= 0 { keep = 0 } else { if ttfb < 0 { ttfb = sys_now_ms() - t0 } total = total + r }
142 }
143 sys_close(fd)
144 let tend: i64 = sys_now_ms() - t0
145 out[0] = total; out[2] = tend
146 if ttfb < 0 { out[1] = AC_V_TIMEOUT; return tend } // NO first byte within timeout = real hang
147 return ttfb // latency = time to FIRST byte
148}
149
150// ===== one concurrency burst per target ==========================
151// fire `conc` simultaneous probes; aggregate on TTFB; print a row; append a time-series row.
152// VERDICT is ttfb-based (real server latency); `slowclose` (fast first byte but slow close = keep-alive/
153// idle artifact) is reported as INFO and NEVER causes AMBER/RED -- that is the artifact-immunity.
154// out_stats: [p50,p95,max,oks,stalls,timeouts,downs,min,maxtotal,slowclose]. Returns the AC_R_* code.
155// ---- SOVEREIGN seg-store (NO tsv): latest-per-label metric record + apm-ids index ----
156func ac_line_eq(buf: *u8, start: i64, end: i64, label: *u8) -> i64 {
157 var i: i64 = start; var j: i64 = 0
158 while i < end { if buf[i] != label[j] { return 0 } i = i + 1; j = j + 1 }
159 if label[j] != (0 as u8) { return 0 }
160 return 1
161}
162func ac_seg_index(label: *u8) -> i64 {
163 let buf: *u8 = sys_mmap(AC_MAGIC_65536); var n: i64 = 0
164 let fd: i64 = sys_openat_rd("knowledge/store/apm-ids" as *u8)
165 if fd >= 0 { n = sys_read(fd, buf, AC_MAGIC_65536); sys_close(fd) }
166 var present: i64 = 0; var ls: i64 = 0; var i: i64 = 0
167 while i <= n { var nl: i64=0; if i>=n {nl=1} else { if buf[i]==(10 as u8){nl=1} } if nl==1 { if i>ls { if ac_line_eq(buf, ls, i, label)==1 { present=1 } } ls=i+1 } i=i+1 }
168 if present == 0 { let afd: i64 = sys_openat_append("knowledge/store/apm-ids" as *u8, 0x1a4); if afd >= 0 { let lb: *u8=sys_mmap(128); var lo: i64=ac_s(lb,0,label); lb[lo]=10 as u8; lo=lo+1; sys_write(afd, lb, lo); sys_close(afd) } }
169 return 0
170}
171func ac_burst(port: i64, path: *u8, label: *u8, conc: i64, timeout_secs: i64, epoch: i64, ip: i64, out_stats: *i64) -> i64 {
172 // SHARED results (MAP_SHARED survives fork) -- [ttfb, bytes, verd, total] per probe. (sys_mmap_shared,
173 // NOT temp files: sys_read_file mmaps 4 GiB/call and ballooned parent VA into fork ENOMEM.)
174 let res: *i64 = sys_mmap_shared(8 * 4 * conc) as *i64
175 var z: i64 = 0; while z < 4 * conc { res[z] = 0 - 1; z = z + 1 }
176 let pids: *i64 = sys_mmap(8 * (conc + 1)) as *i64 // track OUR probe children only
177 var k: i64 = 0
178 while k < conc {
179 let pid: i64 = sys_fork()
180 if pid == 0 {
181 let st: *i64 = sys_mmap(32) as *i64
182 let tt: i64 = ac_probe(port, path, timeout_secs, ip, st)
183 res[k*4] = tt; res[k*4 + 1] = st[0]; res[k*4 + 2] = st[1]; res[k*4 + 3] = st[2]
184 sys_exit(0)
185 }
186 pids[k] = pid
187 k = k + 1
188 }
189 // Reap EXACTLY our probe pids (wait4(-1) would steal a sibling mock/daemon child in a multi-child caller).
190 let stp: *i64 = sys_mmap(16) as *i64; var w: i64 = 0
191 while w < conc { sys_wait4(pids[w], stp, 0); w = w + 1 }
192
193 let ms: *i64 = sys_mmap(8 * (conc + 2)) as *i64
194 var nres: i64 = 0; var oks: i64 = 0; var downs: i64 = 0; var timeouts: i64 = 0; var stalls: i64 = 0; var errs: i64 = 0; var slowclose: i64 = 0; var maxtot: i64 = 0
195 var kk: i64 = 0
196 while kk < conc {
197 let tt: i64 = res[kk*4]; let bytes: i64 = res[kk*4 + 1]; let verd: i64 = res[kk*4 + 2]; let tot: i64 = res[kk*4 + 3]
198 if tt >= 0 { ms[nres] = tt; nres = nres + 1; if tt >= AC_STALL_MS { stalls = stalls + 1 } } // stall = slow FIRST byte
199 if tot > maxtot { maxtot = tot }
200 if verd == AC_V_OK { if bytes > 0 { oks = oks + 1; if tot >= AC_STALL_MS { if tt < AC_STALL_MS { slowclose = slowclose + 1 } } } else { errs = errs + 1 } }
201 else { if verd == AC_V_DOWN { downs = downs + 1 }
202 else { if verd == AC_V_TIMEOUT { timeouts = timeouts + 1 } else { if verd >= 0 { errs = errs + 1 } } } }
203 kk = kk + 1
204 }
205 ac_sort(ms, nres)
206 var mn: i64 = 0; var p50: i64 = 0; var p95: i64 = 0; var mx: i64 = 0
207 if nres > 0 { mn = ms[0]; mx = ms[nres-1]; p50 = ms[nres/2]; var pi: i64 = (nres*95)/100; if pi >= nres { pi = nres - 1 } p95 = ms[pi] }
208
209 // ttfb-based verdict (slowclose is INFO only -> artifact-immune)
210 var code: i64 = AC_R_GREEN
211 if stalls > 0 { code = AC_R_AMBER }
212 if timeouts > 0 { code = AC_R_RED }
213 if errs > 0 { code = AC_R_RED }
214 if downs > 0 { code = AC_R_RED }
215 if downs >= conc { code = AC_R_DOWN }
216
217 out_stats[0] = p50; out_stats[1] = p95; out_stats[2] = mx; out_stats[3] = oks
218 out_stats[4] = stalls; out_stats[5] = timeouts; out_stats[6] = downs; out_stats[7] = mn
219 out_stats[8] = maxtot; out_stats[9] = slowclose
220
221 var vstr: *u8 = "GREEN" as *u8
222 if code == AC_R_AMBER { vstr = "AMBER" as *u8 }
223 if code == AC_R_RED { vstr = "RED " as *u8 }
224 if code == AC_R_DOWN { vstr = "DOWN " as *u8 }
225 ac_w("[" as *u8); ac_w(vstr); ac_w("] " as *u8)
226 let lw: i64 = ac_slen(label); sys_write(1, label, lw); var pad: i64 = 18 - lw; while pad > 0 { sys_write(1, " " as *u8, 1); pad = pad - 1 }
227 ac_w(" :" as *u8); ac_wn(port)
228 ac_w(" ok=" as *u8); ac_wn(oks); ac_w("/" as *u8); ac_wn(conc)
229 ac_w(" ttfb p50=" as *u8); ac_wpad(p50, 5); ac_w(" p95=" as *u8); ac_wpad(p95, 6); ac_w(" max=" as *u8); ac_wpad(mx, 6); ac_w("ms" as *u8)
230 ac_w(" stall=" as *u8); ac_wn(stalls); ac_w(" to=" as *u8); ac_wn(timeouts); ac_w(" down=" as *u8); ac_wn(downs); ac_w(" slowclose=" as *u8); ac_wn(slowclose)
231 ac_w("\n" as *u8)
232
233 // SOVEREIGN seg-store record (latest-per-target, key:value; NO tsv) -- per-key file + apm-ids index
234 sys_mkdir("knowledge/store" as *u8, 0x1ff)
235 let sp: *u8 = sys_mmap(512); var spo: i64 = ac_s(sp, 0, "knowledge/store/apm-" as *u8); spo = ac_s(sp, spo, label); sp[spo]=0 as u8
236 let rb: *u8 = sys_mmap(AC_MAGIC_1024); var ro: i64 = 0
237 ro=ac_s(rb,ro,"label=" as *u8); ro=ac_s(rb,ro,label); rb[ro]=10 as u8; ro=ro+1
238 ro=ac_s(rb,ro,"epoch=" as *u8); ro=ac_n(rb,ro,epoch); rb[ro]=10 as u8; ro=ro+1
239 ro=ac_s(rb,ro,"port=" as *u8); ro=ac_n(rb,ro,port); rb[ro]=10 as u8; ro=ro+1
240 ro=ac_s(rb,ro,"conc=" as *u8); ro=ac_n(rb,ro,conc); rb[ro]=10 as u8; ro=ro+1
241 ro=ac_s(rb,ro,"oks=" as *u8); ro=ac_n(rb,ro,oks); rb[ro]=10 as u8; ro=ro+1
242 ro=ac_s(rb,ro,"ttfb_p50=" as *u8); ro=ac_n(rb,ro,p50); rb[ro]=10 as u8; ro=ro+1
243 ro=ac_s(rb,ro,"ttfb_max=" as *u8); ro=ac_n(rb,ro,mx); rb[ro]=10 as u8; ro=ro+1
244 ro=ac_s(rb,ro,"stalls=" as *u8); ro=ac_n(rb,ro,stalls); rb[ro]=10 as u8; ro=ro+1
245 ro=ac_s(rb,ro,"timeouts=" as *u8); ro=ac_n(rb,ro,timeouts); rb[ro]=10 as u8; ro=ro+1
246 ro=ac_s(rb,ro,"downs=" as *u8); ro=ac_n(rb,ro,downs); rb[ro]=10 as u8; ro=ro+1
247 ro=ac_s(rb,ro,"slowclose=" as *u8); ro=ac_n(rb,ro,slowclose); rb[ro]=10 as u8; ro=ro+1
248 let mfd: i64 = sys_openat_wr(sp, 0x1a4); if mfd >= 0 { sys_write(mfd, rb, ro); sys_close(mfd) }
249 ac_seg_index(label)
250 return code
251}
252
253// parse dotted "a.b.c.d" -> packed i64 (a<<24|b<<16|c<<8|d)
254func ac_parse_ip(s: *u8) -> i64 {
255 let oct: *i64 = sys_mmap(40) as *i64
256 var idx: i64 = 0; var v: i64 = 0; var i: i64 = 0
257 while idx < 4 {
258 let c: i64 = s[i] as i64
259 if c == 46 { oct[idx] = v; idx = idx + 1; v = 0; i = i + 1 }
260 else { if c >= 48 { if c <= 57 { v = v*10 + (c-48); i = i + 1 } else { oct[idx] = v; idx = 4 } } else { oct[idx] = v; idx = 4 } }
261 }
262 oct[3] = v
263 return ((oct[0]&0xff)<<24) | ((oct[1]&0xff)<<16) | ((oct[2]&0xff)<<8) | (oct[3]&0xff)
264}
265
266func ac_starts2(buf: *u8, ls: i64, end: i64, key: *u8) -> i64 { var m: i64=0; while key[m]!=(0 as u8) { if ls+m>=end { return 0 } if buf[ls+m]!=key[m] { return 0 } m=m+1 } return m }
267func ac_kv_int(buf: *u8, n: i64, key: *u8) -> i64 {
268 var ls: i64=0; var i: i64=0
269 while i <= n { var nl: i64=0; if i>=n{nl=1} else { if buf[i]==(10 as u8){nl=1} }
270 if nl==1 { let kl: i64=ac_starts2(buf,ls,i,key); if kl>0 { var v: i64=0; var p: i64=ls+kl; while p<i { let c: i64=buf[p] as i64; if c>=48 { if c<=57 { v=v*10+(c-48) } } p=p+1 } return v } ls=i+1 }
271 i=i+1 }
272 return 0
273}
274func ac_kv_str(buf: *u8, n: i64, key: *u8, out: *u8, outcap: i64) -> i64 {
275 var ls: i64=0; var i: i64=0
276 while i <= n { var nl: i64=0; if i>=n{nl=1} else { if buf[i]==(10 as u8){nl=1} }
277 if nl==1 { let kl: i64=ac_starts2(buf,ls,i,key); if kl>0 { var w: i64=0; var p: i64=ls+kl; while p<i { if w<outcap-1 { out[w]=buf[p]; w=w+1 } p=p+1 } out[w]=0 as u8; return w } ls=i+1 }
278 i=i+1 }
279 out[0]=0 as u8; return 0
280}
281// one-shot migrate legacy apm_targets.tsv -> seg-store config (apmtarget-<label> key:value + apmtarget-ids). idempotent.
282func ac_migrate_targets() -> i64 {
283 let chk: i64 = sys_openat_rd("knowledge/store/apmtarget-ids" as *u8); if chk >= 0 { sys_close(chk); return 0 }
284 let cfg: *u8 = sys_mmap(AC_MAGIC_65536); let n: i64 = ac_read_small(AC_TARGETS, cfg, AC_MAGIC_65536); if n <= 0 { return 0 }
285 sys_mkdir("knowledge/store" as *u8, 0x1ff)
286 let f: *u8 = sys_mmap(256); let path: *u8 = sys_mmap(256); let label: *u8 = sys_mmap(64); let stp: *i64 = sys_mmap(8) as *i64
287 var i: i64 = 0
288 while i < n {
289 if cfg[i] == (35 as u8) { var g: i64 = 1; while g == 1 { if i >= n { g = 0 } else { if cfg[i] == (10 as u8) { i = i + 1; g = 0 } else { i = i + 1 } } } }
290 else { if cfg[i] == (10 as u8) { i = i + 1 }
291 else {
292 i = ac_field(cfg, n, i, f, 256, stp); let port: i64 = ac_atoi(f)
293 if stp[0] == 0 {
294 i = ac_field(cfg, n, i, path, 256, stp)
295 if stp[0] == 0 {
296 i = ac_field(cfg, n, i, label, 64, stp)
297 var conc: i64 = 8
298 if stp[0] == 0 { i = ac_field(cfg, n, i, f, 256, stp); conc = ac_atoi(f) }
299 if conc <= 0 { conc = 8 }
300 if port > 0 {
301 let sp: *u8 = sys_mmap(512); var so: i64 = ac_s(sp, 0, "knowledge/store/apmtarget-" as *u8); so = ac_s(sp, so, label); sp[so] = 0 as u8
302 let rb: *u8 = sys_mmap(512); var ro: i64 = 0
303 ro = ac_s(rb, ro, "port=" as *u8); ro = ac_n(rb, ro, port); rb[ro] = 10 as u8; ro = ro + 1
304 ro = ac_s(rb, ro, "path=" as *u8); ro = ac_s(rb, ro, path); rb[ro] = 10 as u8; ro = ro + 1
305 ro = ac_s(rb, ro, "conc=" as *u8); ro = ac_n(rb, ro, conc); rb[ro] = 10 as u8; ro = ro + 1
306 let fd: i64 = sys_openat_wr(sp, 0x1a4); if fd >= 0 { sys_write(fd, rb, ro); sys_close(fd) }
307 let il: *u8 = sys_mmap(128); var io: i64 = ac_s(il, 0, label); il[io] = 10 as u8; io = io + 1
308 let ifd: i64 = sys_openat_append("knowledge/store/apmtarget-ids" as *u8, 0x1a4); if ifd >= 0 { sys_write(ifd, il, io); sys_close(ifd) }
309 }
310 } else { if stp[0] == 1 { i = i } }
311 }
312 } }
313 }
314 return 0
315}
316
317func main(argc: i64, argv: *i64) -> i64 {
318 let epoch: i64 = sys_now_realtime_sec()
319 var target_ip: i64 = 0x7f000001 // default loopback (on-NAS); argv[1]=a.b.c.d to probe over LAN
320 if argc >= 2 { target_ip = ac_parse_ip(argv[1] as *u8) }
321 ac_w("=== NISHI APM COLLECT (HTTP, Connection: close, no TLS; latency=TTFB) ip=" as *u8); ac_wn((target_ip>>24)&0xff); ac_w("." as *u8); ac_wn((target_ip>>16)&0xff); ac_w("." as *u8); ac_wn((target_ip>>8)&0xff); ac_w("." as *u8); ac_wn(target_ip&0xff); ac_w(" ===\n" as *u8)
322 ac_w(" stall=ttfb>2000ms timeout=" as *u8); ac_wn(AC_TIMEOUT_S); ac_w("s GREEN/AMBER(slow first byte)/RED(no response|err)/DOWN(refused); slowclose=keep-alive artifact (info)\n\n" as *u8)
323 ac_migrate_targets()
324 let idxb: *u8 = sys_mmap(AC_MAGIC_65536); let idn: i64 = ac_read_small("knowledge/store/apmtarget-ids" as *u8, idxb, AC_MAGIC_65536)
325 if idn <= 0 { ac_w("no targets: seg-store knowledge/store/apmtarget-ids empty and no legacy apm_targets.tsv to migrate\n" as *u8); return 0 }
326 let st: *i64 = sys_mmap(8 * 12) as *i64
327 let label: *u8 = sys_mmap(64); let path: *u8 = sys_mmap(256); let recp: *u8 = sys_mmap(512); let rec: *u8 = sys_mmap(AC_MAGIC_1024)
328 var ntargets: i64 = 0; var ls: i64 = 0; var i: i64 = 0
329 while i <= idn {
330 var nl: i64 = 0; if i >= idn { nl = 1 } else { if idxb[i] == (10 as u8) { nl = 1 } }
331 if nl == 1 {
332 if i > ls {
333 var w: i64 = 0; var k: i64 = ls; while k < i { if w < 63 { label[w] = idxb[k]; w = w + 1 } k = k + 1 } label[w] = 0 as u8
334 var po: i64 = ac_s(recp, 0, "knowledge/store/apmtarget-" as *u8); po = ac_s(recp, po, label); recp[po] = 0 as u8
335 let rn: i64 = ac_read_small(recp, rec, AC_MAGIC_1024)
336 if rn > 0 {
337 let port: i64 = ac_kv_int(rec, rn, "port=" as *u8)
338 ac_kv_str(rec, rn, "path=" as *u8, path, 256)
339 var conc: i64 = ac_kv_int(rec, rn, "conc=" as *u8); if conc <= 0 { conc = 8 }
340 if port > 0 { ac_burst(port, path, label, conc, AC_TIMEOUT_S, epoch, target_ip, st); ntargets = ntargets + 1 }
341 }
342 }
343 ls = i + 1
344 }
345 i = i + 1
346 }
347 ac_w("\ncollected " as *u8); ac_wn(ntargets); ac_w(" targets -> seg-store knowledge/store/apm-* (epoch=" as *u8); ac_wn(epoch); ac_w(")\n" as *u8)
348 return 0
349}