From 5b020591d8174e06d5d556ee0baccd1e35aaca49 Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Mon, 28 Jul 2025 16:19:51 -0700 Subject: [PATCH 1/5] update to spall format v3 --- core/prof/spall/spall.odin | 124 ++++++++++++++++++++++++++----------- 1 file changed, 88 insertions(+), 36 deletions(-) diff --git a/core/prof/spall/spall.odin b/core/prof/spall/spall.odin index 12f082b2c..2c6a981ad 100644 --- a/core/prof/spall/spall.odin +++ b/core/prof/spall/spall.odin @@ -8,29 +8,36 @@ import "base:intrinsics" MANUAL_MAGIC :: u64le(0x0BADF00D) -Manual_Header :: struct #packed { +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, + Invalid = 0, - Begin = 3, - End = 4, - Instant = 5, + Begin = 3, + End = 4, + Instant = 5, - Pad_Skip = 7, + Pad_Skip = 7, + + Name_Process = 8, + Name_Thread = 9, } Begin_Event :: struct #packed { type: Manual_Event_Type, - category: u8, - pid: u32le, - tid: u32le, - ts: f64le, + ts: u64le, name_len: u8, args_len: u8, } @@ -38,9 +45,7 @@ BEGIN_EVENT_MAX :: size_of(Begin_Event) + 255 + 255 End_Event :: struct #packed { type: Manual_Event_Type, - pid: u32le, - tid: u32le, - ts: f64le, + ts: u64le, } Pad_Skip :: struct #packed { @@ -48,6 +53,11 @@ Pad_Skip :: struct #packed { size: u32le, } +Name_Container_Event :: struct #packed { + type: Manual_Event_Type, + name_len: u8, +} + // User Interface Context :: struct { @@ -77,7 +87,7 @@ context_create_with_scale :: proc(filename: string, precise_time: bool, timestam ctx.timestamp_scale = timestamp_scale temp := [size_of(Manual_Header)]u8{} - _build_header(temp[:], ctx.timestamp_scale) + _build_stream_header(temp[:], ctx.timestamp_scale) os.write(ctx.fd, temp[:]) ok = true return @@ -102,23 +112,32 @@ context_destroy :: proc(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 + 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 */ { + buffer_size := buffer.head - size_of(Manual_Buffer_Header) + hdr := (^Manual_Buffer_Header)(raw_data(buffer.data)) + hdr.size = buffer_size + hdr.pid = buffer.pid + hdr.tid = buffer.tid + hdr.first_ts = buffer.first_ts + start := _trace_now(ctx) write(ctx.fd, buffer.data[:buffer.head]) - buffer.head = 0 + 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.tid, buffer.pid) - buffer.head += _build_end(buffer.data[buffer.head:], end, buffer.tid, buffer.pid) + 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 } buffer_destroy :: proc(ctx: ^Context, buffer: ^Buffer) { @@ -141,16 +160,16 @@ _scoped_buffer_end :: proc(ctx: ^Context, buffer: ^Buffer, _, _: string, _ := #c } @(no_instrumentation) -_trace_now :: proc "contextless" (ctx: ^Context) -> f64 { +_trace_now :: proc "contextless" (ctx: ^Context) -> u64 { if !ctx.precise_time { - return f64(tick_now()) / 1_000 + return u64(tick_now()) } - return f64(intrinsics.read_cycle_counter()) + return u64(intrinsics.read_cycle_counter()) } @(no_instrumentation) -_build_header :: proc "contextless" (buffer: []u8, timestamp_scale: f64) -> (header_size: int, ok: bool) #optional_ok { +_build_stream_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 @@ -158,7 +177,7 @@ _build_header :: proc "contextless" (buffer: []u8, timestamp_scale: f64) -> (hea hdr := (^Manual_Header)(raw_data(buffer)) hdr.magic = MANUAL_MAGIC - hdr.version = 1 + hdr.version = 3 hdr.timestamp_scale = f64le(timestamp_scale) hdr.reserved = 0 ok = true @@ -166,7 +185,7 @@ _build_header :: proc "contextless" (buffer: []u8, timestamp_scale: f64) -> (hea } @(no_instrumentation) -_build_begin :: #force_inline proc "contextless" (buffer: []u8, name: string, args: string, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok #no_bounds_check /* bounds check would segfault 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) @@ -177,9 +196,7 @@ _build_begin :: #force_inline proc "contextless" (buffer: []u8, name: string, ar } ev.type = .Begin - ev.pid = u32le(pid) - ev.tid = u32le(tid) - ev.ts = f64le(ts) + 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) @@ -190,7 +207,7 @@ _build_begin :: #force_inline proc "contextless" (buffer: []u8, name: string, ar } @(no_instrumentation) -_build_end :: proc "contextless" (buffer: []u8, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok { +_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) { @@ -198,9 +215,28 @@ _build_end :: proc "contextless" (buffer: []u8, ts: f64, tid: u32, pid: u32) -> } ev.type = .End - ev.pid = u32le(pid) - ev.tid = u32le(tid) - ev.ts = f64le(ts) + 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_Container_Event)(raw_data(buffer)) + name_len := min(len(name), 255) + + event_size = size_of(Name_Container_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_Container_Event):]), raw_data(name), name_len) ok = true return @@ -212,7 +248,7 @@ _buffer_begin :: proc "contextless" (ctx: ^Context, buffer: ^Buffer, name: strin 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.head += _build_begin(buffer.data[buffer.head:], name, args, _trace_now(ctx)) } @(no_instrumentation) @@ -223,7 +259,23 @@ _buffer_end :: proc "contextless" (ctx: ^Context, buffer: ^Buffer) #no_bounds_ch buffer_flush(ctx, buffer) } - buffer.head += _build_end(buffer.data[buffer.head:], ts, buffer.tid, buffer.pid) + 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) From 2dd1e3c8e31f3a6c0d8af2e469d3d738ecfc2a9a Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Mon, 28 Jul 2025 16:24:29 -0700 Subject: [PATCH 2/5] fix casts and consts --- core/prof/spall/spall.odin | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/core/prof/spall/spall.odin b/core/prof/spall/spall.odin index 2c6a981ad..5ad3bf5b8 100644 --- a/core/prof/spall/spall.odin +++ b/core/prof/spall/spall.odin @@ -57,6 +57,7 @@ Name_Container_Event :: struct #packed { type: Manual_Event_Type, name_len: u8, } +NAME_EVENT_MAX :: size_of(Name_Container_Event) + 255 // User Interface @@ -71,6 +72,7 @@ Buffer :: struct { head: int, tid: u32, pid: u32, + first_ts: u64, } BUFFER_DEFAULT_SIZE :: 0x10_0000 @@ -125,10 +127,10 @@ buffer_create :: proc(data: []byte, tid: u32 = 0, pid: u32 = 0) -> (buffer: Buff buffer_flush :: proc "contextless" (ctx: ^Context, buffer: ^Buffer) #no_bounds_check /* bounds check would segfault instrumentation */ { buffer_size := buffer.head - size_of(Manual_Buffer_Header) hdr := (^Manual_Buffer_Header)(raw_data(buffer.data)) - hdr.size = buffer_size - hdr.pid = buffer.pid - hdr.tid = buffer.tid - hdr.first_ts = buffer.first_ts + 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]) @@ -170,12 +172,12 @@ _trace_now :: proc "contextless" (ctx: ^Context) -> u64 { @(no_instrumentation) _build_stream_header :: proc "contextless" (buffer: []u8, timestamp_scale: f64) -> (header_size: int, ok: bool) #optional_ok { - header_size = size_of(Manual_Header) + header_size = size_of(Manual_Stream_Header) if header_size > len(buffer) { return 0, false } - hdr := (^Manual_Header)(raw_data(buffer)) + hdr := (^Manual_Stream_Header)(raw_data(buffer)) hdr.magic = MANUAL_MAGIC hdr.version = 3 hdr.timestamp_scale = f64le(timestamp_scale) From 7986d859243e95a82c334bdbe6f2761a6a41209e Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Mon, 28 Jul 2025 16:25:42 -0700 Subject: [PATCH 3/5] name_container_event -> name_event --- core/prof/spall/spall.odin | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/prof/spall/spall.odin b/core/prof/spall/spall.odin index 5ad3bf5b8..e11404bcd 100644 --- a/core/prof/spall/spall.odin +++ b/core/prof/spall/spall.odin @@ -53,11 +53,11 @@ Pad_Skip :: struct #packed { size: u32le, } -Name_Container_Event :: struct #packed { +Name_Event :: struct #packed { type: Manual_Event_Type, name_len: u8, } -NAME_EVENT_MAX :: size_of(Name_Container_Event) + 255 +NAME_EVENT_MAX :: size_of(Name_Event) + 255 // User Interface @@ -225,10 +225,10 @@ _build_end :: proc "contextless" (buffer: []u8, ts: u64) -> (event_size: int, ok @(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_Container_Event)(raw_data(buffer)) + ev := (^Name_Event)(raw_data(buffer)) name_len := min(len(name), 255) - event_size = size_of(Name_Container_Event) + name_len + event_size = size_of(Name_Event) + name_len if event_size > len(buffer) { return 0, false } @@ -238,7 +238,7 @@ _build_name_event :: #force_inline proc "contextless" (buffer: []u8, name: strin ev.type = type ev.name_len = u8(name_len) - intrinsics.mem_copy_non_overlapping(raw_data(buffer[size_of(Name_Container_Event):]), raw_data(name), name_len) + intrinsics.mem_copy_non_overlapping(raw_data(buffer[size_of(Name_Event):]), raw_data(name), name_len) ok = true return From e1fd69f573bcf8ba9c8cfc961a007ab23aa83135 Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Mon, 28 Jul 2025 16:27:54 -0700 Subject: [PATCH 4/5] oops, one more manual_header --- core/prof/spall/spall.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/prof/spall/spall.odin b/core/prof/spall/spall.odin index e11404bcd..4d37913e4 100644 --- a/core/prof/spall/spall.odin +++ b/core/prof/spall/spall.odin @@ -88,7 +88,7 @@ context_create_with_scale :: proc(filename: string, precise_time: bool, timestam ctx.precise_time = precise_time ctx.timestamp_scale = timestamp_scale - temp := [size_of(Manual_Header)]u8{} + temp := [size_of(Manual_Stream_Header)]u8{} _build_stream_header(temp[:], ctx.timestamp_scale) os.write(ctx.fd, temp[:]) ok = true From 3e10684630bd6cd0602124cf3cb574345483eb93 Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Wed, 30 Jul 2025 14:09:50 -0700 Subject: [PATCH 5/5] adjust scale with new format, fix segfault for auto-trace --- core/prof/spall/spall.odin | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/prof/spall/spall.odin b/core/prof/spall/spall.odin index 4d37913e4..16b809359 100644 --- a/core/prof/spall/spall.odin +++ b/core/prof/spall/spall.odin @@ -97,7 +97,7 @@ context_create_with_scale :: proc(filename: string, precise_time: bool, timestam 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 + 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) } @@ -125,6 +125,10 @@ buffer_create :: proc(data: []byte, tid: u32 = 0, pid: u32 = 0) -> (buffer: Buff @(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)