Files
agila_open_tool/tools/v/vlib/v2_toberemoved/profiler/ipc.v
T

176 lines
4.8 KiB
V

// 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()
}