| 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. |
| 4 | module main |
| 5 | |
| 6 | import gg |
| 7 | import os |
| 8 | import v2.profiler |
| 9 | |
| 10 | fn main() { |
| 11 | // Initialize profiler system |
| 12 | profiler.profiler_init() |
| 13 | |
| 14 | // Create application state (on stack, like the working example) |
| 15 | mut app := App{} |
| 16 | |
| 17 | // Check command line args for mode |
| 18 | args := os.args |
| 19 | if '--demo' in args { |
| 20 | app.demo_mode = true |
| 21 | app.live_mode = false |
| 22 | init_demo_data(mut app) |
| 23 | } else { |
| 24 | // Default to live mode - read from snapshot file |
| 25 | app.demo_mode = false |
| 26 | app.live_mode = true |
| 27 | } |
| 28 | |
| 29 | // Create gg context |
| 30 | app.gg = gg.new_context( |
| 31 | width: window_width |
| 32 | height: window_height |
| 33 | create_window: true |
| 34 | window_title: 'V2 Allocation Profiler' |
| 35 | bg_color: bg_color |
| 36 | frame_fn: frame |
| 37 | event_fn: event_handler |
| 38 | user_data: &app |
| 39 | ) |
| 40 | |
| 41 | // Run the application |
| 42 | app.gg.run() |
| 43 | } |
| 44 | |
| 45 | // frame is the frame callback for gg |
| 46 | fn frame(mut app App) { |
| 47 | draw_frame(mut app) |
| 48 | } |
| 49 | |
| 50 | // event_handler handles gg events |
| 51 | fn event_handler(e &gg.Event, mut app App) { |
| 52 | handle_event(e, mut app) |
| 53 | } |
| 54 | |
| 55 | // init_demo_data populates the profiler with simulated data for demonstration |
| 56 | fn init_demo_data(mut app App) { |
| 57 | app.demo_mode = true |
| 58 | app.demo_frame = 0 |
| 59 | |
| 60 | // Create simulated frame and allocation data |
| 61 | app.cached_frames = []profiler.FrameData{cap: 100} |
| 62 | app.cached_allocs = []profiler.AllocRecord{cap: 500} |
| 63 | |
| 64 | // Simulate 100 frames of allocation activity |
| 65 | mut total_live := u64(0) |
| 66 | mut peak := u64(0) |
| 67 | mut alloc_idx := 0 |
| 68 | mut total_allocs := u64(0) |
| 69 | mut total_frees := u64(0) |
| 70 | |
| 71 | for frame_num := 0; frame_num < 100; frame_num++ { |
| 72 | mut frame_data := profiler.FrameData{ |
| 73 | frame_num: u64(frame_num) |
| 74 | } |
| 75 | |
| 76 | // Simulate some allocations each frame |
| 77 | // More allocations early, fewer later (typical startup pattern) |
| 78 | num_allocs := if frame_num < 20 { 10 - frame_num / 4 } else { 2 + (frame_num % 3) } |
| 79 | |
| 80 | for _ in 0 .. num_allocs { |
| 81 | // Random-ish allocation sizes |
| 82 | size := 64 + ((frame_num * 17 + alloc_idx * 31) % 4096) |
| 83 | |
| 84 | app.cached_allocs << profiler.AllocRecord{ |
| 85 | ptr: unsafe { voidptr(u64(0x1000) + u64(alloc_idx) * 0x100) } |
| 86 | size: size |
| 87 | frame: u64(frame_num) |
| 88 | file: ['main.v', 'parser.v', 'scanner.v', 'types.v', 'gen.v'][alloc_idx % 5] |
| 89 | line: 100 + (alloc_idx % 500) |
| 90 | timestamp: i64(frame_num) * 16_666_667 + i64(alloc_idx) * 1000 |
| 91 | freed: false |
| 92 | } |
| 93 | |
| 94 | frame_data.new_allocs << alloc_idx |
| 95 | frame_data.new_bytes += u64(size) |
| 96 | total_live += u64(size) |
| 97 | total_allocs++ |
| 98 | alloc_idx++ |
| 99 | } |
| 100 | |
| 101 | // Simulate some frees (but not all - create some "leaks") |
| 102 | // Free some allocations from previous frames |
| 103 | if frame_num > 5 { |
| 104 | num_frees := num_allocs - 1 // Always free slightly fewer than we allocate |
| 105 | mut freed_count := 0 |
| 106 | for i := 0; i < app.cached_allocs.len && freed_count < num_frees; i++ { |
| 107 | if !app.cached_allocs[i].freed && app.cached_allocs[i].frame < u64(frame_num - 3) { |
| 108 | // 80% chance to free |
| 109 | if (i * 7 + frame_num * 13) % 10 < 8 { |
| 110 | app.cached_allocs[i].freed = true |
| 111 | app.cached_allocs[i].free_frame = u64(frame_num) |
| 112 | frame_data.freed_idxs << i |
| 113 | frame_data.freed_bytes += u64(app.cached_allocs[i].size) |
| 114 | total_live -= u64(app.cached_allocs[i].size) |
| 115 | total_frees++ |
| 116 | freed_count++ |
| 117 | } |
| 118 | } |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | frame_data.live_bytes = total_live |
| 123 | if total_live > peak { |
| 124 | peak = total_live |
| 125 | } |
| 126 | |
| 127 | app.cached_frames << frame_data |
| 128 | } |
| 129 | |
| 130 | // Count leaks |
| 131 | mut leak_count := 0 |
| 132 | for alloc in app.cached_allocs { |
| 133 | if !alloc.freed { |
| 134 | leak_count++ |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | // Update stats |
| 139 | app.cached_stats = profiler.Statistics{ |
| 140 | live_bytes: total_live |
| 141 | peak_bytes: peak |
| 142 | total_allocs: total_allocs |
| 143 | total_frees: total_frees |
| 144 | leak_count: leak_count |
| 145 | frame_count: 100 |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | // update_demo_data simulates ongoing activity in demo mode |
| 150 | fn update_demo_data(mut app App) { |
| 151 | // In demo mode, we just use the static simulated data |
| 152 | // For a more dynamic demo, we could add new frames here |
| 153 | app.demo_frame++ |
| 154 | } |
| 155 | |