298 lines
8.3 KiB
Plaintext
298 lines
8.3 KiB
Plaintext
// 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();
|
||
}
|