|
| 1 | +"""Live profiling collector that displays top-like statistics using curses. |
| 2 | +
|
| 3 | + ┌─────────────────────────────┐ |
| 4 | + │ Target Python Process │ |
| 5 | + │ (being profiled) │ |
| 6 | + └──────────────┬──────────────┘ |
| 7 | + │ Stack sampling at |
| 8 | + │ configured interval |
| 9 | + │ (e.g., 10000µs) |
| 10 | + ▼ |
| 11 | + ┌─────────────────────────────┐ |
| 12 | + │ LiveStatsCollector │ |
| 13 | + │ ┌───────────────────────┐ │ |
| 14 | + │ │ collect() │ │ Aggregates samples |
| 15 | + │ │ - Iterates frames │ │ into statistics |
| 16 | + │ │ - Updates counters │ │ |
| 17 | + │ └───────────┬───────────┘ │ |
| 18 | + │ │ │ |
| 19 | + │ ▼ │ |
| 20 | + │ ┌───────────────────────┐ │ |
| 21 | + │ │ Data Storage │ │ |
| 22 | + │ │ - result dict │ │ Tracks per-function: |
| 23 | + │ │ - direct_calls │ │ • Direct samples |
| 24 | + │ │ - cumulative_calls │ │ • Cumulative samples |
| 25 | + │ └───────────┬───────────┘ │ • Derived time stats |
| 26 | + │ │ │ |
| 27 | + │ ▼ │ |
| 28 | + │ ┌───────────────────────┐ │ |
| 29 | + │ │ Display Update │ │ |
| 30 | + │ │ (10Hz by default) │ │ Rate-limited refresh |
| 31 | + │ └───────────┬───────────┘ │ |
| 32 | + └──────────────┼──────────────┘ |
| 33 | + │ |
| 34 | + ▼ |
| 35 | + ┌─────────────────────────────┐ |
| 36 | + │ DisplayInterface │ |
| 37 | + │ (Abstract layer) │ |
| 38 | + └──────────────┬──────────────┘ |
| 39 | + ┌───────┴────────┐ |
| 40 | + │ │ |
| 41 | + ┌──────────▼────────┐ ┌───▼──────────┐ |
| 42 | + │ CursesDisplay │ │ MockDisplay │ |
| 43 | + │ - Real terminal │ │ - Testing │ |
| 44 | + │ - ncurses backend │ │ - No UI │ |
| 45 | + └─────────┬─────────┘ └──────────────┘ |
| 46 | + │ |
| 47 | + ▼ |
| 48 | + ┌─────────────────────────────────────┐ |
| 49 | + │ Widget-Based Rendering │ |
| 50 | + │ ┌─────────────────────────────────┐ │ |
| 51 | + │ │ HeaderWidget │ │ |
| 52 | + │ │ • PID, uptime, time, interval │ │ |
| 53 | + │ │ • Sample stats & progress bar │ │ |
| 54 | + │ │ • Efficiency bar │ │ |
| 55 | + │ │ • Thread status & GC stats │ │ |
| 56 | + │ │ • Function summary │ │ |
| 57 | + │ │ • Top 3 hottest functions │ │ |
| 58 | + │ ├─────────────────────────────────┤ │ |
| 59 | + │ │ TableWidget │ │ |
| 60 | + │ │ • Column headers (sortable) │ │ Interactive display |
| 61 | + │ │ • Stats rows (scrolling) │ │ with keyboard controls: |
| 62 | + │ │ - nsamples % time │ │ s: sort, p: pause |
| 63 | + │ │ - function file:line │ │ r: reset, /: filter |
| 64 | + │ ├─────────────────────────────────┤ │ q: quit, h: help |
| 65 | + │ │ FooterWidget │ │ |
| 66 | + │ │ • Legend and status │ │ |
| 67 | + │ │ • Filter input prompt │ │ |
| 68 | + │ └─────────────────────────────────┘ │ |
| 69 | + └─────────────────────────────────────┘ |
| 70 | +
|
| 71 | +Architecture: |
| 72 | +
|
| 73 | +The live collector is organized into four layers. The data collection layer |
| 74 | +(LiveStatsCollector) aggregates stack samples into per-function statistics without |
| 75 | +any knowledge of how they will be presented. The display abstraction layer |
| 76 | +(DisplayInterface) defines rendering operations without coupling to curses or any |
| 77 | +specific UI framework. The widget layer (Widget, HeaderWidget, TableWidget, |
| 78 | +FooterWidget, HelpWidget, ProgressBarWidget) encapsulates individual UI components |
| 79 | +with their own rendering logic, promoting modularity and reusability. The |
| 80 | +presentation layer (CursesDisplay/MockDisplay) implements the actual rendering for |
| 81 | +terminal output and testing. |
| 82 | +
|
| 83 | +The system runs two independent update loops. The sampling loop is driven by the |
| 84 | +profiler at the configured interval (e.g., 10000µs) and continuously collects |
| 85 | +stack frames and updates statistics. The display loop runs at a fixed refresh rate |
| 86 | +(default 10Hz) and updates the terminal independently of sampling frequency. This |
| 87 | +separation allows high-frequency sampling without overwhelming the terminal with |
| 88 | +constant redraws. |
| 89 | +
|
| 90 | +Statistics are computed incrementally as samples arrive. The collector maintains |
| 91 | +running counters (direct calls and cumulative calls) in a dictionary keyed by |
| 92 | +function location. Derived metrics like time estimates and percentages are computed |
| 93 | +on-demand during display updates rather than being stored, which minimizes memory |
| 94 | +overhead as the number of tracked functions grows. |
| 95 | +
|
| 96 | +User input is processed asynchronously during display updates using non-blocking I/O. |
| 97 | +This allows interactive controls (sorting, filtering, pausing) without interrupting |
| 98 | +the data collection pipeline. The collector maintains mode flags (paused, |
| 99 | +filter_input_mode) that affect what gets displayed but not what gets collected. |
| 100 | +
|
| 101 | +""" |
| 102 | + |
| 103 | +# Re-export all public classes and constants for backward compatibility |
| 104 | +from .collector import LiveStatsCollector |
| 105 | +from .display import DisplayInterface, CursesDisplay, MockDisplay |
| 106 | +from .widgets import ( |
| 107 | + Widget, |
| 108 | + ProgressBarWidget, |
| 109 | + HeaderWidget, |
| 110 | + TableWidget, |
| 111 | + FooterWidget, |
| 112 | + HelpWidget, |
| 113 | +) |
| 114 | +from .constants import ( |
| 115 | + MICROSECONDS_PER_SECOND, |
| 116 | + DISPLAY_UPDATE_HZ, |
| 117 | + DISPLAY_UPDATE_INTERVAL, |
| 118 | + MIN_TERMINAL_WIDTH, |
| 119 | + MIN_TERMINAL_HEIGHT, |
| 120 | + WIDTH_THRESHOLD_SAMPLE_PCT, |
| 121 | + WIDTH_THRESHOLD_TOTTIME, |
| 122 | + WIDTH_THRESHOLD_CUMUL_PCT, |
| 123 | + WIDTH_THRESHOLD_CUMTIME, |
| 124 | + HEADER_LINES, |
| 125 | + FOOTER_LINES, |
| 126 | + SAFETY_MARGIN, |
| 127 | + TOP_FUNCTIONS_DISPLAY_COUNT, |
| 128 | + COL_WIDTH_NSAMPLES, |
| 129 | + COL_SPACING, |
| 130 | + COL_WIDTH_SAMPLE_PCT, |
| 131 | + COL_WIDTH_TIME, |
| 132 | + MIN_FUNC_NAME_WIDTH, |
| 133 | + MAX_FUNC_NAME_WIDTH, |
| 134 | + MIN_AVAILABLE_SPACE, |
| 135 | + MIN_BAR_WIDTH, |
| 136 | + MAX_SAMPLE_RATE_BAR_WIDTH, |
| 137 | + MAX_EFFICIENCY_BAR_WIDTH, |
| 138 | + MIN_SAMPLE_RATE_FOR_SCALING, |
| 139 | + FINISHED_BANNER_EXTRA_LINES, |
| 140 | + COLOR_PAIR_HEADER_BG, |
| 141 | + COLOR_PAIR_CYAN, |
| 142 | + COLOR_PAIR_YELLOW, |
| 143 | + COLOR_PAIR_GREEN, |
| 144 | + COLOR_PAIR_MAGENTA, |
| 145 | + COLOR_PAIR_RED, |
| 146 | + COLOR_PAIR_SORTED_HEADER, |
| 147 | + DEFAULT_SORT_BY, |
| 148 | + DEFAULT_DISPLAY_LIMIT, |
| 149 | +) |
| 150 | + |
| 151 | +__all__ = [ |
| 152 | + # Main collector |
| 153 | + "LiveStatsCollector", |
| 154 | + # Display interfaces |
| 155 | + "DisplayInterface", |
| 156 | + "CursesDisplay", |
| 157 | + "MockDisplay", |
| 158 | + # Widgets |
| 159 | + "Widget", |
| 160 | + "ProgressBarWidget", |
| 161 | + "HeaderWidget", |
| 162 | + "TableWidget", |
| 163 | + "FooterWidget", |
| 164 | + "HelpWidget", |
| 165 | + # Constants |
| 166 | + "MICROSECONDS_PER_SECOND", |
| 167 | + "DISPLAY_UPDATE_HZ", |
| 168 | + "DISPLAY_UPDATE_INTERVAL", |
| 169 | + "MIN_TERMINAL_WIDTH", |
| 170 | + "MIN_TERMINAL_HEIGHT", |
| 171 | + "WIDTH_THRESHOLD_SAMPLE_PCT", |
| 172 | + "WIDTH_THRESHOLD_TOTTIME", |
| 173 | + "WIDTH_THRESHOLD_CUMUL_PCT", |
| 174 | + "WIDTH_THRESHOLD_CUMTIME", |
| 175 | + "HEADER_LINES", |
| 176 | + "FOOTER_LINES", |
| 177 | + "SAFETY_MARGIN", |
| 178 | + "TOP_FUNCTIONS_DISPLAY_COUNT", |
| 179 | + "COL_WIDTH_NSAMPLES", |
| 180 | + "COL_SPACING", |
| 181 | + "COL_WIDTH_SAMPLE_PCT", |
| 182 | + "COL_WIDTH_TIME", |
| 183 | + "MIN_FUNC_NAME_WIDTH", |
| 184 | + "MAX_FUNC_NAME_WIDTH", |
| 185 | + "MIN_AVAILABLE_SPACE", |
| 186 | + "MIN_BAR_WIDTH", |
| 187 | + "MAX_SAMPLE_RATE_BAR_WIDTH", |
| 188 | + "MAX_EFFICIENCY_BAR_WIDTH", |
| 189 | + "MIN_SAMPLE_RATE_FOR_SCALING", |
| 190 | + "FINISHED_BANNER_EXTRA_LINES", |
| 191 | + "COLOR_PAIR_HEADER_BG", |
| 192 | + "COLOR_PAIR_CYAN", |
| 193 | + "COLOR_PAIR_YELLOW", |
| 194 | + "COLOR_PAIR_GREEN", |
| 195 | + "COLOR_PAIR_MAGENTA", |
| 196 | + "COLOR_PAIR_RED", |
| 197 | + "COLOR_PAIR_SORTED_HEADER", |
| 198 | + "DEFAULT_SORT_BY", |
| 199 | + "DEFAULT_DISPLAY_LIMIT", |
| 200 | +] |
0 commit comments