nx_obs.nx source
↩ module page · 145 lines · 4901 B
1// obs.nx -- observability primitives (spans, structured logs).
2//
3// EFFICIENCY_ROADMAP ยง5.2. OpenTelemetry-style distributed
4// tracing + structured logging. Today's observability is
5// ad-hoc sys_write; this module lands a proper API with
6// trace IDs, parent-child span relationships, + KV attributes.
7//
8// Phase A: runtime spans + log records serialised to stdout as
9// JSON (or future Protobuf via protobuf_wire.nx). Phase B:
10// `@trace` function attribute auto-instruments every call.
11//
12// Designed to match OpenTelemetry data model so future bridges
13// to Jaeger / Honeycomb / Datadog are clean byte-copies.
14//
15// Invariants:
16// OB1 Span IDs are 128-bit; collision probability at one
17// million spans/sec for a year < 1 in 10^30.
18// OB2 Parent-child relationships form a tree per trace_id
19// (enforced by caller; runtime doesn't verify).
20// OB3 Log records are structured: all fields get key+value;
21// no printf-style interpolation (bad for aggregation).
22
23// nx_safety_envelope:
24// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
25// sil_target: SIL1
26// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
27// verdict: NOT_YET_EVALUATED
28
29import "nx_syscalls.nx"
30import "nx_rand.nx"
31const LOG_MAGIC_1000000000: i64 = 1000000000
32const LOG_MAGIC_1000100000: i64 = 1000100000
33const LOG_MAGIC_1000200000: i64 = 1000200000
34const LOG_MAGIC_100000: i64 = 100000
35const LOG_MAGIC_1000500000: i64 = 1000500000
36const LOG_MAGIC_500000: i64 = 500000
37
38// Severity levels (matches syslog + tracing standards).
39const LOG_TRACE: i64 = 0
40const LOG_DEBUG: i64 = 1
41const LOG_INFO: i64 = 2
42const LOG_WARN: i64 = 3
43const LOG_ERROR: i64 = 4
44const LOG_FATAL: i64 = 5
45
46struct Span {
47 // 128-bit IDs stored as pairs of i64 for simplicity.
48 trace_id_hi: i64, trace_id_lo: i64,
49 span_id: i64,
50 parent_id: i64,
51 start_ns: i64, // caller-supplied clock reading
52 end_ns: i64, // 0 until closed
53 status: i64, // 0 ok, nonzero = error code
54 name_off: *u8,
55 name_len: i64,
56}
57
58func span_new(name: *u8, name_len: i64,
59 trace_hi: i64, trace_lo: i64,
60 parent_id: i64, start_ns: i64) -> *Span {
61 let raw: *u8 = sys_mmap(96)
62 let s: *Span = raw as *Span
63 s.trace_id_hi = trace_hi
64 s.trace_id_lo = trace_lo
65
66 // span_id = 64-bit random. rand_bytes gives crypto entropy.
67 let buf: *u8 = sys_mmap(16)
68 rand_bytes(buf, 8)
69 var sid: i64 = 0
70 var i: i64 = 0
71 while i < 8 {
72 sid = (sid << 8) | buf[i]
73 i = i + 1
74 }
75 s.span_id = sid & 0x7FFFFFFFFFFFFFFF
76
77 s.parent_id = parent_id
78 s.start_ns = start_ns
79 s.end_ns = 0
80 s.status = 0
81 s.name_off = name
82 s.name_len = name_len
83 return s
84}
85
86// Fresh root span -- generates new trace_id + parent=0.
87func span_new_root(name: *u8, name_len: i64, start_ns: i64) -> *Span {
88 let buf: *u8 = sys_mmap(32)
89 rand_bytes(buf, 16)
90 var hi: i64 = 0; var lo: i64 = 0
91 var i: i64 = 0
92 while i < 8 { hi = (hi << 8) | buf[i]; i = i + 1 }
93 while i < 16 { lo = (lo << 8) | buf[i]; i = i + 1 }
94 return span_new(name, name_len, hi & 0x7FFFFFFFFFFFFFFF,
95 lo & 0x7FFFFFFFFFFFFFFF, 0, start_ns)
96}
97
98// Close a span by recording end time + status.
99func span_close(s: *Span, end_ns: i64, status: i64) -> i64 {
100 s.end_ns = end_ns
101 s.status = status
102 return 0
103}
104
105func span_duration_ns(s: *Span) -> i64 {
106 return s.end_ns - s.start_ns
107}
108
109// === structured log records ==========================================
110
111// Write a log record to fd. Phase A uses a compact tab-delim
112// format; Phase B emits proper JSON / Protobuf.
113func log_write(fd: i64, level: i64,
114 msg: *u8, msg_len: i64) -> i64 {
115 // Format: "L<level>\t<msg>\n"
116 let buf: *u8 = sys_mmap(msg_len + 16)
117 buf[0] = 0x4C // 'L'
118 buf[1] = 0x30 + level // digit
119 buf[2] = 0x09 // tab
120 var i: i64 = 0
121 while i < msg_len { buf[3 + i] = msg[i]; i = i + 1 }
122 buf[3 + msg_len] = 0x0A // newline
123 sys_write(fd, buf, msg_len + 4)
124 return 0
125}
126
127// Compile-only smoke.
128func main() -> i64 {
129 let root: *Span = span_new_root("test", 4, LOG_MAGIC_1000000000)
130 if root.parent_id != 0 { return 1 }
131 if root.start_ns != LOG_MAGIC_1000000000 { return 2 }
132
133 let child: *Span = span_new("child", 5,
134 root.trace_id_hi, root.trace_id_lo,
135 root.span_id, LOG_MAGIC_1000100000)
136 if child.parent_id != root.span_id { return 3 }
137 if child.trace_id_hi != root.trace_id_hi { return 4 }
138
139 span_close(child, LOG_MAGIC_1000200000, 0)
140 if span_duration_ns(child) != LOG_MAGIC_100000 { return 5 }
141
142 span_close(root, LOG_MAGIC_1000500000, 0)
143 if span_duration_ns(root) != LOG_MAGIC_500000 { return 6 }
144 return 0
145}