Browse Source

Add `core:prof/spall`

gingerBill 2 years ago
parent
commit
9c7656d59a
1 changed files with 208 additions and 0 deletions
  1. 208 0
      core/prof/spall/spall.odin

+ 208 - 0
core/prof/spall/spall.odin

@@ -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)
+}