v / cmd / v2 / guiprof / state.v
176 lines · 157 sloc · 4.85 KB · a76973f101edbe08f2df8b6ee19784ede8bfe966
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// Filter modes for allocation view
10pub enum FilterMode {
11 all // Show all allocations
12 new_not_freed // Show allocations that haven't been freed (potential leaks)
13 large // Show allocations larger than threshold
14 current_frame // Show allocations from selected frame only
15}
16
17// App holds the GUI application state
18pub struct App {
19pub mut:
20 gg &gg.Context = unsafe { nil }
21 // View state
22 selected_frame int = -1 // -1 means no selection
23 selected_alloc int = -1 // Index of selected allocation
24 filter_mode FilterMode = .all
25 large_threshold int = 1024 // Threshold for "large" filter (bytes)
26 search_text string
27 // Scroll/pan state
28 timeline_scroll int // Horizontal scroll for timeline
29 histogram_scroll int // Scroll for histogram if needed
30 // Cached data (updated each frame)
31 cached_frames []profiler.FrameData
32 cached_allocs []profiler.AllocRecord
33 cached_stats profiler.Statistics
34 // Layout dimensions
35 header_height int = 60
36 histogram_height int = 200
37 timeline_height int = 80
38 controls_height int = 40
39 details_height int = 60
40 // Hover state
41 hover_frame int = -1
42 hover_alloc int = -1
43 // Mode
44 demo_mode bool // Use simulated data for demo (false = live mode)
45 demo_frame int // Current demo frame counter
46 live_mode bool = true // Read from profiler snapshot file
47 // Status
48 status_msg string = 'Waiting for profiler data...'
49}
50
51// Colors for the profiler visualization
52pub const bg_color = gg.Color{30, 30, 30, 255} // Dark gray background
53pub const new_alloc_color = gg.Color{0, 200, 255, 200} // Cyan for new allocations
54pub const freed_color = gg.Color{180, 100, 255, 200} // Purple for freed allocations
55pub const leak_color = gg.Color{255, 100, 100, 255} // Red for leaks/live
56pub const timeline_cursor = gg.Color{255, 255, 0, 255} // Yellow for timeline cursor
57pub const text_color = gg.Color{220, 220, 220, 255} // Light gray text
58pub const header_bg = gg.Color{40, 40, 45, 255} // Slightly lighter header
59pub const grid_color = gg.Color{60, 60, 60, 255} // Grid lines
60pub const selected_color = gg.Color{100, 150, 255, 255} // Selection highlight
61
62// Window dimensions
63pub const window_width = 1200
64pub const window_height = 700
65
66// init_app creates a new App instance (unused, kept for reference)
67fn init_app_heap() &App {
68 return &App{}
69}
70
71// update_cache refreshes cached profiler data
72pub fn (mut app App) update_cache() {
73 if app.demo_mode {
74 return
75 }
76
77 if app.live_mode {
78 // Read from snapshot file
79 if snap := profiler.read_snapshot() {
80 app.cached_frames = snap.frames
81 app.cached_allocs = snap.allocs
82 app.cached_stats = profiler.Statistics{
83 live_bytes: snap.live_bytes
84 peak_bytes: snap.peak_bytes
85 total_allocs: snap.total_allocs
86 total_frees: snap.total_frees
87 leak_count: count_leaks(snap.allocs)
88 frame_count: snap.frame_count
89 }
90 app.status_msg = 'Live: Frame ${snap.frame_count}'
91 } else {
92 app.status_msg = 'Waiting for profiler data at ${profiler.profiler_data_path}...'
93 }
94 return
95 }
96
97 // In-process mode (same process)
98 app.cached_frames = profiler.get_frames()
99 app.cached_allocs = profiler.get_allocs()
100 app.cached_stats = profiler.get_statistics()
101}
102
103fn count_leaks(allocs []profiler.AllocRecord) int {
104 mut count := 0
105 for alloc in allocs {
106 if !alloc.freed {
107 count++
108 }
109 }
110 return count
111}
112
113// get_filtered_allocs returns allocations matching current filter
114pub fn (app &App) get_filtered_allocs() []profiler.AllocRecord {
115 mut result := []profiler.AllocRecord{}
116
117 allocs := app.cached_allocs
118
119 for alloc in allocs {
120 match app.filter_mode {
121 .all {
122 result << alloc
123 }
124 .new_not_freed {
125 if !alloc.freed {
126 result << alloc
127 }
128 }
129 .large {
130 if alloc.size >= app.large_threshold {
131 result << alloc
132 }
133 }
134 .current_frame {
135 if app.selected_frame >= 0 && alloc.frame == u64(app.selected_frame) {
136 result << alloc
137 }
138 }
139 }
140 }
141
142 // Apply search filter if text is present
143 if app.search_text.len > 0 {
144 mut filtered := []profiler.AllocRecord{}
145 for alloc in result {
146 if alloc.file.contains(app.search_text) {
147 filtered << alloc
148 }
149 }
150 return filtered
151 }
152
153 return result
154}
155
156// get_frame_at_x returns the frame index at the given x coordinate in timeline
157pub fn (app &App) get_frame_at_x(x f32, timeline_x f32, timeline_width f32) int {
158 frames := app.cached_frames
159 if frames.len == 0 {
160 return -1
161 }
162
163 // Calculate which frame this x position corresponds to
164 relative_x := x - timeline_x
165 if relative_x < 0 || relative_x > timeline_width {
166 return -1
167 }
168
169 frame_width := timeline_width / f32(frames.len)
170 frame_idx := int(relative_x / frame_width)
171
172 if frame_idx >= 0 && frame_idx < frames.len {
173 return frame_idx
174 }
175 return -1
176}
177