v / cmd / v2 / guiprof / draw.v
346 lines · 278 sloc · 9.42 KB · e2e5cf8db56f3562c7baa735061690be936bdf3e
Raw
1// Copyright (c) 2020-2024 Joe Conigliaro. 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 main
5
6import gg
7import v2.profiler
8
9// draw_header renders the top stats bar
10fn draw_header(mut app App) {
11 ctx := app.gg
12 w := f32(ctx.width)
13
14 // Background
15 ctx.draw_rect_filled(0, 0, w, f32(app.header_height), header_bg)
16
17 // Get stats
18 stats := app.cached_stats
19
20 // Format and draw stats
21 heap_str := 'HEAP: ${profiler.format_bytes(stats.live_bytes)}'
22 peak_str := 'Peak: ${profiler.format_bytes(stats.peak_bytes)}'
23 allocs_str := '${stats.total_allocs} allocs'
24 frees_str := '${stats.total_frees} frees'
25 leaks_str := '${stats.leak_count} leaks'
26
27 // Draw stats with spacing
28 mut x := f32(20)
29 y := f32(22)
30
31 ctx.draw_text(int(x), int(y), heap_str, color: text_color, size: 20)
32 x += 180
33
34 ctx.draw_text(int(x), int(y), peak_str, color: text_color, size: 20)
35 x += 160
36
37 ctx.draw_text(int(x), int(y), allocs_str, color: new_alloc_color, size: 20)
38 x += 140
39
40 ctx.draw_text(int(x), int(y), frees_str, color: freed_color, size: 20)
41 x += 120
42
43 // Show leaks in red if there are any
44 leak_clr := if stats.leak_count > 0 { leak_color } else { text_color }
45 ctx.draw_text(int(x), int(y), leaks_str, color: leak_clr, size: 20)
46
47 // Mode and frame counter on right
48 mode_str := if app.demo_mode {
49 'DEMO'
50 } else if app.live_mode {
51 'LIVE'
52 } else {
53 'LOCAL'
54 }
55 ctx.draw_text(int(w) - 250, int(y), mode_str, color: gg.Color{100, 255, 100, 255}, size: 20)
56
57 frame_str := 'Frame: ${stats.frame_count}'
58 ctx.draw_text(int(w) - 150, int(y), frame_str, color: text_color, size: 20)
59
60 // Separator line
61 ctx.draw_line(0, f32(app.header_height), w, f32(app.header_height), grid_color)
62}
63
64// draw_histogram renders the allocation histogram
65fn draw_histogram(mut app App) {
66 ctx := app.gg
67 w := f32(ctx.width)
68
69 // Histogram area
70 hist_y := f32(app.header_height)
71 hist_h := f32(app.histogram_height)
72 center_y := hist_y + hist_h / 2
73
74 // Background
75 ctx.draw_rect_filled(0, hist_y, w, hist_h, bg_color)
76
77 // Center line
78 ctx.draw_line(0, center_y, w, center_y, grid_color)
79
80 // Get frame data (always use cached data - it's populated by update_cache from snapshot or demo)
81 frames := app.cached_frames
82 if frames.len == 0 {
83 ctx.draw_text(int(w / 2) - 80, int(center_y) - 10, 'No frame data',
84 color: text_color
85 size: 18
86 )
87 return
88 }
89
90 // Calculate max values for scaling
91 mut max_new := u64(1)
92 mut max_freed := u64(1)
93 for frame in frames {
94 if frame.new_bytes > max_new {
95 max_new = frame.new_bytes
96 }
97 if frame.freed_bytes > max_freed {
98 max_freed = frame.freed_bytes
99 }
100 }
101
102 // Bar dimensions
103 margin := f32(40)
104 bar_area_width := w - margin * 2
105 bar_width := bar_area_width / f32(frames.len)
106 if bar_width < 1 {
107 return
108 }
109 bar_gap := bar_width * 0.1
110 actual_bar_w := bar_width - bar_gap
111
112 max_bar_height := (hist_h / 2) - 10
113
114 // Draw bars for each frame
115 for i, frame in frames {
116 x := margin + f32(i) * bar_width
117
118 // New allocations (cyan, above center)
119 if frame.new_bytes > 0 {
120 new_height := f32(frame.new_bytes) / f32(max_new) * max_bar_height
121 bar_color := if i == app.selected_frame { selected_color } else { new_alloc_color }
122 ctx.draw_rect_filled(x, center_y - new_height, actual_bar_w, new_height, bar_color)
123 }
124
125 // Freed allocations (purple, below center)
126 if frame.freed_bytes > 0 {
127 freed_height := f32(frame.freed_bytes) / f32(max_freed) * max_bar_height
128 bar_color := if i == app.selected_frame {
129 gg.Color{150, 130, 255, 200}
130 } else {
131 freed_color
132 }
133 ctx.draw_rect_filled(x, center_y, actual_bar_w, freed_height, bar_color)
134 }
135
136 // Hover highlight
137 if i == app.hover_frame && i != app.selected_frame {
138 ctx.draw_rect_empty(x - 1, hist_y + 5, actual_bar_w + 2, hist_h - 10, timeline_cursor)
139 }
140 }
141
142 // Legend
143 ctx.draw_rect_filled(10, hist_y + 10, 12, 12, new_alloc_color)
144 ctx.draw_text(26, int(hist_y) + 8, 'New', color: text_color, size: 14)
145
146 ctx.draw_rect_filled(70, hist_y + 10, 12, 12, freed_color)
147 ctx.draw_text(86, int(hist_y) + 8, 'Freed', color: text_color, size: 14)
148
149 // Separator line
150 ctx.draw_line(0, hist_y + hist_h, w, hist_y + hist_h, grid_color)
151}
152
153// draw_timeline renders the frame timeline with scrubber
154fn draw_timeline(mut app App) {
155 ctx := app.gg
156 w := f32(ctx.width)
157
158 // Timeline area
159 timeline_y := f32(app.header_height + app.histogram_height)
160 timeline_h := f32(app.timeline_height)
161
162 // Background
163 ctx.draw_rect_filled(0, timeline_y, w, timeline_h, header_bg)
164
165 frames := app.cached_frames
166 if frames.len == 0 {
167 return
168 }
169
170 // Draw frame ticks
171 margin := f32(40)
172 tick_area_width := w - margin * 2
173 tick_spacing := tick_area_width / f32(frames.len)
174
175 // Draw tick marks
176 for i := 0; i < frames.len; i++ {
177 x := margin + f32(i) * tick_spacing
178 tick_height := if i % 10 == 0 { f32(15) } else { f32(8) }
179 ctx.draw_line(x, timeline_y + 20, x, timeline_y + 20 + tick_height, grid_color)
180
181 // Frame number labels every 10 frames
182 if i % 10 == 0 && tick_spacing > 3 {
183 ctx.draw_text(int(x) - 10, int(timeline_y) + 40, '${i}', color: text_color, size: 12)
184 }
185 }
186
187 // Draw selected frame cursor
188 if app.selected_frame >= 0 && app.selected_frame < frames.len {
189 cursor_x := margin + f32(app.selected_frame) * tick_spacing
190 // Triangle cursor
191 draw_triangle_filled(ctx, cursor_x, timeline_y + 15, cursor_x - 8, timeline_y + 5,
192
193 cursor_x + 8, timeline_y + 5, timeline_cursor)
194
195 // Vertical line
196 ctx.draw_line(cursor_x, timeline_y + 15, cursor_x, timeline_y + timeline_h - 5,
197 timeline_cursor)
198
199 // Frame info
200 frame := frames[app.selected_frame]
201 info := 'Frame ${app.selected_frame}: +${profiler.format_bytes(frame.new_bytes)} / -${profiler.format_bytes(frame.freed_bytes)}'
202 ctx.draw_text(int(w / 2) - 100, int(timeline_y) + 55, info, color: timeline_cursor, size: 16)
203 }
204
205 // Separator line
206 ctx.draw_line(0, timeline_y + timeline_h, w, timeline_y + timeline_h, grid_color)
207}
208
209// draw_controls renders the filter controls
210fn draw_controls(mut app App) {
211 ctx := app.gg
212 w := f32(ctx.width)
213
214 // Controls area
215 ctrl_y := f32(app.header_height + app.histogram_height + app.timeline_height)
216 ctrl_h := f32(app.controls_height)
217
218 // Background
219 ctx.draw_rect_filled(0, ctrl_y, w, ctrl_h, bg_color)
220
221 // Filter buttons
222 mut x := f32(20)
223 y := ctrl_y + 10
224
225 // Filter label
226 ctx.draw_text(int(x), int(y), 'Filter:', color: text_color, size: 16)
227 x += 60
228
229 // Filter mode buttons
230 filters := ['All', 'Leaks', 'Large', 'Frame']
231 modes := [FilterMode.all, FilterMode.new_not_freed, FilterMode.large, FilterMode.current_frame]
232
233 for i, label in filters {
234 btn_w := f32(60)
235 btn_h := f32(24)
236
237 // Highlight active filter
238 btn_color := if app.filter_mode == modes[i] { selected_color } else { header_bg }
239 ctx.draw_rect_filled(x, y - 2, btn_w, btn_h, btn_color)
240 ctx.draw_rect_empty(x, y - 2, btn_w, btn_h, grid_color)
241
242 ctx.draw_text(int(x) + 8, int(y), label, color: text_color, size: 14)
243 x += btn_w + 10
244 }
245
246 // Separator line
247 ctx.draw_line(0, ctrl_y + ctrl_h, w, ctrl_y + ctrl_h, grid_color)
248}
249
250// draw_details renders the selection details panel
251fn draw_details(mut app App) {
252 ctx := app.gg
253 w := f32(ctx.width)
254 h := f32(ctx.height)
255
256 // Details area
257 details_y := f32(app.header_height + app.histogram_height + app.timeline_height +
258 app.controls_height)
259 details_h := h - details_y
260
261 // Background
262 ctx.draw_rect_filled(0, details_y, w, details_h, header_bg)
263
264 // Show selected allocation info or instructions
265 if app.selected_alloc >= 0 {
266 allocs := app.cached_allocs
267 if app.selected_alloc < allocs.len {
268 alloc := allocs[app.selected_alloc]
269
270 // Format allocation info
271 ptr_str := 'Address: 0x${voidptr(alloc.ptr):p}'
272 size_str := 'Size: ${profiler.format_bytes(u64(alloc.size))} (${alloc.size} bytes)'
273 frame_str := 'Allocated: frame ${alloc.frame}'
274 status_str := if alloc.freed {
275 'Status: FREED (frame ${alloc.free_frame})'
276 } else {
277 'Status: LIVE (potential leak)'
278 }
279
280 status_color := if alloc.freed { freed_color } else { leak_color }
281
282 mut y := details_y + 15
283 ctx.draw_text(20, int(y), ptr_str, color: text_color, size: 16)
284
285 ctx.draw_text(250, int(y), size_str, color: text_color, size: 16)
286
287 ctx.draw_text(500, int(y), frame_str, color: text_color, size: 16)
288
289 ctx.draw_text(700, int(y), status_str, color: status_color, size: 16)
290
291 // Source location if available
292 if alloc.file.len > 0 {
293 y += 25
294 loc_str := 'Source: ${alloc.file}:${alloc.line}'
295 ctx.draw_text(20, int(y), loc_str, color: new_alloc_color, size: 16)
296 }
297 }
298 } else {
299 // Instructions
300 ctx.draw_text(20, int(details_y) + 20,
301 'Click on a histogram bar to select a frame, or use keyboard arrows to navigate',
302 color: text_color
303 size: 16
304 )
305 }
306}
307
308// draw_frame is the main frame callback
309pub fn draw_frame(mut app App) {
310 if app.gg == unsafe { nil } {
311 return
312 }
313 app.gg.begin()
314
315 // Update data
316 if app.demo_mode {
317 update_demo_data(mut app)
318 } else {
319 app.update_cache()
320 }
321
322 // Draw all sections
323 draw_header(mut app)
324 draw_histogram(mut app)
325 draw_timeline(mut app)
326 draw_controls(mut app)
327 draw_details(mut app)
328
329 // Draw status message if in live mode with no data
330 if app.live_mode && app.cached_frames.len == 0 {
331 ctx := app.gg
332 w := f32(ctx.width)
333 h := f32(ctx.height)
334 ctx.draw_text(int(w / 2) - 200, int(h / 2), app.status_msg,
335 color: text_color
336 size: 20
337 )
338 }
339
340 app.gg.end()
341}
342
343// Helper: draw a filled triangle
344fn draw_triangle_filled(ctx &gg.Context, x1 f32, y1 f32, x2 f32, y2 f32, x3 f32, y3 f32, c gg.Color) {
345 ctx.draw_convex_poly([x1, y1, x2, y2, x3, y3], c)
346}
347