nx_log_v2.nx source
↩ module page · 168 lines · 6047 B
1// nx_log_v2.nx -- single-syscall fast emit for real-time competitions.
2//
3// v1 (nx_log.nx) does ~7 sys_write calls per event (seq + level +
4// space + tag + space + msg + newline). Each syscall is ~200ns on
5// modern hardware, so v1 costs ~1.4us per event. In a CASC / SAT-COMP
6// loop emitting 10k events/sec that's 14ms/sec = 1.4% CPU just on
7// logging syscalls -- before even counting the format work.
8//
9// v2 formats the entire line into a single buffer, then issues ONE
10// sys_write. Expected: ~200ns per event (7x faster). Below-min-level
11// emit is a single load+compare, ~3ns.
12//
13// API mirrors v1's nx_log_emit so callers can swap in place. The
14// NxLogContext struct from v1 is reused -- no schema break.
15//
16// Performance discipline per cardinal
17// [[feedback-genealogy-source-lineage-runtime-one-step-ten-sec]]:
18// EAT the logging cost (don't strip in production), but EAT_LESS_THAN_BEFORE.
19
20// nx_safety_envelope:
21// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
22// sil_target: SIL1
23// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
24// verdict: NOT_YET_EVALUATED
25
26import "nx_syscalls.nx"
27import "nx_log.nx"
28
29const NX_LOG_V2_BUF_BYTES: i64 = 1024
30
31// Per-process static buffer reused across emits. Single-threaded
32// today; per-thread when nx_threads lands. 1KiB caps single-event
33// payload -- longer events should chunk via nx_log_chunked.nx.
34//
35// Pattern follows nx_crc32_init: store the mmap'd pointer as an i64
36// in a static slot, init flag in another static. Avoids the
37// `static *i64` double-indirection that segfaulted in earlier draft.
38static NX_LOG_V2_BUF_INIT: i64
39static NX_LOG_V2_BUF_ADDR: i64
40
41func nx_log_v2_buf() -> *u8 {
42 if NX_LOG_V2_BUF_INIT == 0 {
43 let raw: *u8 = sys_mmap(NX_LOG_V2_BUF_BYTES)
44 NX_LOG_V2_BUF_ADDR = raw as i64
45 NX_LOG_V2_BUF_INIT = 1
46 }
47 return NX_LOG_V2_BUF_ADDR as *u8
48}
49
50// strlen helper -- local to avoid the nx_log.nx call overhead.
51func nx_log_v2_slen(s: *u8) -> i64 {
52 var n: i64 = 0
53 while s[n] != 0 { n = n + 1 }
54 return n
55}
56
57// memcpy helper -- compiler doesn't inline these for us yet.
58func nx_log_v2_memcpy(dst: *u8, src: *u8, n: i64) -> i64 {
59 var i: i64 = 0
60 while i < n {
61 dst[i] = src[i]
62 i = i + 1
63 }
64 return n
65}
66
67// 8-digit zero-padded sequence number directly into buf at offset.
68func nx_log_v2_write_seq(buf: *u8, off: i64, seq: i64) -> i64 {
69 var d: i64 = 7
70 var s: i64 = seq
71 while d >= 0 {
72 let digit: i64 = s - (s / 10) * 10
73 buf[off + d] = 0x30 + digit
74 s = s / 10
75 d = d - 1
76 }
77 return off + 8
78}
79
80// One 5-char level tag directly into buf.
81func nx_log_v2_write_level(buf: *u8, off: i64, level: i64) -> i64 {
82 if level == NX_LOG_TRACE {
83 buf[off+0] = 0x54; buf[off+1] = 0x52; buf[off+2] = 0x41; buf[off+3] = 0x43; buf[off+4] = 0x45 // TRACE
84 } else {
85 if level == NX_LOG_DEBUG {
86 buf[off+0] = 0x44; buf[off+1] = 0x45; buf[off+2] = 0x42; buf[off+3] = 0x55; buf[off+4] = 0x47 // DEBUG
87 } else {
88 if level == NX_LOG_INFO {
89 buf[off+0] = 0x49; buf[off+1] = 0x4E; buf[off+2] = 0x46; buf[off+3] = 0x4F; buf[off+4] = 0x20 // INFO_
90 } else {
91 if level == NX_LOG_WARN {
92 buf[off+0] = 0x57; buf[off+1] = 0x41; buf[off+2] = 0x52; buf[off+3] = 0x4E; buf[off+4] = 0x20 // WARN_
93 } else {
94 if level == NX_LOG_ERROR {
95 buf[off+0] = 0x45; buf[off+1] = 0x52; buf[off+2] = 0x52; buf[off+3] = 0x4F; buf[off+4] = 0x52 // ERROR
96 } else {
97 if level == NX_LOG_FATAL {
98 buf[off+0] = 0x46; buf[off+1] = 0x41; buf[off+2] = 0x54; buf[off+3] = 0x41; buf[off+4] = 0x4C // FATAL
99 } else {
100 buf[off+0] = 0x3F; buf[off+1] = 0x3F; buf[off+2] = 0x3F; buf[off+3] = 0x3F; buf[off+4] = 0x3F // ?????
101 }
102 }
103 }
104 }
105 }
106 }
107 return off + 5
108}
109
110// Core fast emit. Format into a single buffer, then ONE sys_write.
111// Layout: '<8seq> <5LEVEL> <tag> <msg>\n'
112//
113// Returns 0 on success, -1 if message too large for buffer (caller
114// should chunk via nx_log_chunked.nx).
115func nx_log_v2_emit(ctx: *NxLogContext, level: i64,
116 tag: *u8, msg: *u8) -> i64 {
117 // FAST PATH: filtered-below-min emit. Single load + compare.
118 // Increment drop counter for observability.
119 if level < ctx.min_level {
120 ctx.drop_cnt = ctx.drop_cnt + 1
121 return 0
122 }
123
124 let buf: *u8 = nx_log_v2_buf()
125 var off: i64 = 0
126
127 // seq + space
128 off = nx_log_v2_write_seq(buf, off, ctx.seq)
129 ctx.seq = ctx.seq + 1
130 buf[off] = 0x20 // ' '
131 off = off + 1
132
133 // level + space (LEVEL is fixed 5 chars; trailing space)
134 off = nx_log_v2_write_level(buf, off, level)
135 buf[off] = 0x20
136 off = off + 1
137
138 // tag + space
139 let tag_n: i64 = nx_log_v2_slen(tag)
140 if off + tag_n + 4 >= NX_LOG_V2_BUF_BYTES { return -1 }
141 let _t: i64 = nx_log_v2_memcpy((buf as i64 + off) as *u8, tag, tag_n)
142 off = off + tag_n
143 buf[off] = 0x20
144 off = off + 1
145
146 // msg + newline
147 let msg_n: i64 = nx_log_v2_slen(msg)
148 if off + msg_n + 2 >= NX_LOG_V2_BUF_BYTES { return -1 }
149 let _m: i64 = nx_log_v2_memcpy((buf as i64 + off) as *u8, msg, msg_n)
150 off = off + msg_n
151 buf[off] = 0x0A // '\n'
152 off = off + 1
153
154 // ONE syscall for the whole event.
155 sys_write(ctx.out_fd, buf, off)
156 return 0
157}
158
159// Convenience wrappers mirror v1.
160func nx_log_v2_info(tag: *u8, msg: *u8) -> i64 {
161 return nx_log_v2_emit(nx_log_global(), NX_LOG_INFO, tag, msg)
162}
163func nx_log_v2_warn(tag: *u8, msg: *u8) -> i64 {
164 return nx_log_v2_emit(nx_log_global(), NX_LOG_WARN, tag, msg)
165}
166func nx_log_v2_error(tag: *u8, msg: *u8) -> i64 {
167 return nx_log_v2_emit(nx_log_global(), NX_LOG_ERROR, tag, msg)
168}