v / vlib / v2 / profiler / runtime.v
215 lines · 192 sloc · 4.6 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
6// profiler_init initializes the profiler system
7// Must be called before enabling profiling
8pub fn profiler_init() {
9 init_profiler_state()
10}
11
12// profiler_enable enables allocation tracking
13pub fn profiler_enable() {
14 if profiler_state.mu == unsafe { nil } {
15 init_profiler_state()
16 }
17 profiler_state.mu.lock()
18 defer {
19 profiler_state.mu.unlock()
20 }
21 profiler_state.enabled = true
22 // Start first frame
23 if profiler_state.frames.len == 0 {
24 profiler_state.frames << FrameData{
25 frame_num: 0
26 new_bytes: 0
27 freed_bytes: 0
28 live_bytes: profiler_state.live_bytes
29 }
30 }
31}
32
33// profiler_disable disables allocation tracking
34pub fn profiler_disable() {
35 if profiler_state.mu == unsafe { nil } {
36 return
37 }
38 profiler_state.mu.lock()
39 defer {
40 profiler_state.mu.unlock()
41 }
42 profiler_state.enabled = false
43}
44
45// profiler_is_enabled returns whether profiling is active
46pub fn profiler_is_enabled() bool {
47 if profiler_state.mu == unsafe { nil } {
48 return false
49 }
50 profiler_state.mu.lock()
51 defer {
52 profiler_state.mu.unlock()
53 }
54 return profiler_state.enabled
55}
56
57// frame_end signals the end of a frame and collects frame data
58// Call this at the end of each game/application frame
59pub fn frame_end() {
60 if profiler_state.mu == unsafe { nil } || !profiler_state.enabled {
61 return
62 }
63
64 profiler_state.mu.lock()
65 defer {
66 profiler_state.mu.unlock()
67 }
68
69 // Finalize current frame's live bytes
70 if profiler_state.frames.len > 0 {
71 profiler_state.frames[profiler_state.frames.len - 1].live_bytes = profiler_state.live_bytes
72 }
73
74 // Increment frame counter
75 profiler_state.current_frame++
76
77 // Start new frame
78 profiler_state.frames << FrameData{
79 frame_num: profiler_state.current_frame
80 new_bytes: 0
81 freed_bytes: 0
82 live_bytes: profiler_state.live_bytes
83 }
84}
85
86// get_frame_count returns the current frame number
87pub fn get_frame_count() u64 {
88 if profiler_state.mu == unsafe { nil } {
89 return 0
90 }
91 profiler_state.mu.lock()
92 defer {
93 profiler_state.mu.unlock()
94 }
95 return profiler_state.current_frame
96}
97
98// get_frames returns a copy of all frame data
99pub fn get_frames() []FrameData {
100 if profiler_state.mu == unsafe { nil } {
101 return []
102 }
103 profiler_state.mu.lock()
104 defer {
105 profiler_state.mu.unlock()
106 }
107 return profiler_state.frames.clone()
108}
109
110// get_allocs returns a copy of all allocation records
111pub fn get_allocs() []AllocRecord {
112 if profiler_state.mu == unsafe { nil } {
113 return []
114 }
115 profiler_state.mu.lock()
116 defer {
117 profiler_state.mu.unlock()
118 }
119 return profiler_state.allocs.clone()
120}
121
122// get_leaks returns allocations that were never freed
123pub fn get_leaks() []AllocRecord {
124 if profiler_state.mu == unsafe { nil } {
125 return []
126 }
127 profiler_state.mu.lock()
128 defer {
129 profiler_state.mu.unlock()
130 }
131
132 mut leaks := []AllocRecord{}
133 for alloc in profiler_state.allocs {
134 if !alloc.freed {
135 leaks << alloc
136 }
137 }
138 return leaks
139}
140
141// get_allocs_for_frame returns allocations made in a specific frame
142pub fn get_allocs_for_frame(frame_num u64) []AllocRecord {
143 if profiler_state.mu == unsafe { nil } {
144 return []
145 }
146 profiler_state.mu.lock()
147 defer {
148 profiler_state.mu.unlock()
149 }
150
151 mut frame_allocs := []AllocRecord{}
152 for alloc in profiler_state.allocs {
153 if alloc.frame == frame_num {
154 frame_allocs << alloc
155 }
156 }
157 return frame_allocs
158}
159
160// get_live_allocs returns currently live (not freed) allocations
161pub fn get_live_allocs() []AllocRecord {
162 if profiler_state.mu == unsafe { nil } {
163 return []
164 }
165 profiler_state.mu.lock()
166 defer {
167 profiler_state.mu.unlock()
168 }
169
170 mut live := []AllocRecord{}
171 for alloc in profiler_state.allocs {
172 if !alloc.freed {
173 live << alloc
174 }
175 }
176 return live
177}
178
179// reset clears all profiler data
180pub fn reset() {
181 if profiler_state.mu == unsafe { nil } {
182 return
183 }
184 profiler_state.mu.lock()
185 defer {
186 profiler_state.mu.unlock()
187 }
188
189 profiler_state.allocs.clear()
190 profiler_state.alloc_map.clear()
191 profiler_state.frames.clear()
192 profiler_state.current_frame = 0
193 profiler_state.live_bytes = 0
194 profiler_state.peak_bytes = 0
195 profiler_state.total_allocs = 0
196 profiler_state.total_frees = 0
197
198 // Start fresh frame
199 if profiler_state.enabled {
200 profiler_state.frames << FrameData{
201 frame_num: 0
202 }
203 }
204}
205
206// use_profiler_allocator enables the profiler allocator in the current context
207// Returns the old allocator for restoration
208pub fn use_profiler_allocator() Allocator {
209 return push_allocator(profiler_allocator())
210}
211
212// restore_allocator restores the previous allocator
213pub fn restore_allocator(old Allocator) {
214 pop_allocator(old)
215}
216