v / vlib / v2 / profiler / alloc.v
127 lines · 114 sloc · 2.98 KB · 03947c8bd69e101474701862d96c206cb020d182
Raw
1// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module profiler
5
6import time
7import sync
8
9// AllocRecord tracks a single allocation
10pub struct AllocRecord {
11pub:
12 ptr voidptr
13 size int
14 frame u64
15 file string // Source file
16 line int // Source line
17 timestamp i64 // Monotonic time in nanoseconds
18pub mut:
19 freed bool
20 free_frame u64
21}
22
23// FrameData aggregates allocation info for a single frame
24pub struct FrameData {
25pub:
26 frame_num u64
27pub mut:
28 new_bytes u64
29 freed_bytes u64
30 live_bytes u64
31 new_allocs []int // Indices into allocs array
32 freed_idxs []int // Indices of allocations freed this frame
33}
34
35// ProfilerState holds all profiling data
36@[heap]
37pub struct ProfilerState {
38pub mut:
39 enabled bool
40 current_frame u64
41 allocs []AllocRecord
42 alloc_map map[voidptr]int // ptr -> index in allocs
43 frames []FrameData
44 peak_bytes u64
45 live_bytes u64
46 total_allocs u64
47 total_frees u64
48 mu &sync.Mutex = unsafe { nil }
49}
50
51// Global profiler state singleton
52__global profiler_state = &ProfilerState{}
53
54// init_profiler_state initializes the global profiler state
55// Must be called before using the profiler
56pub fn init_profiler_state() {
57 profiler_state.mu = sync.new_mutex()
58 profiler_state.enabled = false
59 profiler_state.current_frame = 0
60 profiler_state.allocs = []AllocRecord{cap: 10000}
61 profiler_state.frames = []FrameData{cap: 1000}
62 profiler_state.peak_bytes = 0
63 profiler_state.live_bytes = 0
64 profiler_state.total_allocs = 0
65 profiler_state.total_frees = 0
66}
67
68// get_state returns the global profiler state
69pub fn get_state() &ProfilerState {
70 return profiler_state
71}
72
73// Statistics returns summary stats
74pub struct Statistics {
75pub:
76 live_bytes u64
77 peak_bytes u64
78 total_allocs u64
79 total_frees u64
80 leak_count int
81 frame_count u64
82}
83
84// get_statistics returns current profiler statistics
85pub fn get_statistics() Statistics {
86 if profiler_state.mu == unsafe { nil } {
87 return Statistics{}
88 }
89 profiler_state.mu.lock()
90 defer {
91 profiler_state.mu.unlock()
92 }
93
94 mut leak_count := 0
95 for alloc in profiler_state.allocs {
96 if !alloc.freed {
97 leak_count++
98 }
99 }
100
101 return Statistics{
102 live_bytes: profiler_state.live_bytes
103 peak_bytes: profiler_state.peak_bytes
104 total_allocs: profiler_state.total_allocs
105 total_frees: profiler_state.total_frees
106 leak_count: leak_count
107 frame_count: profiler_state.current_frame
108 }
109}
110
111// format_bytes converts bytes to a human-readable string
112pub fn format_bytes(bytes u64) string {
113 if bytes < 1024 {
114 return '${bytes} B'
115 } else if bytes < 1024 * 1024 {
116 return '${f64(bytes) / 1024.0:.1} KB'
117 } else if bytes < 1024 * 1024 * 1024 {
118 return '${f64(bytes) / (1024.0 * 1024.0):.1} MB'
119 } else {
120 return '${f64(bytes) / (1024.0 * 1024.0 * 1024.0):.2} GB'
121 }
122}
123
124// now_mono returns the current monotonic time in nanoseconds
125fn now_mono() i64 {
126 return time.sys_mono_now()
127}
128