code wiki / (root) / nx_log_v2.nx

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}