Files
Odin/core/prof/spall/spall.odin
T
Matias Fernandez 57c14f6a9b Allow custom sleep on tsc fallback.
This gives the user more control over the spectrum of precision vs. load time on Windows. Spall's output with much lower sleep times is still useful in my experience.

NOTE: A better API might be to allow the user to pass the freq as a param to "create_context" in case they already paid for it beforehand, but this seems fine for now.
2023-05-09 00:03:58 -04:00

216 lines
4.9 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 :: proc(filename: string, freq_fallback_sleep := 2 * time.Second) -> (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
freq, freq_ok := time.tsc_frequency(freq_fallback_sleep)
ctx.precise_time = freq_ok
ctx.timestamp_scale = ((1 / f64(freq)) * 1_000_000) if freq_ok else 1
temp := [size_of(Manual_Header)]u8{}
_build_header(temp[:], ctx.timestamp_scale)
os.write(ctx.fd, temp[:])
ok = true
return
}
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)
}