Files
spl/stage2/utils/profile.spl
T

304 lines
8.8 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Expect retcode = 0
// 全量热点/耗时记录器(配合 @file_name()/@line()/@col() 与 sys/time.spl)。
// 每个调用点聚合:count、独占(self)与包含(total)的 steps/ns。
// - total_* 含被调用者(嵌套会重复,跨站点求和会超过墙钟)
// - 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;
match Vec.push(&self.stack, &token as *u8) { .Ok => {}, .Err(error) => {} }
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;
match Vec.pop(&self.stack, &popped as *u8) { .Ok => {}, .Err(error) => {} }
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;
}
}
}
// ---- dump / save ----
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 {
match Vec.push(&idx, &i as *u8) { .Ok => {}, .Err(error) => {} }
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 { ignore_usize(v + 1); }
}
fn ignore_usize(v: usize) {}
fn main() {
test_profile();
}