Files
spl/stage2/utils/profile.spl
T

298 lines
8.3 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
// 采样热点记录器:每个调用点聚合 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();
}