Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,175 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
module profiler
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
// Default path for profiler data exchange
|
||||
pub const profiler_data_path = '/tmp/v2_profiler.dat'
|
||||
|
||||
// Snapshot represents a point-in-time view of profiler state for IPC
|
||||
pub struct Snapshot {
|
||||
pub mut:
|
||||
timestamp i64
|
||||
frame_count u64
|
||||
live_bytes u64
|
||||
peak_bytes u64
|
||||
total_allocs u64
|
||||
total_frees u64
|
||||
frames []FrameData
|
||||
allocs []AllocRecord
|
||||
}
|
||||
|
||||
// write_snapshot writes the current profiler state to the shared file
|
||||
// Call this at the end of each frame in the profiled application
|
||||
pub fn write_snapshot() {
|
||||
write_snapshot_to(profiler_data_path)
|
||||
}
|
||||
|
||||
// write_snapshot_to writes profiler state to a specific path
|
||||
pub fn write_snapshot_to(path string) {
|
||||
if profiler_state.mu == unsafe { nil } {
|
||||
return
|
||||
}
|
||||
|
||||
profiler_state.mu.lock()
|
||||
defer {
|
||||
profiler_state.mu.unlock()
|
||||
}
|
||||
|
||||
// Build snapshot data as simple text format (easy to parse)
|
||||
mut sb := []u8{cap: 4096}
|
||||
|
||||
// Header with stats
|
||||
sb << 'V2PROF1\n'.bytes() // Version marker
|
||||
sb << 'T:${time.sys_mono_now()}\n'.bytes()
|
||||
sb << 'F:${profiler_state.current_frame}\n'.bytes()
|
||||
sb << 'L:${profiler_state.live_bytes}\n'.bytes()
|
||||
sb << 'P:${profiler_state.peak_bytes}\n'.bytes()
|
||||
sb << 'A:${profiler_state.total_allocs}\n'.bytes()
|
||||
sb << 'R:${profiler_state.total_frees}\n'.bytes()
|
||||
|
||||
// Frame data (last 200 frames max)
|
||||
start_frame := if profiler_state.frames.len > 200 {
|
||||
profiler_state.frames.len - 200
|
||||
} else {
|
||||
0
|
||||
}
|
||||
sb << 'FRAMES:${profiler_state.frames.len - start_frame}\n'.bytes()
|
||||
for i := start_frame; i < profiler_state.frames.len; i++ {
|
||||
frame := profiler_state.frames[i]
|
||||
sb << '${frame.frame_num},${frame.new_bytes},${frame.freed_bytes},${frame.live_bytes}\n'.bytes()
|
||||
}
|
||||
|
||||
// Allocation records (last 1000 max, for performance)
|
||||
start_alloc := if profiler_state.allocs.len > 1000 {
|
||||
profiler_state.allocs.len - 1000
|
||||
} else {
|
||||
0
|
||||
}
|
||||
sb << 'ALLOCS:${profiler_state.allocs.len - start_alloc}\n'.bytes()
|
||||
for i := start_alloc; i < profiler_state.allocs.len; i++ {
|
||||
alloc := profiler_state.allocs[i]
|
||||
freed_val := if alloc.freed { '1' } else { '0' }
|
||||
// ptr,size,frame,freed,free_frame,file,line
|
||||
sb << '${u64(alloc.ptr)},${alloc.size},${alloc.frame},${freed_val},${alloc.free_frame},${alloc.file},${alloc.line}\n'.bytes()
|
||||
}
|
||||
|
||||
sb << 'END\n'.bytes()
|
||||
|
||||
// Write atomically by writing to temp file then renaming
|
||||
tmp_path := path + '.tmp'
|
||||
os.write_file(tmp_path, sb.bytestr()) or { return }
|
||||
os.rename(tmp_path, path) or { return }
|
||||
}
|
||||
|
||||
// read_snapshot reads profiler state from the shared file
|
||||
// Returns none if file doesn't exist or is invalid
|
||||
pub fn read_snapshot() ?Snapshot {
|
||||
return read_snapshot_from(profiler_data_path)
|
||||
}
|
||||
|
||||
// read_snapshot_from reads profiler state from a specific path
|
||||
pub fn read_snapshot_from(path string) ?Snapshot {
|
||||
content := os.read_file(path) or { return none }
|
||||
lines := content.split_into_lines()
|
||||
|
||||
if lines.len < 7 || lines[0] != 'V2PROF1' {
|
||||
return none
|
||||
}
|
||||
|
||||
mut snap := Snapshot{}
|
||||
|
||||
// Parse header
|
||||
snap.timestamp = parse_value(lines[1], 'T:')
|
||||
snap.frame_count = u64(parse_value(lines[2], 'F:'))
|
||||
snap.live_bytes = u64(parse_value(lines[3], 'L:'))
|
||||
snap.peak_bytes = u64(parse_value(lines[4], 'P:'))
|
||||
snap.total_allocs = u64(parse_value(lines[5], 'A:'))
|
||||
snap.total_frees = u64(parse_value(lines[6], 'R:'))
|
||||
|
||||
mut line_idx := 7
|
||||
|
||||
// Parse frames
|
||||
if line_idx < lines.len && lines[line_idx].starts_with('FRAMES:') {
|
||||
num_frames := lines[line_idx].all_after('FRAMES:').int()
|
||||
line_idx++
|
||||
for _ in 0 .. num_frames {
|
||||
if line_idx >= lines.len {
|
||||
break
|
||||
}
|
||||
parts := lines[line_idx].split(',')
|
||||
if parts.len >= 4 {
|
||||
snap.frames << FrameData{
|
||||
frame_num: u64(parts[0].i64())
|
||||
new_bytes: u64(parts[1].i64())
|
||||
freed_bytes: u64(parts[2].i64())
|
||||
live_bytes: u64(parts[3].i64())
|
||||
}
|
||||
}
|
||||
line_idx++
|
||||
}
|
||||
}
|
||||
|
||||
// Parse allocs
|
||||
if line_idx < lines.len && lines[line_idx].starts_with('ALLOCS:') {
|
||||
num_allocs := lines[line_idx].all_after('ALLOCS:').int()
|
||||
line_idx++
|
||||
for _ in 0 .. num_allocs {
|
||||
if line_idx >= lines.len {
|
||||
break
|
||||
}
|
||||
parts := lines[line_idx].split(',')
|
||||
if parts.len >= 7 {
|
||||
snap.allocs << AllocRecord{
|
||||
ptr: unsafe { voidptr(u64(parts[0].i64())) }
|
||||
size: parts[1].int()
|
||||
frame: u64(parts[2].i64())
|
||||
freed: parts[3] == '1'
|
||||
free_frame: u64(parts[4].i64())
|
||||
file: parts[5]
|
||||
line: parts[6].int()
|
||||
}
|
||||
}
|
||||
line_idx++
|
||||
}
|
||||
}
|
||||
|
||||
return snap
|
||||
}
|
||||
|
||||
fn parse_value(line string, prefix string) i64 {
|
||||
if line.starts_with(prefix) {
|
||||
return line.all_after(prefix).i64()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// frame_end_with_snapshot ends the frame and writes a snapshot
|
||||
// Use this instead of frame_end() when you want live monitoring
|
||||
pub fn frame_end_with_snapshot() {
|
||||
frame_end()
|
||||
write_snapshot()
|
||||
}
|
||||
Reference in new issue
Block a user