// Expect retcode = 0 // 采样热点记录器:每个调用点聚合 count 与 self/total 的 steps/ns(self = total 减子区间)。 // 严格 LIFO,用 defer 配对: // var tok = Profile.begin(&prof, @file_name(), @line(), @col()); // defer { Profile.end(&prof, tok); } type .{ .Error } = @import("error.spl"); type .{ .Vec } = @import("vec.spl"); type .{ .ByteView } = @import("byte_view.spl"); type .{ .ByteBuffer } = @import("byte_buffer.spl"); type .{ .append_usize } = @import("fmt.spl"); type .{ .open_cstr, .write_all } = @import("io.spl"); type File = @import("../sys/file.spl"); type Time = @import("../sys/time.spl"); type Site = struct { fname: ByteView, line: usize, col: usize, count: usize, self_steps: usize, total_steps: usize, self_ns: usize, total_ns: usize, } type SpanRec = struct { site: usize, t0_ns: usize, t0_steps: usize, child_steps: usize, child_ns: usize, active: bool, } type Profile = @self; sites: Vec, // Site spans: Vec, // SpanRec stack: Vec, // usize(活动 span 的 token,LIFO) has_time: bool, fn new(): Profile { var has = false; if Time.available() != 0 { has = true; } ret Profile { .sites = Vec.blank(@sizeof(Site), @alignof(Site)), .spans = Vec.blank(@sizeof(SpanRec), @alignof(SpanRec)), .stack = Vec.blank(@sizeof(usize), @alignof(usize)), .has_time = has, }; } fn drop(self: *Profile) { Vec.drop(&self.stack); Vec.drop(&self.spans); Vec.drop(&self.sites); } fn site_at(self: *Profile, i: usize): *Site { match Vec.get(&self.sites, i) { .Ok(ptr) => { ret ptr as *Site; }, .Err(error) => { ret null; }, } ret null; } fn span_at(self: *Profile, i: usize): *SpanRec { match Vec.get(&self.spans, i) { .Ok(ptr) => { ret ptr as *SpanRec; }, .Err(error) => { ret null; }, } ret null; } fn usize_at(v: *Vec, i: usize): usize { match Vec.get(v, i) { .Ok(ptr) => { ret (ptr as *usize).*; }, .Err(error) => { ret 0; }, } ret 0; } fn find_site(self: *Profile, fname: []u8, line: usize, col: usize): usize { var fv = ByteView.from_slice(fname); var i: usize = 0; while i < self.sites.len { var s = site_at(self, i); if s != null && s.line == line && s.col == col && ByteView.eq(s.fname, fv) { ret i; } i = i + 1; } var fresh = Site { .fname = fv, .line = line, .col = col, .count = 0, .self_steps = 0, .total_steps = 0, .self_ns = 0, .total_ns = 0, }; match Vec.push(&self.sites, &fresh as *u8) { .Ok => {}, .Err(error) => { ret 0; }, } ret self.sites.len - 1; } // 仅计数(轻量点)。 fn hit(self: *Profile, fname: []u8, line: usize, col: usize) { var site = find_site(self, fname, line, col); var s = site_at(self, site); if s != null { s.count = s.count + 1; } } fn begin(self: *Profile, fname: []u8, line: usize, col: usize): usize { var site = find_site(self, fname, line, col); var s = site_at(self, site); if s != null { s.count = s.count + 1; } var t_ns: usize = 0; if self.has_time { t_ns = Time.now_ns(); } var rec = SpanRec { .site = site, .t0_ns = t_ns, .t0_steps = Time.steps(), .child_steps = 0, .child_ns = 0, .active = true, }; match Vec.push(&self.spans, &rec as *u8) { .Ok => {}, .Err(error) => { ret 0; }, } var token = self.spans.len - 1; Vec.push(&self.stack, &token as *u8); ret token; } fn end(self: *Profile, token: usize) { var rec = span_at(self, token); if rec == null || !rec.active { ret; } var e_steps = Time.steps() - rec.t0_steps; var e_ns: usize = 0; if self.has_time { e_ns = Time.now_ns() - rec.t0_ns; } var s = site_at(self, rec.site); if s != null { s.total_steps = s.total_steps + e_steps; s.self_steps = s.self_steps + (e_steps - rec.child_steps); if self.has_time { s.total_ns = s.total_ns + e_ns; s.self_ns = s.self_ns + (e_ns - rec.child_ns); } } rec.active = false; // 弹栈并把区间计入父 span(LIFO)。 var popped: usize = 0; Vec.pop(&self.stack, &popped as *u8); var n = self.stack.len; if n > 0 { var p = span_at(self, usize_at(&self.stack, n - 1)); if p != null { p.child_steps = p.child_steps + e_steps; p.child_ns = p.child_ns + e_ns; } } } fn out_s(out: *ByteBuffer, s: []u8): bool { match ByteBuffer.append(out, s) { .Ok => { ret true; }, .Err(error) => { ret false; } } ret false; } fn out_u(out: *ByteBuffer, v: usize): bool { match append_usize(out, v, 10, false) { .Ok => { ret true; }, .Err(error) => { ret false; } } ret false; } // 列:count self_steps total_steps self_ns total_ns avg_self location;按 self_steps 降序。 fn dump(self: *Profile, out: *ByteBuffer): bool { if !out_s(out, "profile sites=") { ret false; } if !out_u(out, self.sites.len) { ret false; } if !out_s(out, " has_time=") { ret false; } if !out_u(out, if self.has_time { 1 } else { 0 }) { ret false; } if !out_s(out, "\n") { ret false; } if !out_s(out, "count\tself_steps\ttotal_steps\tself_ns\ttotal_ns\tavg_self\tlocation\n") { ret false; } var idx = Vec.blank(@sizeof(usize), @alignof(usize)); defer { Vec.drop(&idx); } var i: usize = 0; while i < self.sites.len { Vec.push(&idx, &i as *u8); i = i + 1; } i = 1; while i < idx.len { var j = i; while j > 0 { var a = site_at(self, usize_at(&idx, j - 1)); var b = site_at(self, usize_at(&idx, j)); var swap = false; if a != null && b != null { if b.self_steps > a.self_steps { swap = true; } } if !swap { break; } var tmp = usize_at(&idx, j - 1); match Vec.get(&idx, j - 1) { .Ok(p) => { (p as *usize).* = usize_at(&idx, j); }, .Err(e) => {} } match Vec.get(&idx, j) { .Ok(p) => { (p as *usize).* = tmp; }, .Err(e) => {} } j = j - 1; } i = i + 1; } i = 0; while i < idx.len { var s = site_at(self, usize_at(&idx, i)); if s != null { if !out_u(out, s.count) { ret false; } if !out_s(out, "\t") { ret false; } if !out_u(out, s.self_steps) { ret false; } if !out_s(out, "\t") { ret false; } if !out_u(out, s.total_steps) { ret false; } if !out_s(out, "\t") { ret false; } if !out_u(out, s.self_ns) { ret false; } if !out_s(out, "\t") { ret false; } if !out_u(out, s.total_ns) { ret false; } if !out_s(out, "\t") { ret false; } if s.count != 0 { if !out_u(out, s.self_steps / s.count) { ret false; } } else { if !out_s(out, "0") { ret false; } } if !out_s(out, "\t") { ret false; } if !out_s(out, ByteView.as_slice(s.fname)) { ret false; } if !out_s(out, ":") { ret false; } if !out_u(out, s.line) { ret false; } if !out_s(out, ":") { ret false; } if !out_u(out, s.col) { ret false; } if !out_s(out, "\n") { ret false; } } i = i + 1; } ret true; } fn save(self: *Profile, path: ByteView): bool { var out = ByteBuffer.new(); defer { ByteBuffer.drop(&out); } if !dump(self, &out) { ret false; } var f = open_cstr(path, ByteView.from_slice("wb")); if !File.valid(&f) { ret false; } var w = write_all(&f, ByteBuffer.as_slice(out)); var closed = File.close(&f); match w { .Ok => {}, .Err(error) => { ret false; }, } if closed != 0 { ret false; } ret true; } fn spin(n: usize): usize { var i: usize = 0; var s: usize = 0; while i < n { s = s + i; i = i + 1; } ret s; } fn test_profile() { var p = new(); defer { drop(&p); } var tok = begin(&p, @file_name(), @line(), @col()); var v = spin(1000); end(&p, tok); @assert(p.sites.len >= 1, "sites"); var s = site_at(&p, 0); @assert(s != null && s.count >= 1, "count"); @assert(s.total_steps > 0, "steps"); @assert(s.self_steps > 0, "self steps"); if v == 0 { v + 1; } } fn main() { test_profile(); }