|
@@ -0,0 +1,208 @@
|
|
|
+package prof_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) -> (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()
|
|
|
+ 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) > 0)
|
|
|
+ buffer.data = data
|
|
|
+ buffer.tid = tid
|
|
|
+ buffer.pid = pid
|
|
|
+ buffer.head = 0
|
|
|
+ ok = true
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+buffer_flush :: proc(ctx: ^Context, buffer: ^Buffer) {
|
|
|
+ os.write(ctx.fd, buffer.data[:buffer.head])
|
|
|
+ buffer.head = 0
|
|
|
+}
|
|
|
+
|
|
|
+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(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)
|
|
|
+}
|