Files
Odin/core/prof/spall/spall.odin
T
2023-05-09 16:43:26 -04:00

223 lines
5.2 KiB
Odin

package spall
import "core:os"
import "core:time"
import "core:intrinsics"
import "core:mem"
// File Format
MANUAL_MAGIC :: u64le(0x0BADF00D)
Manual_Header :: struct #packed {
magic: u64le,
version: u64le,
timestamp_scale: f64le,
reserved: u64le,
}
Manual_Event_Type :: enum u8 {
Invalid = 0,
Begin = 3,
End = 4,
Instant = 5,
Pad_Skip = 7,
}
Begin_Event :: struct #packed {
type: Manual_Event_Type,
category: u8,
pid: u32le,
tid: u32le,
ts: f64le,
name_len: u8,
args_len: u8,
}
BEGIN_EVENT_MAX :: size_of(Begin_Event) + 255 + 255
End_Event :: struct #packed {
type: Manual_Event_Type,
pid: u32le,
tid: u32le,
ts: f64le,
}
Pad_Skip :: struct #packed {
type: Manual_Event_Type,
size: u32le,
}
// User Interface
Context :: struct {
precise_time: bool,
timestamp_scale: f64,
fd: os.Handle,
}
Buffer :: struct {
data: []u8,
head: int,
tid: u32,
pid: u32,
}
BUFFER_DEFAULT_SIZE :: 0x10_0000
context_create_with_scale :: proc(filename: string, precise_time: bool, timestamp_scale: f64) -> (ctx: Context, ok: bool) #optional_ok {
fd, err := os.open(filename, os.O_WRONLY | os.O_APPEND | os.O_CREATE | os.O_TRUNC, 0o600)
if err != os.ERROR_NONE {
return
}
ctx.fd = fd
ctx.precise_time = precise_time
ctx.timestamp_scale = timestamp_scale
temp := [size_of(Manual_Header)]u8{}
_build_header(temp[:], ctx.timestamp_scale)
os.write(ctx.fd, temp[:])
ok = true
return
}
context_create_with_sleep :: proc(filename: string, sleep := 2 * time.Second) -> (ctx: Context, ok: bool) #optional_ok {
freq, freq_ok := time.tsc_frequency(sleep)
timestamp_scale: f64 = ((1 / f64(freq)) * 1_000_000) if freq_ok else 1
return context_create_with_scale(filename, freq_ok, timestamp_scale)
}
context_create :: proc{context_create_with_scale, context_create_with_sleep}
context_destroy :: proc(ctx: ^Context) {
if ctx == nil {
return
}
os.close(ctx.fd)
ctx^ = Context{}
}
buffer_create :: proc(data: []byte, tid: u32 = 0, pid: u32 = 0) -> (buffer: Buffer, ok: bool) #optional_ok {
assert(len(data) >= 1024)
buffer.data = data
buffer.tid = tid
buffer.pid = pid
buffer.head = 0
ok = true
return
}
buffer_flush :: proc(ctx: ^Context, buffer: ^Buffer) {
start := _trace_now(ctx)
os.write(ctx.fd, buffer.data[:buffer.head])
buffer.head = 0
end := _trace_now(ctx)
buffer.head += _build_begin(buffer.data[buffer.head:], "Spall Trace Buffer Flush", "", start, buffer.tid, buffer.pid)
buffer.head += _build_end(buffer.data[buffer.head:], end, buffer.tid, buffer.pid)
}
buffer_destroy :: proc(ctx: ^Context, buffer: ^Buffer) {
buffer_flush(ctx, buffer)
buffer^ = Buffer{}
}
@(deferred_in=_scoped_buffer_end)
SCOPED_EVENT :: proc(ctx: ^Context, buffer: ^Buffer, name: string, args: string = "", location := #caller_location) -> bool {
_buffer_begin(ctx, buffer, name, args, location)
return true
}
@(private)
_scoped_buffer_end :: proc(ctx: ^Context, buffer: ^Buffer, _, _: string, _ := #caller_location) {
_buffer_end(ctx, buffer)
}
_trace_now :: proc "contextless" (ctx: ^Context) -> f64 {
if !ctx.precise_time {
return f64(time.tick_now()._nsec) / 1_000
}
return f64(intrinsics.read_cycle_counter())
}
_build_header :: proc "contextless" (buffer: []u8, timestamp_scale: f64) -> (header_size: int, ok: bool) #optional_ok {
header_size = size_of(Manual_Header)
if header_size > len(buffer) {
return 0, false
}
hdr := (^Manual_Header)(raw_data(buffer))
hdr.magic = MANUAL_MAGIC
hdr.version = 1
hdr.timestamp_scale = f64le(timestamp_scale)
hdr.reserved = 0
ok = true
return
}
_build_begin :: proc "contextless" (buffer: []u8, name: string, args: string, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok {
ev := (^Begin_Event)(raw_data(buffer))
name_len := min(len(name), 255)
args_len := min(len(args), 255)
event_size = size_of(Begin_Event) + name_len + args_len
if event_size > len(buffer) {
return 0, false
}
ev.type = .Begin
ev.pid = u32le(pid)
ev.tid = u32le(tid)
ev.ts = f64le(ts)
ev.name_len = u8(name_len)
ev.args_len = u8(args_len)
mem.copy(raw_data(buffer[size_of(Begin_Event):]), raw_data(name), name_len)
mem.copy(raw_data(buffer[size_of(Begin_Event)+name_len:]), raw_data(args), args_len)
ok = true
return
}
_build_end :: proc "contextless" (buffer: []u8, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok {
ev := (^End_Event)(raw_data(buffer))
event_size = size_of(End_Event)
if event_size > len(buffer) {
return 0, false
}
ev.type = .End
ev.pid = u32le(pid)
ev.tid = u32le(tid)
ev.ts = f64le(ts)
ok = true
return
}
_buffer_begin :: proc(ctx: ^Context, buffer: ^Buffer, name: string, args: string = "", location := #caller_location) {
if buffer.head + BEGIN_EVENT_MAX > len(buffer.data) {
buffer_flush(ctx, buffer)
}
name := location.procedure if name == "" else name
buffer.head += _build_begin(buffer.data[buffer.head:], name, args, _trace_now(ctx), buffer.tid, buffer.pid)
}
_buffer_end :: proc(ctx: ^Context, buffer: ^Buffer) {
ts := _trace_now(ctx)
if buffer.head + size_of(End_Event) > len(buffer.data) {
buffer_flush(ctx, buffer)
}
buffer.head += _build_end(buffer.data[buffer.head:], ts, buffer.tid, buffer.pid)
}