Scalene-Debugging.md
The CPU signal handler (cpu_signal_handler) receives this_frame as the raw frame parameter. The compute_frames_to_record() function filters this down to user-only frames via _should_trace().
Critical gotcha: When the main thread is idle in the event loop (the exact case async profiling needs to detect), there are NO user frames — asyncio/selector frames are all filtered out. So frames from compute_frames_to_record() is empty. Must use this_frame directly for event loop detection.
To verify async profiling is working:
python3 -m scalene run --async test/test_async_demo.pypython3 -m scalene view --cli should show Await % columnTo debug zero-await-data:
is_in_event_loop() returns True when event loop is idle_poll_suspended_tasks() finds tasksthis_frame, not filtered framesThere are three separate renderers for profile output. All must be updated when adding new columns:
scalene_json.py:output_profiles() → output_profile_line()scalene_parseargs.py:_display_profile_cli() — used by scalene view --cliscalene_output.py:output_profiles() — used by scalene view --htmlscalene-gui.ts:makeProfileLine() → embedded Vega-Lite charts via vegaEmbed()scalene_utility.py:generate_html(standalone=True) embeds all assets inlineNote: _display_profile_cli() in scalene_parseargs.py is completely separate from scalene_output.py. This is easy to miss.
Any dict or set that accumulates per-sample data must be bounded:
async_task_names capped at 100 per location)._suspended_tasks): Must be capped and cleared when exceeded.RunningStats: Fixed-size (count, mean, M2) — OK.ScaleneSigQueue: Uses SimpleQueue with continuous consumer drain — OK.