v / vlib / v2 / profiler / profiler_alloc.v
204 lines · 173 sloc · 5.12 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
7
8// profiler_alloc is the allocation function that records allocations
9fn profiler_alloc(size int, ctx voidptr) voidptr {
10 ptr := unsafe { C.malloc(size) }
11 if ptr == unsafe { nil } {
12 return ptr
13 }
14
15 if profiler_state.mu == unsafe { nil } || !profiler_state.enabled {
16 return ptr
17 }
18
19 profiler_state.mu.lock()
20 defer {
21 profiler_state.mu.unlock()
22 }
23
24 idx := profiler_state.allocs.len
25 profiler_state.allocs << AllocRecord{
26 ptr: ptr
27 size: size
28 frame: profiler_state.current_frame
29 file: '' // Will be set by caller if needed
30 line: 0
31 timestamp: time.sys_mono_now()
32 freed: false
33 }
34 profiler_state.alloc_map[ptr] = idx
35 profiler_state.live_bytes += u64(size)
36 profiler_state.total_allocs++
37
38 if profiler_state.live_bytes > profiler_state.peak_bytes {
39 profiler_state.peak_bytes = profiler_state.live_bytes
40 }
41
42 // Add to current frame's new allocs
43 if profiler_state.frames.len > 0 {
44 frame_idx := profiler_state.frames.len - 1
45 profiler_state.frames[frame_idx].new_allocs << idx
46 profiler_state.frames[frame_idx].new_bytes += u64(size)
47 }
48
49 return ptr
50}
51
52// profiler_alloc_with_location records allocation with source location
53pub fn profiler_alloc_with_location(size int, file string, line int) voidptr {
54 ptr := unsafe { C.malloc(size) }
55 if ptr == unsafe { nil } {
56 return ptr
57 }
58
59 if profiler_state.mu == unsafe { nil } || !profiler_state.enabled {
60 return ptr
61 }
62
63 profiler_state.mu.lock()
64 defer {
65 profiler_state.mu.unlock()
66 }
67
68 idx := profiler_state.allocs.len
69 profiler_state.allocs << AllocRecord{
70 ptr: ptr
71 size: size
72 frame: profiler_state.current_frame
73 file: file
74 line: line
75 timestamp: time.sys_mono_now()
76 freed: false
77 }
78 profiler_state.alloc_map[ptr] = idx
79 profiler_state.live_bytes += u64(size)
80 profiler_state.total_allocs++
81
82 if profiler_state.live_bytes > profiler_state.peak_bytes {
83 profiler_state.peak_bytes = profiler_state.live_bytes
84 }
85
86 // Add to current frame's new allocs
87 if profiler_state.frames.len > 0 {
88 frame_idx := profiler_state.frames.len - 1
89 profiler_state.frames[frame_idx].new_allocs << idx
90 profiler_state.frames[frame_idx].new_bytes += u64(size)
91 }
92
93 return ptr
94}
95
96// profiler_free records a free operation
97fn profiler_free(ptr voidptr, ctx voidptr) {
98 if ptr == unsafe { nil } {
99 return
100 }
101
102 if profiler_state.mu != unsafe { nil } && profiler_state.enabled {
103 profiler_state.mu.lock()
104
105 if idx := profiler_state.alloc_map[ptr] {
106 profiler_state.allocs[idx].freed = true
107 profiler_state.allocs[idx].free_frame = profiler_state.current_frame
108 alloc_size := u64(profiler_state.allocs[idx].size)
109 if profiler_state.live_bytes >= alloc_size {
110 profiler_state.live_bytes -= alloc_size
111 }
112 profiler_state.total_frees++
113
114 // Add to current frame's freed list
115 if profiler_state.frames.len > 0 {
116 frame_idx := profiler_state.frames.len - 1
117 profiler_state.frames[frame_idx].freed_idxs << idx
118 profiler_state.frames[frame_idx].freed_bytes += alloc_size
119 }
120
121 // Remove from map
122 profiler_state.alloc_map.delete(ptr)
123 }
124
125 profiler_state.mu.unlock()
126 }
127
128 unsafe { C.free(ptr) }
129}
130
131// profiler_realloc records a realloc operation
132fn profiler_realloc(ptr voidptr, new_size int, ctx voidptr) voidptr {
133 if ptr == unsafe { nil } {
134 return profiler_alloc(new_size, ctx)
135 }
136 if new_size == 0 {
137 profiler_free(ptr, ctx)
138 return unsafe { nil }
139 }
140
141 // Record the free of old allocation
142 old_size := 0
143 if profiler_state.mu != unsafe { nil } && profiler_state.enabled {
144 profiler_state.mu.lock()
145 if idx := profiler_state.alloc_map[ptr] {
146 unsafe {
147 *(&old_size) = profiler_state.allocs[idx].size
148 }
149 }
150 profiler_state.mu.unlock()
151 }
152
153 new_ptr := unsafe { C.realloc(ptr, new_size) }
154 if new_ptr == unsafe { nil } {
155 return new_ptr
156 }
157
158 if profiler_state.mu != unsafe { nil } && profiler_state.enabled {
159 profiler_state.mu.lock()
160 defer {
161 profiler_state.mu.unlock()
162 }
163
164 // Remove old entry
165 if idx := profiler_state.alloc_map[ptr] {
166 profiler_state.allocs[idx].freed = true
167 profiler_state.allocs[idx].free_frame = profiler_state.current_frame
168 if profiler_state.live_bytes >= u64(old_size) {
169 profiler_state.live_bytes -= u64(old_size)
170 }
171 profiler_state.alloc_map.delete(ptr)
172 }
173
174 // Add new entry
175 new_idx := profiler_state.allocs.len
176 profiler_state.allocs << AllocRecord{
177 ptr: new_ptr
178 size: new_size
179 frame: profiler_state.current_frame
180 file: ''
181 line: 0
182 timestamp: time.sys_mono_now()
183 freed: false
184 }
185 profiler_state.alloc_map[new_ptr] = new_idx
186 profiler_state.live_bytes += u64(new_size)
187
188 if profiler_state.live_bytes > profiler_state.peak_bytes {
189 profiler_state.peak_bytes = profiler_state.live_bytes
190 }
191 }
192
193 return new_ptr
194}
195
196// profiler_allocator returns an Allocator that tracks all allocations
197pub fn profiler_allocator() Allocator {
198 return Allocator{
199 alloc_fn: profiler_alloc
200 free_fn: profiler_free
201 realloc_fn: profiler_realloc
202 ctx: unsafe { nil }
203 }
204}
205