package spall import "base:intrinsics" import "core:os" import "core:time" // File Format MANUAL_MAGIC :: u64le(0x0BADF00D) Manual_Stream_Header :: struct #packed { magic: u64le, version: u64le, timestamp_scale: f64le, reserved: u64le, } Manual_Buffer_Header :: struct #packed { size: u32le, tid: u32le, pid: u32le, first_ts: u64le, } Manual_Event_Type :: enum u8 { Invalid = 0, Begin = 3, End = 4, Instant = 5, Pad_Skip = 7, Name_Process = 8, Name_Thread = 9, } Begin_Event :: struct #packed { type: Manual_Event_Type, ts: u64le, name_len: u8, args_len: u8, } BEGIN_EVENT_MAX :: size_of(Begin_Event) + 255 + 255 End_Event :: struct #packed { type: Manual_Event_Type, ts: u64le, } Pad_Skip :: struct #packed { type: Manual_Event_Type, size: u32le, } Name_Event :: struct #packed { type: Manual_Event_Type, name_len: u8, } NAME_EVENT_MAX :: size_of(Name_Event) + 255 // User Interface Context :: struct { precise_time: bool, timestamp_scale: f64, file: ^os.File, fd: uintptr, } Buffer :: struct { data: []u8, head: int, tid: u32, pid: u32, first_ts: u64, } BUFFER_DEFAULT_SIZE :: 0x10_0000 context_create_with_scale :: proc(filename: string, precise_time: bool, timestamp_scale: f64) -> (ctx: Context, ok: bool) #optional_ok { file, err := os.open(filename, {.Write, .Append, .Create, .Trunc}, {.Read_User, .Write_User}) if err != nil { return } ctx.file = file ctx.fd = os.fd(file) ctx.precise_time = precise_time ctx.timestamp_scale = timestamp_scale temp := [size_of(Manual_Stream_Header)]u8{} _build_stream_header(temp[:], ctx.timestamp_scale) 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_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} @(no_instrumentation) context_destroy :: proc(ctx: ^Context) { if ctx == nil { return } os.close(ctx.file) 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.first_ts = 0 buffer.head = size_of(Manual_Buffer_Header) ok = true return } @(no_instrumentation) buffer_flush :: proc "contextless" (ctx: ^Context, buffer: ^Buffer) #no_bounds_check /* bounds check would segfault instrumentation */ { if len(buffer.data) == 0 { return } buffer_size := buffer.head - size_of(Manual_Buffer_Header) hdr := (^Manual_Buffer_Header)(raw_data(buffer.data)) hdr.size = u32le(buffer_size) hdr.pid = u32le(buffer.pid) hdr.tid = u32le(buffer.tid) hdr.first_ts = u64le(buffer.first_ts) start := _trace_now(ctx) write(ctx.fd, buffer.data[:buffer.head]) buffer.head = size_of(Manual_Buffer_Header) end := _trace_now(ctx) buffer.head += _build_begin(buffer.data[buffer.head:], "Spall Trace Buffer Flush", "", start) buffer.head += _build_end(buffer.data[buffer.head:], end) buffer.first_ts = end } @(no_instrumentation) buffer_destroy :: proc(ctx: ^Context, buffer: ^Buffer) { buffer_flush(ctx, buffer) buffer^ = Buffer{} } @(deferred_in=_scoped_buffer_end) @(no_instrumentation) 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) @(no_instrumentation) _scoped_buffer_end :: proc(ctx: ^Context, buffer: ^Buffer, _, _: string, _ := #caller_location) { _buffer_end(ctx, buffer) } @(no_instrumentation) _trace_now :: proc "contextless" (ctx: ^Context) -> u64 { if !ctx.precise_time { return u64(tick_now()) } return u64(intrinsics.read_cycle_counter()) } @(no_instrumentation) _build_stream_header :: proc "contextless" (buffer: []u8, timestamp_scale: f64) -> (header_size: int, ok: bool) #optional_ok { header_size = size_of(Manual_Stream_Header) if header_size > len(buffer) { return 0, false } hdr := (^Manual_Stream_Header)(raw_data(buffer)) hdr.magic = MANUAL_MAGIC hdr.version = 3 hdr.timestamp_scale = f64le(timestamp_scale) hdr.reserved = 0 ok = true return } @(no_instrumentation) _build_begin :: #force_inline proc "contextless" (buffer: []u8, name: string, args: string, ts: u64) -> (event_size: int, ok: bool) #optional_ok #no_bounds_check /* bounds check would segfault instrumentation */ { 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.ts = u64le(ts) ev.name_len = u8(name_len) ev.args_len = u8(args_len) intrinsics.mem_copy_non_overlapping(raw_data(buffer[size_of(Begin_Event):]), raw_data(name), name_len) intrinsics.mem_copy_non_overlapping(raw_data(buffer[size_of(Begin_Event)+name_len:]), raw_data(args), args_len) ok = true return } @(no_instrumentation) _build_end :: proc "contextless" (buffer: []u8, ts: u64) -> (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.ts = u64le(ts) ok = true return } @(no_instrumentation) _build_name_event :: #force_inline proc "contextless" (buffer: []u8, name: string, type: Manual_Event_Type) -> (event_size: int, ok: bool) #optional_ok #no_bounds_check /* bounds check would segfault instrumentation */ { ev := (^Name_Event)(raw_data(buffer)) name_len := min(len(name), 255) event_size = size_of(Name_Event) + name_len if event_size > len(buffer) { return 0, false } if type != .Name_Process && type != .Name_Thread { return 0, false } ev.type = type ev.name_len = u8(name_len) intrinsics.mem_copy_non_overlapping(raw_data(buffer[size_of(Name_Event):]), raw_data(name), name_len) ok = true return } @(no_instrumentation) _buffer_begin :: proc "contextless" (ctx: ^Context, buffer: ^Buffer, name: string, args: string = "", location := #caller_location) #no_bounds_check /* bounds check would segfault instrumentation */ { 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)) } @(no_instrumentation) _buffer_end :: proc "contextless" (ctx: ^Context, buffer: ^Buffer) #no_bounds_check /* bounds check would segfault instrumentation */ { 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) } @(no_instrumentation) _buffer_name_thread :: proc "contextless" (ctx: ^Context, buffer: ^Buffer, name: string, location := #caller_location) #no_bounds_check /* bounds check would segfault instrumentation */ { if buffer.head + NAME_EVENT_MAX > len(buffer.data) { buffer_flush(ctx, buffer) } buffer.head += _build_name_event(buffer.data[buffer.head:], name, .Name_Thread) } @(no_instrumentation) _buffer_name_process :: proc "contextless" (ctx: ^Context, buffer: ^Buffer, name: string, location := #caller_location) #no_bounds_check /* bounds check would segfault instrumentation */ { if buffer.head + NAME_EVENT_MAX > len(buffer.data) { buffer_flush(ctx, buffer) } buffer.head += _build_name_event(buffer.data[buffer.head:], name, .Name_Process) } @(no_instrumentation, private="package") write :: proc "contextless" (fd: uintptr, buf: []byte) { _write(fd, buf) } @(no_instrumentation, private="package") tick_now :: proc "contextless" () -> (ns: i64) { return _tick_now() }