{"owner":"plasma-umass","repo":"scalene","hasSkills":true,"totalSkillsCount":2,"totalTokensCount":11205,"categories":["subagent-persona","claude-rule"],"hasMcp":false,"mcpConfig":null,"found":["agents/windows-memory-profiling-port.md","CLAUDE.md"],"skills":{"agents/windows-memory-profiling-port.md":"# Windows Memory Profiling Port - Progress and Plans\n\n## Overview\n\nPorting Scalene's memory profiling from Linux/macOS to Windows. On Unix systems, Scalene uses `LD_PRELOAD`/`DYLD_INSERT_LIBRARIES` to interpose on malloc/free. Windows doesn't support this mechanism, so we use Python's allocator API instead.\n\n## Architecture\n\n### Unix Approach\n- `libscalene.so`/`libscalene.dylib` is preloaded via environment variables\n- Interposes on malloc/free/realloc at the C library level\n- Uses Unix signals (SIGXCPU, SIGXFSZ) to communicate with Python\n- Uses POSIX shared memory (`/tmp/scalene-*`) for data transfer\n\n### Windows Approach\n- `libscalene.dll` is loaded explicitly via ctypes\n- Uses Python's `PyMem_SetAllocator` API to intercept allocations\n- Uses Windows Events instead of Unix signals (but currently using polling)\n- Uses Windows Named Shared Memory (`Local\\scalene-*`) for data transfer\n\n## Files Created/Modified\n\n### New Files\n- `src/source/libscalene_windows.cpp` - Main Windows DLL implementation (includes Detours native hooks)\n- `src/include/samplefile_win.hpp` - Windows shared memory implementation\n- `src/include/common_win.hpp` - Windows compatibility macros\n- `src/include/mallocrecursionguard_win.hpp` - Windows TLS-based recursion guard\n- `scalene/scalene_windows.py` - Python helper for Windows memory profiling\n- `vendor/Detours/` - Microsoft Detours library for native malloc/free hooking (vendored)\n\n### Modified Files\n- `CMakeLists.txt` - Cross-platform build configuration with Detours integration\n- `src/include/pywhere.hpp` - Windows DLL export/import macros\n- `src/source/pywhere.cpp` - Windows symbol lookup via accessor functions\n- `scalene/scalene_profiler.py` - Windows DLL loading and initialization\n- `scalene/scalene_mapfile.py` - Windows named shared memory support\n- `scalene/scalene_signal_manager.py` - Windows memory polling thread\n- `scalene/scalene_preload.py` - Case-insensitive ARM64 detection\n- `scalene/scalene_arguments.py` - Enable memory profiling on Windows\n\n## Current Status (2024-12-17) - FULLY WORKING WITH NATIVE LIBRARY SUPPORT!\n\n### All Core Features Working\n1. ✅ DLL builds successfully with CMake/MSBuild for ARM64\n2. ✅ DLL loads without crashing\n3. ✅ Python allocator hooks are installed via `PyMem_SetAllocator`\n4. ✅ Allocation size tracking implemented (hash map with critical section)\n5. ✅ Shared memory objects created successfully (both malloc and memcpy)\n6. ✅ ScaleneMapFile opens shared memory from Python side successfully\n7. ✅ All `thread_local` variables removed (caused crashes with dynamically loaded DLLs on Windows)\n8. ✅ Data format updated to match Unix (comma-separated, 9 fields for malloc, 6 for memcpy)\n9. ✅ Windows memory polling thread added to `scalene_signal_manager.py`\n10. ✅ **64-bit pointer handling fixed in ctypes code**\n11. ✅ Memory profiling working - samples being collected and logged\n12. ✅ **Native library tracking (numpy, etc.) via Microsoft Detours**\n13. ✅ **Proper free tracking with manual size tracking hash map**\n\n### Test Results (2024-12-17)\n```\nalloc_samples: 203\nmax_footprint_mb: 152.71\nmax_footprint_python_fraction: 0.000288\nNative fraction: 99.97%\n```\n\nThe profiler now correctly tracks memory allocations from native libraries like numpy, with proper line attribution.\n\n### Known Limitation\n- Console output may fail with `UnicodeEncodeError` on Windows consoles using CP1252 encoding\n- This is not a profiler bug - it's a Windows console limitation\n- **Workaround**: Set `PYTHONIOENCODING=utf-8` environment variable or use `--html` or `--json` output\n\n## The Critical Bug Fix (2024-12-16)\n\n### Root Cause: ctypes 64-bit Pointer Truncation\n\nThe Access Violation crash (exit code 3221225477 = 0xC0000005) was caused by a **ctypes bug** where 64-bit pointers were being truncated to 32-bit values.\n\n**Problem**: By default, ctypes assumes Windows API functions return `c_int` (32-bit). On 64-bit Windows, `MapViewOfFile` returns a 64-bit pointer. Without explicit return type declaration, the high 32 bits were truncated, causing invalid memory addresses.\n\n**Example of bug**:\n```\nMapViewOfFile returns: 0x00007FFBF2650B90  (valid 64-bit pointer)\nctypes stored as:      0x00000000F2650B90  (truncated to 32-bit, invalid!)\n```\n\n### The Fix (scalene_mapfile.py)\n\nAdded proper return type declarations before calling Windows API functions:\n\n```python\nfrom ctypes import wintypes\n\nkernel32 = ctypes.windll.kernel32\n\n# IMPORTANT: Set proper return types for Windows API functions\n# Default ctypes return type is c_int (32-bit) which truncates 64-bit pointers\nkernel32.OpenFileMappingW.restype = wintypes.HANDLE\nkernel32.MapViewOfFile.restype = ctypes.c_void_p\nkernel32.UnmapViewOfFile.argtypes = [ctypes.c_void_p]\nkernel32.CloseHandle.argtypes = [wintypes.HANDLE]\n```\n\nThis fix is in `scalene/scalene_mapfile.py` in the `_init_windows()` method.\n\n## Native Library Memory Tracking (2024-12-17)\n\n### The Problem\n\nThe original Windows implementation only tracked Python allocations via `PyMem_SetAllocator`. Native library allocations (numpy arrays, pandas dataframes, etc.) went untracked because they bypass Python's allocator and call the C runtime `malloc`/`free` directly.\n\n**Before**: Only Python allocations tracked (0.03% of actual memory usage)\n**After**: All allocations tracked including native libraries (99.97% native, 0.03% Python)\n\n### Solution: Microsoft Detours\n\nWe use [Microsoft Detours](https://github.com/microsoft/Detours) to intercept `malloc`/`free`/`realloc`/`calloc` at the C runtime level. Detours works by rewriting the first few bytes of target functions with a jump to our hooks.\n\n**Why Detours over alternatives:**\n- **MinHook**: Does NOT support ARM64 (only x86/x64)\n- **IAT Hooking**: Complex, requires hooking each loaded module separately\n- **ETW (Event Tracing)**: Requires admin privileges, high overhead\n- **Detours**: Supports ARM64, x64, x86, ARM - official Microsoft library, MIT licensed\n\n### Implementation\n\n#### Files Modified/Added\n- `vendor/Detours/` - Microsoft Detours source (vendored)\n- `CMakeLists.txt` - Added Detours to Windows build with architecture-specific disassembler\n- `src/source/libscalene_windows.cpp` - Added native hooks using Detours\n\n#### CMakeLists.txt Changes\n```cmake\n# Microsoft Detours sources for native malloc/free hooking\nset(DETOURS_SOURCES\n    vendor/Detours/src/detours.cpp\n    vendor/Detours/src/modules.cpp\n    vendor/Detours/src/disasm.cpp\n    vendor/Detours/src/image.cpp\n    vendor/Detours/src/creatwth.cpp\n)\n\n# Add architecture-specific disassembler\nif(SCALENE_ARCH STREQUAL \"ARM64\")\n    list(APPEND DETOURS_SOURCES vendor/Detours/src/disolarm64.cpp)\nelseif(SCALENE_ARCH STREQUAL \"X64\")\n    list(APPEND DETOURS_SOURCES vendor/Detours/src/disolx64.cpp)\nelseif(SCALENE_ARCH STREQUAL \"X86\")\n    list(APPEND DETOURS_SOURCES vendor/Detours/src/disolx86.cpp)\nendif()\n\ntarget_compile_definitions(scalene PRIVATE DETOURS_INTERNAL)\n```\n\n#### Native Hook Implementation\n```cpp\n#include \"detours.h\"\n\n// Original function pointers (Detours fills these with trampolines)\nstatic void* (__cdecl *Real_malloc)(size_t) = malloc;\nstatic void (__cdecl *Real_free)(void*) = free;\nstatic void* (__cdecl *Real_realloc)(void*, size_t) = realloc;\nstatic void* (__cdecl *Real_calloc)(size_t, size_t) = calloc;\n\n// Recursion guard - CRITICAL for preventing infinite loops\nstatic bool g_in_native_hook = false;\n\n// Coordination with Python allocator hooks\nstatic bool g_in_python_allocator = false;\n\nstatic void* __cdecl Hooked_malloc(size_t size) {\n    // Check recursion guard FIRST - track_native_alloc may call malloc internally\n    if (g_in_native_hook || g_in_python_allocator) {\n        return Real_malloc(size);\n    }\n\n    g_in_native_hook = true;\n    void* ptr = Real_malloc(size);\n    if (ptr) {\n        track_native_alloc(ptr, size);\n        if (!p_scalene_done) {\n            TheHeapWrapper::register_malloc(size, ptr, false);  // false = native\n        }\n    }\n    g_in_native_hook = false;\n    return ptr;\n}\n\nbool install_native_hooks() {\n    DetourRestoreAfterWith();\n    DetourTransactionBegin();\n    DetourUpdateThread(GetCurrentThread());\n\n    DetourAttach(&(PVOID&)Real_malloc, Hooked_malloc);\n    DetourAttach(&(PVOID&)Real_free, Hooked_free);\n    DetourAttach(&(PVOID&)Real_realloc, Hooked_realloc);\n    DetourAttach(&(PVOID&)Real_calloc, Hooked_calloc);\n\n    return DetourTransactionCommit() == NO_ERROR;\n}\n```\n\n### Critical Bug Fix: Recursion Guard Order\n\n**Problem**: The native hooks use `std::unordered_map` for size tracking, which internally calls `malloc`. If the recursion guard was checked AFTER calling tracking functions, infinite recursion occurred:\n\n1. User code calls `malloc`\n2. `Hooked_malloc` is called\n3. `track_native_alloc(ptr, size)` is called (hash map insert)\n4. Hash map internally calls `malloc` for bucket allocation\n5. `Hooked_malloc` is called again (recursion!)\n6. Since `g_in_native_hook` wasn't set yet, we recurse infinitely → stack overflow\n\n**Fix**: Check and set `g_in_native_hook` at the VERY BEGINNING of each hook, BEFORE any operations that might allocate:\n\n```cpp\nstatic void* __cdecl Hooked_malloc(size_t size) {\n    // MUST check recursion guard FIRST\n    if (g_in_native_hook || g_in_python_allocator) {\n        return Real_malloc(size);  // Bypass hook\n    }\n\n    g_in_native_hook = true;  // Set BEFORE any allocating operations\n    // ... rest of hook logic ...\n    g_in_native_hook = false;\n    return ptr;\n}\n```\n\n### Free Size Tracking\n\n**Problem**: `_msize()` doesn't work reliably for custom allocators (numpy uses aligned allocations with `_aligned_malloc`).\n\n**Solution**: Manual size tracking with a hash map:\n\n```cpp\nstatic std::unordered_map<void*, size_t> g_native_alloc_sizes;\nstatic CRITICAL_SECTION g_native_alloc_sizes_lock;\n\nstatic void track_native_alloc(void* ptr, size_t size) {\n    EnterCriticalSection(&g_native_alloc_sizes_lock);\n    g_native_alloc_sizes[ptr] = size;\n    LeaveCriticalSection(&g_native_alloc_sizes_lock);\n}\n\nstatic size_t untrack_native_alloc(void* ptr) {\n    EnterCriticalSection(&g_native_alloc_sizes_lock);\n    auto it = g_native_alloc_sizes.find(ptr);\n    size_t size = (it != g_native_alloc_sizes.end()) ? it->second : 0;\n    if (it != g_native_alloc_sizes.end()) g_native_alloc_sizes.erase(it);\n    LeaveCriticalSection(&g_native_alloc_sizes_lock);\n    return size;\n}\n```\n\n### Coordination Between Python and Native Hooks\n\nTo prevent double-counting allocations that go through both Python's allocator AND the native malloc:\n\n1. Python allocator hooks set `g_in_python_allocator = true` when active\n2. Native malloc hooks check this flag and skip if Python allocator is handling it\n3. Each allocation is counted exactly once\n\n```cpp\n// In Python allocator hook:\nstatic void* scalene_malloc(void* ctx, size_t len) {\n    g_in_python_allocator = true;  // Prevent native hooks\n    void* ptr = g_original_mem_allocator.malloc(...);\n    // ... tracking code ...\n    g_in_python_allocator = false;\n    return ptr;\n}\n```\n\n## Fixes Applied\n\n### 1. **ctypes 64-bit Pointer Fix** (Critical)\n- Added `restype` declarations for all Windows API functions that return handles or pointers\n- `MapViewOfFile.restype = ctypes.c_void_p` - returns memory address\n- `OpenFileMappingW.restype = wintypes.HANDLE` - returns handle\n- Also set `argtypes` for functions taking pointers\n\n### 2. Data Format Fix\nChanged malloc/free output from tab-separated 4-field format to comma-separated 9-field format matching Unix:\n```\nM,alloc_time,count,python_fraction,pid,pointer,filename,lineno,bytei\\n\\n\n```\n\n### 3. Removed `thread_local` Variables\nWindows DLLs loaded with `LoadLibrary`/ctypes don't properly support `thread_local` variables. Changed all to static variables:\n- `mallocSampler` and `memcpySampler`\n- `g_pythonCount`, `g_cCount`\n- `g_lastMallocTrigger`, `g_freedLastMallocTrigger`\n- `g_memcpyOps`\n- `inMalloc` (for recursion guard)\n\n### 4. Added Windows Memory Polling\nSince Unix signals (SIGXCPU/SIGXFSZ) don't exist on Windows, added a polling thread in `scalene_signal_manager.py`:\n- `_windows_memory_poll_loop()` - polls every 10ms\n- Triggers `_alloc_sigqueue_processor` and `_memcpy_sigqueue_processor`\n- Started when memory profiling is enabled on Windows\n\n### 5. Safety Checks in Allocator Hooks\nAdded null checks for original allocator function pointers to prevent crashes if allocation hooks are called before initialization.\n\n## Test Results\n\nRunning the profiler now shows memory profiling data being collected:\n```\nmalloc_calls=113807912, samples=595, logged=595\n```\n\nThe profiler correctly tracks:\n- Python vs native time\n- Memory allocations with line attribution\n- Memory timeline/growth rate\n\n## Build Instructions\n\n```bash\n# Configure with CMake (for ARM64 native)\ncmake -B build -A ARM64 -DPython3_ROOT_DIR=\"C:\\Users\\emery\\AppData\\Local\\Programs\\Python\\Python311-arm64\"\n\n# Build the DLL\nMSBuild.exe build/scalene.sln -p:Configuration=Release -p:Platform=ARM64 -t:scalene\n\n# DLL is automatically placed in scalene\\ directory\n\n# Run profiler\npython -m scalene --cli --cpu --memory test\\testme.py\n```\n\n## Key Differences from Unix Implementation\n\n| Aspect | Unix | Windows |\n|--------|------|---------|\n| Native malloc hooking | LD_PRELOAD | **Microsoft Detours** (inline function hooking) |\n| Python allocator | PyMem_SetAllocator | PyMem_SetAllocator (same) |\n| Signals | SIGXCPU/SIGXFSZ | Polling thread |\n| Shared Memory | /tmp files + mmap | Named shared memory |\n| Symbol Lookup | dlsym(RTLD_DEFAULT) | GetProcAddress + accessor functions |\n| Size Tracking | malloc_usable_size | Manual hash map (\\_msize unreliable) |\n| Thread-local | `thread_local` | Static variables (GIL protects) |\n| Pointer handling | Native 64-bit | **Requires explicit ctypes declarations** |\n| Architecture support | All | ARM64, x64, x86 (via Detours) |\n\n## Learnings\n\n### 1. ctypes Default Return Types Are Dangerous on 64-bit Windows\n- **Always** set `restype` for any Windows API function that returns a handle or pointer\n- Default `c_int` (32-bit) silently truncates 64-bit values\n- This causes delayed crashes that are hard to debug (crash happens when truncated pointer is used, not when it's returned)\n- Use `ctypes.c_void_p` for memory addresses, `wintypes.HANDLE` for handles\n\n### 2. Debugging Methodology That Worked\nThe crash was isolated using **systematic component elimination**:\n1. First confirmed DLL was the cause by renaming it (profiler ran without crash)\n2. Tested with allocator hooks disabled - still crashed (ruled out hooks as cause)\n3. Tested with only shared memory initialization - still crashed (narrowed to shared memory)\n4. Examined shared memory code and found the ctypes issue\n\n### 3. Windows DLL Loading Constraints\n- `thread_local` variables don't work reliably in DLLs loaded via `LoadLibrary`/ctypes\n- Static variables work fine since Python's GIL serializes access\n- DLL entry point (`DllMain`) has severe restrictions - avoid complex initialization there\n\n### 4. Build System Considerations\n- CMake generates Visual Studio solutions that work well for cross-platform builds\n- ARM64 native builds require explicit `-A ARM64` and matching Python version\n- MSBuild can be invoked directly for rebuilds without re-running CMake\n\n### 5. Windows vs Unix IPC\n- Named shared memory on Windows uses `Local\\` prefix for per-session namespace\n- Windows Events can replace Unix signals but polling is simpler for this use case\n- Mutexes on Windows are more heavyweight than Unix futexes\n\n### 6. Recursion Guards in malloc Hooks Must Come FIRST\n- **Critical**: Any code in a malloc hook that uses STL containers (std::unordered_map, std::vector, etc.) may internally call malloc\n- The recursion guard (`g_in_native_hook`) MUST be checked and set BEFORE any other operations\n- Failing to do this causes infinite recursion → stack overflow → exit code 127 or crash\n- Pattern: `if (guard) return original(); guard = true; ... ; guard = false; return result;`\n\n### 7. MinHook Does NOT Support ARM64\n- MinHook is a popular inline hooking library but only supports x86 and x64\n- Microsoft Detours supports ARM64, ARM, x86, x64, and IA64\n- Detours is MIT licensed (open source since 2018) and very well tested\n- Use architecture-specific disassemblers: `disolarm64.cpp` for ARM64, `disolx64.cpp` for x64\n\n### 8. _msize() Is Unreliable for Custom Allocators\n- `_msize()` only works for standard CRT heap allocations\n- numpy and other libraries use aligned allocations (`_aligned_malloc`) which `_msize` doesn't handle\n- Solution: Manual size tracking with a hash map, storing size at allocation time\n- This adds ~10% overhead but is necessary for accurate free tracking\n\n### 9. Coordination Between Multiple Hook Layers\n- When hooking at both Python allocator level AND native malloc level, coordination is essential\n- Use a flag (`g_in_python_allocator`) to prevent native hooks from double-counting Python allocations\n- Pattern: Set flag before calling original allocator, clear after\n- This ensures each allocation is tracked exactly once\n\n## Next Steps\n\n### High Priority\n1. **x64 Build and Testing**: Current implementation tested only on ARM64. Need to verify x64 builds work correctly with Detours.\n\n2. **Unicode Console Output**: The `UnicodeEncodeError` for sparkline characters could be handled more gracefully:\n   - Detect console encoding and fall back to ASCII sparklines\n   - Or auto-set `PYTHONIOENCODING=utf-8` when running on Windows\n\n3. **Performance Optimization**: The polling thread polls every 10ms. Consider:\n   - Using Windows Events for more efficient signaling\n   - Adaptive polling rate based on allocation frequency\n\n### Medium Priority\n4. **Multi-Process Support**: Test and fix any issues with profiling child processes on Windows (the Unix `redirect_python` mechanism may need Windows adaptation).\n\n5. **GPU Profiling on Windows**: Verify NVIDIA GPU profiling works on Windows (pynvml should work but needs testing).\n\n6. **CI/CD Integration**: Add Windows builds to GitHub Actions workflow:\n   - Build DLL for both x64 and ARM64\n   - Run tests on Windows\n   - Include DLL in wheel packages\n\n### Low Priority\n7. **Windows Event-Based Signaling**: Replace polling with proper Windows Events for lower latency and CPU usage:\n   - DLL already creates events (`ScaleneMallocEvent`, etc.)\n   - Python side would use `WaitForMultipleObjects` instead of polling\n\n8. **Memory Leak Detection**: The `--memory-leak-detector` feature may need Windows-specific testing.\n\n9. **Web UI Testing**: Verify the web-based GUI works correctly on Windows (browser launching, port binding).\n\n### Completed\n- ~~**Native Library Memory Tracking**: Implement malloc/free hooking for numpy, pandas, etc.~~ ✅ Done (2024-12-17) - Using Microsoft Detours\n\n## Debugging Tips for Future Issues\n\n### Useful Debug Techniques\n```python\n# Add to scalene_mapfile.py to debug shared memory issues\nprint(f\"Handle value: {handle:#x}\", file=sys.stderr)\nprint(f\"View address: {view:#x}\", file=sys.stderr)\n\n# Check if pointer looks valid (should have high bits set on 64-bit)\nif view < 0x100000000:\n    print(\"WARNING: Pointer may be truncated!\", file=sys.stderr)\n```\n\n### Building with Debug Output\n```cpp\n// In libscalene_windows.cpp, temporarily add:\nfprintf(stderr, \"DEBUG: ptr=%p, size=%zu\\n\", ptr, size);\n```\n\nThen rebuild with:\n```bash\nMSBuild.exe build/scalene.sln -p:Configuration=Release -p:Platform=ARM64 -t:scalene\n```\n\n### Common Windows Error Codes\n- `0xC0000005` (3221225477) - Access Violation (invalid memory access)\n- `0xC0000008` - Invalid Handle\n- `0xC000001D` - Illegal Instruction (wrong architecture)\n- Error 193 - \"Not a valid Win32 application\" (architecture mismatch)\n\n## References\n\n- Python Memory Allocator API: https://docs.python.org/3/c-api/memory.html\n- Windows Named Shared Memory: https://docs.microsoft.com/en-us/windows/win32/memory/creating-named-shared-memory\n- CMake Windows DLL: https://cmake.org/cmake/help/latest/prop_tgt/WINDOWS_EXPORT_ALL_SYMBOLS.html\n- Windows thread_local issues: https://devblogs.microsoft.com/cppblog/c11-thread-local-storage-and-dll-load-failure/\n- **ctypes 64-bit pointers**: https://docs.python.org/3/library/ctypes.html#return-types\n- **Microsoft Detours**: https://github.com/microsoft/Detours - Official Microsoft library for inline function hooking (MIT license)\n- Detours Wiki: https://github.com/microsoft/Detours/wiki - API documentation and examples\n","CLAUDE.md":"# Scalene Development Guide\n\n## Project Overview\n\nScalene is a high-performance CPU, GPU, and memory profiler for Python with AI-powered optimization proposals. It runs significantly faster than other Python profilers while providing detailed performance information. See the paper `docs/osdi23-berger.pdf` for technical details on Scalene's design.\n\n**Key features:**\n- CPU, GPU (NVIDIA/Apple), and memory profiling\n- AI-powered optimization suggestions (OpenAI, Anthropic, Azure, Amazon Bedrock, Gemini, Ollama)\n- Web-based GUI and CLI interfaces\n- Jupyter notebook support via magic commands (`%scrun`, `%%scalene`)\n- Line-by-line profiling with low overhead\n- Separates Python time from native/C time\n\n**Platform support:** Linux, macOS, WSL 2 (full support); Windows (partial support)\n\n## Build & Test Commands\n\n```bash\n# Install in development mode\npip install -e .\n\n# Run all tests\npython3 -m pytest tests/\n\n# Run tests for a specific Python version\npython3.X -m pytest tests/\n\n# Run linters\nmypy scalene\nruff check scalene\n\n# Run a single test file\npython3 -m pytest tests/test_coverup_83.py -v\n```\n\n## Project Structure\n\n### Core Profiler Components (`scalene/`)\n\n- **`scalene_profiler.py`** - Main profiler class (`Scalene`). Entry point for profiling. Uses signal-based sampling for CPU profiling. Coordinates all profiling subsystems.\n- **`scalene_statistics.py`** - `ScaleneStatistics` class. Collects and aggregates profiling data. Key types: `ProfilingSample`, `MemcpyProfilingSample`. Uses `RunningStats` for statistical aggregation.\n- **`scalene_output.py`** - Profile output formatting for CLI/HTML\n- **`scalene_json.py`** - `ScaleneJSON` class for JSON output format\n- **`scalene_analysis.py`** - Profile analysis logic\n\n### Entry Points\n\n- **`__main__.py`** - Entry point for `python -m scalene`\n- **`profile.py`** - Entry point for `--on`/`--off` control of background profiling\n\n### Configuration & Arguments\n\n- **`scalene_config.py`** - Version info (`scalene_version`, `scalene_date`) and constants:\n  - `SCALENE_PORT = 11235` - Default port for web UI\n  - `NEWLINE_TRIGGER_LENGTH` - Must match `src/include/sampleheap.hpp`\n- **`scalene_arguments.py`** - `ScaleneArguments` class (extends `argparse.Namespace`) with all profiler options and their defaults defined in `ScaleneArgumentsDict`\n- **`scalene_parseargs.py`** - `ScaleneParseArgs.parse_args()` builds the argument parser. `RichArgParser` provides colored help output (uses Rich on Python < 3.14, native argparse colors on 3.14+)\n\n### Signal Handling\n\n- **`scalene_signals.py`** - Signal definitions for CPU sampling\n- **`scalene_signal_manager.py`** - Manages signal handlers\n- **`scalene_sigqueue.py`** - Signal queue management\n- **`scalene_client_timer.py`** - Timer for periodic profiling\n\n### GPU Support\n\n- **`scalene_nvidia_gpu.py`** - NVIDIA GPU profiling via `pynvml`\n- **`scalene_apple_gpu.py`** - Apple GPU profiling (Metal)\n- **`scalene_accelerator.py`** - Generic accelerator interface\n- **`scalene_neuron.py`** - AWS Neuron support\n\n### Memory Profiling\n\n- **`scalene_memory_profiler.py`** - Memory profiling logic\n- **`scalene_leak_analysis.py`** - Memory leak detection (experimental, `--memory-leak-detector`)\n- **`scalene_mapfile.py`** - `ScaleneMapFile` for memory-mapped communication with native extension\n- **`scalene_preload.py`** - Sets up `LD_PRELOAD`/`DYLD_INSERT_LIBRARIES` for native memory tracking\n\n### Jupyter Integration\n\n- **`scalene_magics.py`** - Jupyter magic commands (`%scrun` for line mode, `%%scalene` for cell mode)\n- **`scalene_jupyter.py`** - Jupyter notebook support utilities\n\n### Replacement Modules (`replacement_*.py`)\n\nThese modules monkey-patch standard library functions to capture profiling data during blocking operations:\n- **`replacement_fork.py`** - Tracks `os.fork()`\n- **`replacement_exit.py`** - Tracks `sys.exit()`\n- **`replacement_lock.py`**, **`replacement_mp_lock.py`**, **`replacement_sem_lock.py`** - Lock acquisition timing\n- **`replacement_thread_join.py`**, **`replacement_pjoin.py`** - Thread/process join timing\n- **`replacement_signal_fns.py`** - Signal function replacements\n- **`replacement_poll_selector.py`** - I/O polling timing\n- **`replacement_get_context.py`** - Multiprocessing context\n\n### Utilities\n\n- **`runningstats.py`** - `RunningStats` class for online statistical calculations (mean, variance)\n- **`scalene_funcutils.py`** - Function utilities\n- **`scalene_utility.py`** - General utilities\n- **`sparkline.py`** - Sparkline generation for memory visualization\n- **`syntaxline.py`** - Syntax-highlighted source code lines\n- **`adaptive.py`** - Adaptive sampling logic\n- **`time_info.py`** - Time measurement utilities\n- **`sorted_reservoir.py`** - Reservoir sampling for bounded-size sample collection\n\n### GUI (`scalene/scalene-gui/`)\n\nWeb-based GUI built with TypeScript, bundled with esbuild.\n\n**Core Files:**\n- **`index.html.template`** - Jinja2 template for main GUI page (rendered by `scalene_utility.py`)\n- **`scalene-gui.ts`** - Main TypeScript entry point, UI event handlers, initialization\n- **`scalene-gui-bundle.js`** - Bundled JavaScript output (generated, do not edit directly)\n\n**AI Provider Modules:**\n- **`openai.ts`** - OpenAI API integration (`sendPromptToOpenAI`, `fetchOpenAIModels`)\n- **`anthropic.ts`** - Anthropic Claude API integration\n- **`gemini.ts`** - Google Gemini API integration (`sendPromptToGemini`, `fetchGeminiModels`)\n- **`optimizations.ts`** - Provider dispatch logic, prompt generation\n- **`persistence.ts`** - localStorage persistence with environment variable fallbacks\n\n**Support Files:**\n- **`launchbrowser.py`** - Opens browser to GUI (default port 11235)\n- **`find_browser.py`** - Cross-platform browser detection\n\n**Vendored Assets (for offline support):**\n- **`jquery-3.6.0.slim.min.js`** - jQuery (vendored locally, not loaded from CDN)\n- **`bootstrap.min.css`** - Bootstrap 5.1.3 CSS\n- **`bootstrap.bundle.min.js`** - Bootstrap 5.1.3 JS with Popper\n- **`prism.css`** - Syntax highlighting styles\n- **`favicon.ico`** - Scalene favicon\n- **`scalene-image.png`** - Scalene logo\n\nThese assets are copied, with the page, into a fresh private (0700) temp directory that `launchbrowser.py` serves and deletes on exit, enabling the GUI to work in air-gapped/offline environments. Never serve the shared system temp dir itself.\n\n**Building the GUI:**\n```bash\nnpm --prefix scalene/scalene-gui run build\n```\nThe `build` script in `scalene/scalene-gui/package.json` invokes esbuild\nwith `--minify --sourcemap --target=es2020`. The minified bundle is what\ngets checked in (≈1.1 MB vs ≈2.5 MB unminified). A `build:dev` variant\n(no minification) is available for debugging.\n\n### Native Extensions (`src/`)\n\nC++ code for low-overhead memory allocation tracking:\n\n**Headers (`src/include/`):**\n- **`sampleheap.hpp`** - Sampling heap allocator. Key constant `NEWLINE` must match Python config.\n- **`memcpysampler.hpp`** - Intercepts `memcpy` to track copy volume\n- **`pywhere.hpp`** - Tracks Python file/line info for allocations\n- **`samplefile.hpp`** - File-based communication with Python\n- **`sampler.hpp`**, **`poissonsampler.hpp`**, **`thresholdsampler.hpp`** - Sampling strategies\n- **`scaleneheader.hpp`** - Common header definitions\n\n**Sources (`src/source/`):**\n- **`libscalene.cpp`** - Main native library (loaded via `LD_PRELOAD`)\n- **`pywhere.cpp`** - Python location tracking implementation\n- **`get_line_atomic.cpp`** - Atomic line number access\n- **`traceconfig.cpp`** - Trace configuration\n\n### Vendor Libraries (`vendor/`)\n\n- **`Heap-Layers/`** - Memory allocator infrastructure (by Emery Berger)\n- **`printf/`** - Async-signal-safe printf implementation\n\n## Key Patterns\n\n### Python Version Compatibility\n\nThe codebase supports Python 3.8-3.14. Version-specific code uses:\n\n```python\nif sys.version_info >= (3, 14):\n    # Python 3.14+ specific code\nelse:\n    # Older Python versions\n```\n\n**Type Annotation Compatibility (Python 3.8/3.9):**\n- **Do NOT use `X | Y` union syntax** in runtime-evaluated annotations (PEP 604 requires Python 3.10+). Use `Optional[X]` or `Union[X, Y]` from `typing` instead.\n- **Do NOT use `list[X]`, `dict[K, V]`, `tuple[X, ...]`** in runtime-evaluated annotations (PEP 585 lowercase generics require Python 3.9+). Use `List`, `Dict`, `Tuple` from `typing` for 3.8 support.\n- Adding `from __future__ import annotations` makes all annotations strings (not evaluated at runtime), which allows modern syntax on older Python. However, this can break code that inspects annotations at runtime (e.g., dataclasses, pydantic).\n- The safest approach for this codebase: use `typing.Optional`, `typing.Union`, `typing.List`, `typing.Tuple`, `typing.Dict` in all annotation positions that are evaluated at runtime (function signatures, variable annotations outside `if TYPE_CHECKING` blocks).\n\n**Python 3.13 Changes (`dis` module):**\n- `dis.Instruction.starts_line` changed from `int | None` (line number) to `bool`\n- New `dis.Instruction.line_number` attribute (`int | None`) added for the actual line number\n- On Python < 3.13, `starts_line` is only set on the **first** instruction of each source line; use a line-tracking loop to propagate line numbers to subsequent instructions\n\n**Bytecode/Opcode Compatibility (`dis` module):**\n- **Never match specific opcode names** (e.g., `JUMP_BACKWARD`, `JUMP_ABSOLUTE`, `POP_JUMP_IF_TRUE`). Opcode names change across Python versions — for example, Python 3.10 while loops use `POP_JUMP_IF_TRUE` for backward jumps, Python 3.11+ uses `JUMP_BACKWARD`, and `JUMP_ABSOLUTE` was removed in 3.12.\n- **Always use abstract `dis` module categories** when possible: `dis.hasjabs` (absolute jump opcodes), `dis.hasjrel` (relative jump opcodes), `dis.hasconst`, `dis.hasname`, etc. These are maintained by CPython and work across all versions.\n- For call detection, matching `opname.startswith(\"CALL\")` is acceptable since that prefix has been stable, but prefer opcode integer sets over name strings for hot paths.\n- When checking jump direction (forward vs backward), use `instr.argval` (which `dis` resolves to an absolute offset) and compare against `instr.offset`, rather than relying on opcode names to imply direction.\n\n**Python 3.14 Changes:**\n- `argparse` now has built-in colored help output (`color=True` parameter)\n- `RichArgParser` uses Rich for colors on Python < 3.14, native argparse colors on 3.14+\n\n### Argument Parsing (`scalene_parseargs.py`)\n\n```python\nclass RichArgParser(argparse.ArgumentParser):\n    \"\"\"ArgumentParser that uses Rich for colored output on Python < 3.14.\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        if sys.version_info < (3, 14):\n            from rich.console import Console\n            self._console = Console()\n        else:\n            self._console = None\n        super().__init__(*args, **kwargs)\n```\n\nThe `_colorize_help_for_rich()` function applies Python 3.14-style colors using Rich markup:\n- `usage:` and `options:` → bold blue\n- Program name → bold magenta\n- Long options (`--foo`) → bold cyan\n- Short options (`-h`) → bold green\n- Metavars (`FOO`) → bold yellow\n\n### GUI Patterns\n\n**Preventing Browser Password Prompts:**\nUse `autocomplete=\"one-time-code\"` on password/API key inputs to prevent browsers from offering to save them:\n```html\n<input type=\"password\" id=\"api-key\" autocomplete=\"one-time-code\">\n```\n\n**Show/Hide Password Toggle:**\n```typescript\nfunction togglePassword(inputId: string, button: HTMLButtonElement): void {\n  const input = document.getElementById(inputId) as HTMLInputElement;\n  if (input.type === \"password\") {\n    input.type = \"text\";\n    button.textContent = \"Hide\";\n  } else {\n    input.type = \"password\";\n    button.textContent = \"Show\";\n  }\n}\n```\n\n**Provider Field Visibility:**\nUse CSS classes to show/hide provider-specific fields:\n```typescript\nfunction toggleServiceFields(): void {\n  const service = (document.getElementById(\"service\") as HTMLSelectElement).value;\n  // Hide all provider sections\n  document.querySelectorAll(\".provider-section\").forEach((el) => {\n    (el as HTMLElement).style.display = \"none\";\n  });\n  // Show selected provider section\n  const section = document.querySelector(`.${service}-fields`);\n  if (section) (section as HTMLElement).style.display = \"block\";\n}\n```\n\n**Persistent Form Elements:**\nAdd class `persistent` to inputs that should be saved/restored from localStorage:\n```html\n<input type=\"text\" id=\"api-key\" class=\"persistent\">\n```\nThe `persistence.ts` module handles save/restore automatically.\n\n**Standalone HTML Generation:**\nThe `generate_html()` function in `scalene_utility.py` supports a `standalone` parameter:\n- When `standalone=False` (default): Assets are referenced as local files (e.g., `<script src=\"jquery-3.6.0.slim.min.js\">`)\n- When `standalone=True`: All assets are embedded inline (JS/CSS as text, images as base64)\n\nThe Jinja2 template uses conditionals:\n```html\n{% if standalone %}\n<script>{{ jquery_js }}</script>\n<style>{{ bootstrap_css }}</style>\n{% else %}\n<script src=\"jquery-3.6.0.slim.min.js\"></script>\n<link href=\"bootstrap.min.css\" rel=\"stylesheet\">\n{% endif %}\n```\n\n### Module Imports\n\nWhen importing submodules, be explicit:\n\n```python\n# Correct - mypy can verify this\nimport importlib.util\nimportlib.util.find_spec(mod_name)\n\n# Wrong - mypy error: Module has no attribute \"util\"\nimport importlib\nimportlib.util.find_spec(mod_name)\n```\n\n## Testing\n\n### Test Files (`tests/`)\n\n- **`test_coverup_*.py`** - Auto-generated coverage tests\n- **`test_runningstats.py`** - Statistics tests (requires `hypothesis`)\n- **`test_scalene_json.py`** - JSON output tests (requires `hypothesis`)\n- **`test_nested_package_relative_import.py`** - Import handling tests\n\n### Test Dependencies\n\n```bash\npip install pytest pytest-asyncio hypothesis\n```\n\n### Running Tests Across Python Versions\n\n```bash\nfor v in 3.9 3.10 3.11 3.12 3.13 3.14; do\n    python$v -m pytest tests/test_coverup_83.py -v\ndone\n```\n\n### Flaky Smoketests\n\nThe smoketests in `test/` can be flaky due to timing/sampling issues inherent to profiling:\n\n- **\"No non-zero lines in X\"** - The profiler didn't collect enough samples. This happens when the test runs too quickly or signal delivery timing varies.\n- **\"Expected function 'X' not returned\"** - A function wasn't sampled. Common with short-running functions.\n\nThese failures are usually timing-related and pass on re-run. They're more common on CI due to variable machine load.\n\n### Port Binding in Tests\n\nWhen testing port availability, never use hardcoded ports - they may already be in use on CI runners:\n\n```python\n# Bad - port 49200 might be in use\nport = 49200\nsock.bind((\"\", port))\n\n# Good - find an available port first\nport = find_available_port(49200, 49300)\nif port is None:\n    return  # Skip test if no ports available\nsock.bind((\"\", port))\n```\n\n## CI/CD (`.github/workflows/`)\n\n- **`run-linters.yml`** - Runs mypy and ruff on Python 3.9-3.14\n- **`tests.yml`** - Runs pytest on Python 3.9-3.14\n- **`build-and-upload.yml`** - Build and publish to PyPI\n\n## Common Tasks\n\n### Adding a New CLI Option\n\n1. Add default value in `scalene_arguments.py`:\n   ```python\n   class ScaleneArgumentsDict(TypedDict, total=False):\n       my_option: bool\n   ```\n\n2. Add argument in `scalene_parseargs.py`:\n   ```python\n   parser.add_argument(\n       \"--my-option\",\n       dest=\"my_option\",\n       action=\"store_true\",\n       default=defaults.my_option,\n       help=\"Description of option\",\n   )\n   ```\n\n### Adding a New AI Provider\n\n1. **Create provider module** (`scalene/scalene-gui/newprovider.ts`):\n   ```typescript\n   export async function sendPromptToNewProvider(\n     prompt: string,\n     apiKey: string\n   ): Promise<string> {\n     // API call implementation\n   }\n\n   export async function fetchNewProviderModels(apiKey: string): Promise<string[]> {\n     // Optional: fetch available models from API\n   }\n   ```\n\n2. **Update `optimizations.ts`**:\n   - Import the new module\n   - Add case in `sendPromptToService()` switch statement\n\n3. **Update `index.html.template`**:\n   - Add option to `#service` select dropdown\n   - Add provider section with API key input, model selector, etc.\n   - Add CSS for `.newprovider-fields` visibility\n\n4. **Update `scalene-gui.ts`**:\n   - Add provider to `toggleServiceFields()` function\n   - Add refresh handler if dynamic model fetching is supported\n   - Update `getDefaultProvider()` if env var support is needed\n\n5. **Update `persistence.ts`** (for env var support):\n   - Add mapping in `envKeyMap` for new fields\n\n6. **Update `launchbrowser.py`** (for env var support):\n   - Add the field and its environment variable(s) to `ENV_API_KEY_VARIABLES`\n   - Add the field to `EnvApiKeys` / `envApiKeyFields` in `persistence.ts`\n\n7. **Rebuild the bundle**:\n   ```bash\n   npm --prefix scalene/scalene-gui run build\n   ```\n\n### Environment Variable API Keys\n\nWith `scalene view --api-keys-from-env` (opt-in), the GUI prepopulates API\nkeys from environment variables:\n\n| Element ID | Environment Variable | Provider |\n|------------|---------------------|----------|\n| `api-key` | `OPENAI_API_KEY` | OpenAI |\n| `anthropic-api-key` | `ANTHROPIC_API_KEY` | Anthropic |\n| `gemini-api-key` | `GEMINI_API_KEY` or `GOOGLE_API_KEY` | Gemini |\n| `azure-api-key` | `AZURE_OPENAI_API_KEY` | Azure OpenAI |\n| `azure-api-url` | `AZURE_OPENAI_ENDPOINT` | Azure OpenAI |\n| `aws-access-key` | `AWS_ACCESS_KEY_ID` | Amazon Bedrock |\n| `aws-secret-key` | `AWS_SECRET_ACCESS_KEY` | Amazon Bedrock |\n| `aws-region` | `AWS_DEFAULT_REGION` or `AWS_REGION` | Amazon Bedrock |\n\n**Flow:**\n1. `scalene view --api-keys-from-env` passes the flag to `launchbrowser.py`,\n   whose local server reads the env vars and serves them from memory at\n   `/env-api-keys.json` (an empty object without the flag)\n2. `loadEnvApiKeys()` in `persistence.ts` fetches that on page load (only when\n   the page is served over HTTP), before persistent fields are restored\n3. `persistence.ts` uses env vars as fallbacks when localStorage is empty;\n   `prefillNonPersistentEnvFields()` fills the non-persistent OpenAI key\n\n**Never embed credentials in the generated HTML.** `scalene-profile.html` is\nwritten to the working directory, copied to the system temp dir, and\n`--standalone` files exist to be shared (#1095;\n`tests/test_html_credential_security.py` enforces this). The endpoint answers\nonly requests whose `Host` is `localhost:<port>`/`127.0.0.1:<port>` (blocks DNS\nrebinding) and whose `Sec-Fetch-Site`, if sent, is same-origin; it sends no\nCORS headers. The flag is rejected with `--cli`, `--html`, and `--standalone`.\n\n### Updating Version\n\nEdit `scalene/scalene_config.py`:\n```python\nscalene_version = \"X.Y.Z\"\nscalene_date = \"YYYY.MM.DD\"\n```\n\n## Dependencies\n\nKey runtime dependencies:\n- `rich` - Terminal formatting and colors\n- `cloudpickle` - Serialization\n- `pynvml` - NVIDIA GPU support (optional)\n\nSee `requirements.txt` for full list.\n\n## CLI Structure\n\nScalene uses a verb-based CLI with two main subcommands:\n\n```bash\n# Profile a program (saves to scalene-profile.json by default)\nscalene run [options] yourprogram.py\n\n# View an existing profile\nscalene view [options] [profile.json]\n```\n\n### Run Subcommand Options\n\n```bash\nscalene run prog.py                      # profile, save to scalene-profile.json\nscalene run -o my.json prog.py           # save to custom file\nscalene run --cpu-only prog.py           # profile CPU only (faster)\nscalene run -c config.yaml prog.py       # load options from config file\nscalene run prog.py --- --arg            # pass args to program\n```\n\n### View Subcommand Options\n\n```bash\nscalene view                             # open in browser\nscalene view --cli                       # view in terminal\nscalene view --html                      # save to scalene-profile.html\nscalene view --standalone                # save as self-contained HTML (all assets embedded)\nscalene view myprofile.json              # open specific profile\n```\n\n### Profile Completion Message\n\nAfter profiling completes, Scalene prints instructions for viewing the profile:\n```\nScalene: profile saved to scalene-profile.json\n  To view in browser:  scalene view\n  To view in terminal: scalene view --cli\n```\n\nThe filename is only included in the command if a non-default output file was used.\n\n### YAML Configuration\n\nCreate a `scalene.yaml` file with options:\n\n```yaml\noutfile: my-profile.json\ncpu-only: true\nprofile-only: \"mypackage,utils\"\ncpu-percent-threshold: 5\n```\n\nLoad with: `scalene run -c scalene.yaml prog.py`\n\n### Advanced Options\n\nUse `scalene run --help-advanced` to see all options including:\n- `--profile-all` - profile all code, not just the target program\n- `--profile-only PATH` - only profile files containing these strings\n- `--profile-exclude PATH` - exclude files containing these strings\n- `--profile-system-libraries` - profile Python stdlib and installed packages (skipped by default)\n- `--gpu` - profile GPU time and memory\n- `--memory` - profile memory usage\n- `--stacks` - collect stack traces\n- `--profile-interval N` - output profiles every N seconds\n\n### Smoke Tests\n\nSmoke tests in `test/` use the new CLI syntax:\n\n```python\n# test/smoketest.py\ncmd = [sys.executable, \"-m\", \"scalene\", \"run\", \"-o\", str(outfile), *rest, fname]\n```\n\n### GitHub Workflows\n\nWorkflows in `.github/workflows/` use the new CLI:\n\n```yaml\n# Profile with interval, then view\n- run: python -m scalene run --profile-interval=2 test/testme.py && python -m scalene view --cli\n\n# Profile with module invocation\n- run: python -m scalene run --- -m import_stress_test && python -m scalene view --cli\n```\n\n## Signal Handling\n\nScalene uses several Unix signals for profiling. The signal assignments are in `scalene_signals.py`:\n\n| Signal | Purpose | Platform |\n|--------|---------|----------|\n| `SIGVTALRM` | CPU profiling timer (default) | Unix |\n| `SIGALRM` | CPU profiling timer (real time mode) | Unix |\n| `SIGILL` | Start profiling (`--on`) | Unix |\n| `SIGBUS` | Stop profiling (`--off`) | Unix |\n| `SIGPROF` | memcpy tracking | Unix |\n| `SIGXCPU` | malloc tracking | Unix |\n| `SIGXFSZ` | free tracking | Unix |\n\n### Signal Conflicts with Libraries\n\nLibraries like PyTorch Lightning may also use these signals. The `replacement_signal_fns.py` module handles conflicts:\n\n**On Linux:** Uses real-time signals (`SIGRTMIN+1` to `SIGRTMIN+5`) for redirection. When user code sets a handler for a Scalene signal, their handler is redirected to a real-time signal. Calls to `raise_signal()` and `kill()` are also redirected transparently.\n\n**On macOS/other platforms:** Uses handler chaining. Both Scalene's handler and the user's handler are called when the signal fires.\n\n```python\n# Platform-specific signal handling\n_use_rt_signals = sys.platform == \"linux\" and hasattr(signal, \"SIGRTMIN\")\n\nif _use_rt_signals:\n    # Linux: redirect to real-time signals\n    rt_base = signal.SIGRTMIN + 1\n    _signal_redirects[signal.SIGILL] = rt_base\nelse:\n    # macOS: chain handlers\n    def chained_handler(sig, frame):\n        scalene_handler(sig, frame)\n        user_handler(sig, frame)\n```\n\n### Frame Line Number Can Be None (Python 3.11+)\n\nIn Python 3.11+, `frame.f_lineno` can be `None` in edge cases (e.g., during multiprocessing cleanup). Always use a fallback:\n\n```python\nlineno = frame.f_lineno if frame.f_lineno is not None else frame.f_code.co_firstlineno\n```\n\n## Native Extension Build Issues\n\n### C++ Standard Library Conflicts with vendor/printf\n\nThe `vendor/printf/printf.h` header defines macros that conflict with C++ standard library:\n\n```c\n#define vsnprintf vsnprintf_\n#define snprintf  snprintf_\n```\n\nThis breaks `std::vsnprintf` in `<string>` and other headers. **Fix:** Include C++ standard headers BEFORE vendor headers in `src/source/libscalene.cpp`:\n\n```cpp\n// Include C++ standard headers FIRST\n#include <cstddef>\n#include <string>\n\n// Then vendor headers that define conflicting macros\n#include <heaplayers.h>  // Eventually includes printf.h\n```\n\n## Profiling Guide\n\nSee [Scalene-Agents.md](Scalene-Agents.md) for detailed information about interpreting Scalene's profiling output, including Python vs C time, memory metrics, and optimization strategies.\n\n## Debugging Guide\n\nSee [Scalene-Debugging.md](Scalene-Debugging.md) for signal handler debugging, async profiling debugging, the profile output pipeline (three separate renderers!), and unbounded growth prevention patterns.\n\n## GUI Development Guide\n\nSee [Scalene-GUI.md](Scalene-GUI.md) for adding new columns, Vega-Lite chart types, pie chart best practices (two-wedge rendering, rotating pies), and the chart rendering flow.\n"},"files":{"agents/windows-memory-profiling-port.md":"# Windows Memory Profiling Port - Progress and Plans\n\n## Overview\n\nPorting Scalene's memory profiling from Linux/macOS to Windows. On Unix systems, Scalene uses `LD_PRELOAD`/`DYLD_INSERT_LIBRARIES` to interpose on malloc/free. Windows doesn't support this mechanism, so we use Python's allocator API instead.\n\n## Architecture\n\n### Unix Approach\n- `libscalene.so`/`libscalene.dylib` is preloaded via environment variables\n- Interposes on malloc/free/realloc at the C library level\n- Uses Unix signals (SIGXCPU, SIGXFSZ) to communicate with Python\n- Uses POSIX shared memory (`/tmp/scalene-*`) for data transfer\n\n### Windows Approach\n- `libscalene.dll` is loaded explicitly via ctypes\n- Uses Python's `PyMem_SetAllocator` API to intercept allocations\n- Uses Windows Events instead of Unix signals (but currently using polling)\n- Uses Windows Named Shared Memory (`Local\\scalene-*`) for data transfer\n\n## Files Created/Modified\n\n### New Files\n- `src/source/libscalene_windows.cpp` - Main Windows DLL implementation (includes Detours native hooks)\n- `src/include/samplefile_win.hpp` - Windows shared memory implementation\n- `src/include/common_win.hpp` - Windows compatibility macros\n- `src/include/mallocrecursionguard_win.hpp` - Windows TLS-based recursion guard\n- `scalene/scalene_windows.py` - Python helper for Windows memory profiling\n- `vendor/Detours/` - Microsoft Detours library for native malloc/free hooking (vendored)\n\n### Modified Files\n- `CMakeLists.txt` - Cross-platform build configuration with Detours integration\n- `src/include/pywhere.hpp` - Windows DLL export/import macros\n- `src/source/pywhere.cpp` - Windows symbol lookup via accessor functions\n- `scalene/scalene_profiler.py` - Windows DLL loading and initialization\n- `scalene/scalene_mapfile.py` - Windows named shared memory support\n- `scalene/scalene_signal_manager.py` - Windows memory polling thread\n- `scalene/scalene_preload.py` - Case-insensitive ARM64 detection\n- `scalene/scalene_arguments.py` - Enable memory profiling on Windows\n\n## Current Status (2024-12-17) - FULLY WORKING WITH NATIVE LIBRARY SUPPORT!\n\n### All Core Features Working\n1. ✅ DLL builds successfully with CMake/MSBuild for ARM64\n2. ✅ DLL loads without crashing\n3. ✅ Python allocator hooks are installed via `PyMem_SetAllocator`\n4. ✅ Allocation size tracking implemented (hash map with critical section)\n5. ✅ Shared memory objects created successfully (both malloc and memcpy)\n6. ✅ ScaleneMapFile opens shared memory from Python side successfully\n7. ✅ All `thread_local` variables removed (caused crashes with dynamically loaded DLLs on Windows)\n8. ✅ Data format updated to match Unix (comma-separated, 9 fields for malloc, 6 for memcpy)\n9. ✅ Windows memory polling thread added to `scalene_signal_manager.py`\n10. ✅ **64-bit pointer handling fixed in ctypes code**\n11. ✅ Memory profiling working - samples being collected and logged\n12. ✅ **Native library tracking (numpy, etc.) via Microsoft Detours**\n13. ✅ **Proper free tracking with manual size tracking hash map**\n\n### Test Results (2024-12-17)\n```\nalloc_samples: 203\nmax_footprint_mb: 152.71\nmax_footprint_python_fraction: 0.000288\nNative fraction: 99.97%\n```\n\nThe profiler now correctly tracks memory allocations from native libraries like numpy, with proper line attribution.\n\n### Known Limitation\n- Console output may fail with `UnicodeEncodeError` on Windows consoles using CP1252 encoding\n- This is not a profiler bug - it's a Windows console limitation\n- **Workaround**: Set `PYTHONIOENCODING=utf-8` environment variable or use `--html` or `--json` output\n\n## The Critical Bug Fix (2024-12-16)\n\n### Root Cause: ctypes 64-bit Pointer Truncation\n\nThe Access Violation crash (exit code 3221225477 = 0xC0000005) was caused by a **ctypes bug** where 64-bit pointers were being truncated to 32-bit values.\n\n**Problem**: By default, ctypes assumes Windows API functions return `c_int` (32-bit). On 64-bit Windows, `MapViewOfFile` returns a 64-bit pointer. Without explicit return type declaration, the high 32 bits were truncated, causing invalid memory addresses.\n\n**Example of bug**:\n```\nMapViewOfFile returns: 0x00007FFBF2650B90  (valid 64-bit pointer)\nctypes stored as:      0x00000000F2650B90  (truncated to 32-bit, invalid!)\n```\n\n### The Fix (scalene_mapfile.py)\n\nAdded proper return type declarations before calling Windows API functions:\n\n```python\nfrom ctypes import wintypes\n\nkernel32 = ctypes.windll.kernel32\n\n# IMPORTANT: Set proper return types for Windows API functions\n# Default ctypes return type is c_int (32-bit) which truncates 64-bit pointers\nkernel32.OpenFileMappingW.restype = wintypes.HANDLE\nkernel32.MapViewOfFile.restype = ctypes.c_void_p\nkernel32.UnmapViewOfFile.argtypes = [ctypes.c_void_p]\nkernel32.CloseHandle.argtypes = [wintypes.HANDLE]\n```\n\nThis fix is in `scalene/scalene_mapfile.py` in the `_init_windows()` method.\n\n## Native Library Memory Tracking (2024-12-17)\n\n### The Problem\n\nThe original Windows implementation only tracked Python allocations via `PyMem_SetAllocator`. Native library allocations (numpy arrays, pandas dataframes, etc.) went untracked because they bypass Python's allocator and call the C runtime `malloc`/`free` directly.\n\n**Before**: Only Python allocations tracked (0.03% of actual memory usage)\n**After**: All allocations tracked including native libraries (99.97% native, 0.03% Python)\n\n### Solution: Microsoft Detours\n\nWe use [Microsoft Detours](https://github.com/microsoft/Detours) to intercept `malloc`/`free`/`realloc`/`calloc` at the C runtime level. Detours works by rewriting the first few bytes of target functions with a jump to our hooks.\n\n**Why Detours over alternatives:**\n- **MinHook**: Does NOT support ARM64 (only x86/x64)\n- **IAT Hooking**: Complex, requires hooking each loaded module separately\n- **ETW (Event Tracing)**: Requires admin privileges, high overhead\n- **Detours**: Supports ARM64, x64, x86, ARM - official Microsoft library, MIT licensed\n\n### Implementation\n\n#### Files Modified/Added\n- `vendor/Detours/` - Microsoft Detours source (vendored)\n- `CMakeLists.txt` - Added Detours to Windows build with architecture-specific disassembler\n- `src/source/libscalene_windows.cpp` - Added native hooks using Detours\n\n#### CMakeLists.txt Changes\n```cmake\n# Microsoft Detours sources for native malloc/free hooking\nset(DETOURS_SOURCES\n    vendor/Detours/src/detours.cpp\n    vendor/Detours/src/modules.cpp\n    vendor/Detours/src/disasm.cpp\n    vendor/Detours/src/image.cpp\n    vendor/Detours/src/creatwth.cpp\n)\n\n# Add architecture-specific disassembler\nif(SCALENE_ARCH STREQUAL \"ARM64\")\n    list(APPEND DETOURS_SOURCES vendor/Detours/src/disolarm64.cpp)\nelseif(SCALENE_ARCH STREQUAL \"X64\")\n    list(APPEND DETOURS_SOURCES vendor/Detours/src/disolx64.cpp)\nelseif(SCALENE_ARCH STREQUAL \"X86\")\n    list(APPEND DETOURS_SOURCES vendor/Detours/src/disolx86.cpp)\nendif()\n\ntarget_compile_definitions(scalene PRIVATE DETOURS_INTERNAL)\n```\n\n#### Native Hook Implementation\n```cpp\n#include \"detours.h\"\n\n// Original function pointers (Detours fills these with trampolines)\nstatic void* (__cdecl *Real_malloc)(size_t) = malloc;\nstatic void (__cdecl *Real_free)(void*) = free;\nstatic void* (__cdecl *Real_realloc)(void*, size_t) = realloc;\nstatic void* (__cdecl *Real_calloc)(size_t, size_t) = calloc;\n\n// Recursion guard - CRITICAL for preventing infinite loops\nstatic bool g_in_native_hook = false;\n\n// Coordination with Python allocator hooks\nstatic bool g_in_python_allocator = false;\n\nstatic void* __cdecl Hooked_malloc(size_t size) {\n    // Check recursion guard FIRST - track_native_alloc may call malloc internally\n    if (g_in_native_hook || g_in_python_allocator) {\n        return Real_malloc(size);\n    }\n\n    g_in_native_hook = true;\n    void* ptr = Real_malloc(size);\n    if (ptr) {\n        track_native_alloc(ptr, size);\n        if (!p_scalene_done) {\n            TheHeapWrapper::register_malloc(size, ptr, false);  // false = native\n        }\n    }\n    g_in_native_hook = false;\n    return ptr;\n}\n\nbool install_native_hooks() {\n    DetourRestoreAfterWith();\n    DetourTransactionBegin();\n    DetourUpdateThread(GetCurrentThread());\n\n    DetourAttach(&(PVOID&)Real_malloc, Hooked_malloc);\n    DetourAttach(&(PVOID&)Real_free, Hooked_free);\n    DetourAttach(&(PVOID&)Real_realloc, Hooked_realloc);\n    DetourAttach(&(PVOID&)Real_calloc, Hooked_calloc);\n\n    return DetourTransactionCommit() == NO_ERROR;\n}\n```\n\n### Critical Bug Fix: Recursion Guard Order\n\n**Problem**: The native hooks use `std::unordered_map` for size tracking, which internally calls `malloc`. If the recursion guard was checked AFTER calling tracking functions, infinite recursion occurred:\n\n1. User code calls `malloc`\n2. `Hooked_malloc` is called\n3. `track_native_alloc(ptr, size)` is called (hash map insert)\n4. Hash map internally calls `malloc` for bucket allocation\n5. `Hooked_malloc` is called again (recursion!)\n6. Since `g_in_native_hook` wasn't set yet, we recurse infinitely → stack overflow\n\n**Fix**: Check and set `g_in_native_hook` at the VERY BEGINNING of each hook, BEFORE any operations that might allocate:\n\n```cpp\nstatic void* __cdecl Hooked_malloc(size_t size) {\n    // MUST check recursion guard FIRST\n    if (g_in_native_hook || g_in_python_allocator) {\n        return Real_malloc(size);  // Bypass hook\n    }\n\n    g_in_native_hook = true;  // Set BEFORE any allocating operations\n    // ... rest of hook logic ...\n    g_in_native_hook = false;\n    return ptr;\n}\n```\n\n### Free Size Tracking\n\n**Problem**: `_msize()` doesn't work reliably for custom allocators (numpy uses aligned allocations with `_aligned_malloc`).\n\n**Solution**: Manual size tracking with a hash map:\n\n```cpp\nstatic std::unordered_map<void*, size_t> g_native_alloc_sizes;\nstatic CRITICAL_SECTION g_native_alloc_sizes_lock;\n\nstatic void track_native_alloc(void* ptr, size_t size) {\n    EnterCriticalSection(&g_native_alloc_sizes_lock);\n    g_native_alloc_sizes[ptr] = size;\n    LeaveCriticalSection(&g_native_alloc_sizes_lock);\n}\n\nstatic size_t untrack_native_alloc(void* ptr) {\n    EnterCriticalSection(&g_native_alloc_sizes_lock);\n    auto it = g_native_alloc_sizes.find(ptr);\n    size_t size = (it != g_native_alloc_sizes.end()) ? it->second : 0;\n    if (it != g_native_alloc_sizes.end()) g_native_alloc_sizes.erase(it);\n    LeaveCriticalSection(&g_native_alloc_sizes_lock);\n    return size;\n}\n```\n\n### Coordination Between Python and Native Hooks\n\nTo prevent double-counting allocations that go through both Python's allocator AND the native malloc:\n\n1. Python allocator hooks set `g_in_python_allocator = true` when active\n2. Native malloc hooks check this flag and skip if Python allocator is handling it\n3. Each allocation is counted exactly once\n\n```cpp\n// In Python allocator hook:\nstatic void* scalene_malloc(void* ctx, size_t len) {\n    g_in_python_allocator = true;  // Prevent native hooks\n    void* ptr = g_original_mem_allocator.malloc(...);\n    // ... tracking code ...\n    g_in_python_allocator = false;\n    return ptr;\n}\n```\n\n## Fixes Applied\n\n### 1. **ctypes 64-bit Pointer Fix** (Critical)\n- Added `restype` declarations for all Windows API functions that return handles or pointers\n- `MapViewOfFile.restype = ctypes.c_void_p` - returns memory address\n- `OpenFileMappingW.restype = wintypes.HANDLE` - returns handle\n- Also set `argtypes` for functions taking pointers\n\n### 2. Data Format Fix\nChanged malloc/free output from tab-separated 4-field format to comma-separated 9-field format matching Unix:\n```\nM,alloc_time,count,python_fraction,pid,pointer,filename,lineno,bytei\\n\\n\n```\n\n### 3. Removed `thread_local` Variables\nWindows DLLs loaded with `LoadLibrary`/ctypes don't properly support `thread_local` variables. Changed all to static variables:\n- `mallocSampler` and `memcpySampler`\n- `g_pythonCount`, `g_cCount`\n- `g_lastMallocTrigger`, `g_freedLastMallocTrigger`\n- `g_memcpyOps`\n- `inMalloc` (for recursion guard)\n\n### 4. Added Windows Memory Polling\nSince Unix signals (SIGXCPU/SIGXFSZ) don't exist on Windows, added a polling thread in `scalene_signal_manager.py`:\n- `_windows_memory_poll_loop()` - polls every 10ms\n- Triggers `_alloc_sigqueue_processor` and `_memcpy_sigqueue_processor`\n- Started when memory profiling is enabled on Windows\n\n### 5. Safety Checks in Allocator Hooks\nAdded null checks for original allocator function pointers to prevent crashes if allocation hooks are called before initialization.\n\n## Test Results\n\nRunning the profiler now shows memory profiling data being collected:\n```\nmalloc_calls=113807912, samples=595, logged=595\n```\n\nThe profiler correctly tracks:\n- Python vs native time\n- Memory allocations with line attribution\n- Memory timeline/growth rate\n\n## Build Instructions\n\n```bash\n# Configure with CMake (for ARM64 native)\ncmake -B build -A ARM64 -DPython3_ROOT_DIR=\"C:\\Users\\emery\\AppData\\Local\\Programs\\Python\\Python311-arm64\"\n\n# Build the DLL\nMSBuild.exe build/scalene.sln -p:Configuration=Release -p:Platform=ARM64 -t:scalene\n\n# DLL is automatically placed in scalene\\ directory\n\n# Run profiler\npython -m scalene --cli --cpu --memory test\\testme.py\n```\n\n## Key Differences from Unix Implementation\n\n| Aspect | Unix | Windows |\n|--------|------|---------|\n| Native malloc hooking | LD_PRELOAD | **Microsoft Detours** (inline function hooking) |\n| Python allocator | PyMem_SetAllocator | PyMem_SetAllocator (same) |\n| Signals | SIGXCPU/SIGXFSZ | Polling thread |\n| Shared Memory | /tmp files + mmap | Named shared memory |\n| Symbol Lookup | dlsym(RTLD_DEFAULT) | GetProcAddress + accessor functions |\n| Size Tracking | malloc_usable_size | Manual hash map (\\_msize unreliable) |\n| Thread-local | `thread_local` | Static variables (GIL protects) |\n| Pointer handling | Native 64-bit | **Requires explicit ctypes declarations** |\n| Architecture support | All | ARM64, x64, x86 (via Detours) |\n\n## Learnings\n\n### 1. ctypes Default Return Types Are Dangerous on 64-bit Windows\n- **Always** set `restype` for any Windows API function that returns a handle or pointer\n- Default `c_int` (32-bit) silently truncates 64-bit values\n- This causes delayed crashes that are hard to debug (crash happens when truncated pointer is used, not when it's returned)\n- Use `ctypes.c_void_p` for memory addresses, `wintypes.HANDLE` for handles\n\n### 2. Debugging Methodology That Worked\nThe crash was isolated using **systematic component elimination**:\n1. First confirmed DLL was the cause by renaming it (profiler ran without crash)\n2. Tested with allocator hooks disabled - still crashed (ruled out hooks as cause)\n3. Tested with only shared memory initialization - still crashed (narrowed to shared memory)\n4. Examined shared memory code and found the ctypes issue\n\n### 3. Windows DLL Loading Constraints\n- `thread_local` variables don't work reliably in DLLs loaded via `LoadLibrary`/ctypes\n- Static variables work fine since Python's GIL serializes access\n- DLL entry point (`DllMain`) has severe restrictions - avoid complex initialization there\n\n### 4. Build System Considerations\n- CMake generates Visual Studio solutions that work well for cross-platform builds\n- ARM64 native builds require explicit `-A ARM64` and matching Python version\n- MSBuild can be invoked directly for rebuilds without re-running CMake\n\n### 5. Windows vs Unix IPC\n- Named shared memory on Windows uses `Local\\` prefix for per-session namespace\n- Windows Events can replace Unix signals but polling is simpler for this use case\n- Mutexes on Windows are more heavyweight than Unix futexes\n\n### 6. Recursion Guards in malloc Hooks Must Come FIRST\n- **Critical**: Any code in a malloc hook that uses STL containers (std::unordered_map, std::vector, etc.) may internally call malloc\n- The recursion guard (`g_in_native_hook`) MUST be checked and set BEFORE any other operations\n- Failing to do this causes infinite recursion → stack overflow → exit code 127 or crash\n- Pattern: `if (guard) return original(); guard = true; ... ; guard = false; return result;`\n\n### 7. MinHook Does NOT Support ARM64\n- MinHook is a popular inline hooking library but only supports x86 and x64\n- Microsoft Detours supports ARM64, ARM, x86, x64, and IA64\n- Detours is MIT licensed (open source since 2018) and very well tested\n- Use architecture-specific disassemblers: `disolarm64.cpp` for ARM64, `disolx64.cpp` for x64\n\n### 8. _msize() Is Unreliable for Custom Allocators\n- `_msize()` only works for standard CRT heap allocations\n- numpy and other libraries use aligned allocations (`_aligned_malloc`) which `_msize` doesn't handle\n- Solution: Manual size tracking with a hash map, storing size at allocation time\n- This adds ~10% overhead but is necessary for accurate free tracking\n\n### 9. Coordination Between Multiple Hook Layers\n- When hooking at both Python allocator level AND native malloc level, coordination is essential\n- Use a flag (`g_in_python_allocator`) to prevent native hooks from double-counting Python allocations\n- Pattern: Set flag before calling original allocator, clear after\n- This ensures each allocation is tracked exactly once\n\n## Next Steps\n\n### High Priority\n1. **x64 Build and Testing**: Current implementation tested only on ARM64. Need to verify x64 builds work correctly with Detours.\n\n2. **Unicode Console Output**: The `UnicodeEncodeError` for sparkline characters could be handled more gracefully:\n   - Detect console encoding and fall back to ASCII sparklines\n   - Or auto-set `PYTHONIOENCODING=utf-8` when running on Windows\n\n3. **Performance Optimization**: The polling thread polls every 10ms. Consider:\n   - Using Windows Events for more efficient signaling\n   - Adaptive polling rate based on allocation frequency\n\n### Medium Priority\n4. **Multi-Process Support**: Test and fix any issues with profiling child processes on Windows (the Unix `redirect_python` mechanism may need Windows adaptation).\n\n5. **GPU Profiling on Windows**: Verify NVIDIA GPU profiling works on Windows (pynvml should work but needs testing).\n\n6. **CI/CD Integration**: Add Windows builds to GitHub Actions workflow:\n   - Build DLL for both x64 and ARM64\n   - Run tests on Windows\n   - Include DLL in wheel packages\n\n### Low Priority\n7. **Windows Event-Based Signaling**: Replace polling with proper Windows Events for lower latency and CPU usage:\n   - DLL already creates events (`ScaleneMallocEvent`, etc.)\n   - Python side would use `WaitForMultipleObjects` instead of polling\n\n8. **Memory Leak Detection**: The `--memory-leak-detector` feature may need Windows-specific testing.\n\n9. **Web UI Testing**: Verify the web-based GUI works correctly on Windows (browser launching, port binding).\n\n### Completed\n- ~~**Native Library Memory Tracking**: Implement malloc/free hooking for numpy, pandas, etc.~~ ✅ Done (2024-12-17) - Using Microsoft Detours\n\n## Debugging Tips for Future Issues\n\n### Useful Debug Techniques\n```python\n# Add to scalene_mapfile.py to debug shared memory issues\nprint(f\"Handle value: {handle:#x}\", file=sys.stderr)\nprint(f\"View address: {view:#x}\", file=sys.stderr)\n\n# Check if pointer looks valid (should have high bits set on 64-bit)\nif view < 0x100000000:\n    print(\"WARNING: Pointer may be truncated!\", file=sys.stderr)\n```\n\n### Building with Debug Output\n```cpp\n// In libscalene_windows.cpp, temporarily add:\nfprintf(stderr, \"DEBUG: ptr=%p, size=%zu\\n\", ptr, size);\n```\n\nThen rebuild with:\n```bash\nMSBuild.exe build/scalene.sln -p:Configuration=Release -p:Platform=ARM64 -t:scalene\n```\n\n### Common Windows Error Codes\n- `0xC0000005` (3221225477) - Access Violation (invalid memory access)\n- `0xC0000008` - Invalid Handle\n- `0xC000001D` - Illegal Instruction (wrong architecture)\n- Error 193 - \"Not a valid Win32 application\" (architecture mismatch)\n\n## References\n\n- Python Memory Allocator API: https://docs.python.org/3/c-api/memory.html\n- Windows Named Shared Memory: https://docs.microsoft.com/en-us/windows/win32/memory/creating-named-shared-memory\n- CMake Windows DLL: https://cmake.org/cmake/help/latest/prop_tgt/WINDOWS_EXPORT_ALL_SYMBOLS.html\n- Windows thread_local issues: https://devblogs.microsoft.com/cppblog/c11-thread-local-storage-and-dll-load-failure/\n- **ctypes 64-bit pointers**: https://docs.python.org/3/library/ctypes.html#return-types\n- **Microsoft Detours**: https://github.com/microsoft/Detours - Official Microsoft library for inline function hooking (MIT license)\n- Detours Wiki: https://github.com/microsoft/Detours/wiki - API documentation and examples\n","CLAUDE.md":"# Scalene Development Guide\n\n## Project Overview\n\nScalene is a high-performance CPU, GPU, and memory profiler for Python with AI-powered optimization proposals. It runs significantly faster than other Python profilers while providing detailed performance information. See the paper `docs/osdi23-berger.pdf` for technical details on Scalene's design.\n\n**Key features:**\n- CPU, GPU (NVIDIA/Apple), and memory profiling\n- AI-powered optimization suggestions (OpenAI, Anthropic, Azure, Amazon Bedrock, Gemini, Ollama)\n- Web-based GUI and CLI interfaces\n- Jupyter notebook support via magic commands (`%scrun`, `%%scalene`)\n- Line-by-line profiling with low overhead\n- Separates Python time from native/C time\n\n**Platform support:** Linux, macOS, WSL 2 (full support); Windows (partial support)\n\n## Build & Test Commands\n\n```bash\n# Install in development mode\npip install -e .\n\n# Run all tests\npython3 -m pytest tests/\n\n# Run tests for a specific Python version\npython3.X -m pytest tests/\n\n# Run linters\nmypy scalene\nruff check scalene\n\n# Run a single test file\npython3 -m pytest tests/test_coverup_83.py -v\n```\n\n## Project Structure\n\n### Core Profiler Components (`scalene/`)\n\n- **`scalene_profiler.py`** - Main profiler class (`Scalene`). Entry point for profiling. Uses signal-based sampling for CPU profiling. Coordinates all profiling subsystems.\n- **`scalene_statistics.py`** - `ScaleneStatistics` class. Collects and aggregates profiling data. Key types: `ProfilingSample`, `MemcpyProfilingSample`. Uses `RunningStats` for statistical aggregation.\n- **`scalene_output.py`** - Profile output formatting for CLI/HTML\n- **`scalene_json.py`** - `ScaleneJSON` class for JSON output format\n- **`scalene_analysis.py`** - Profile analysis logic\n\n### Entry Points\n\n- **`__main__.py`** - Entry point for `python -m scalene`\n- **`profile.py`** - Entry point for `--on`/`--off` control of background profiling\n\n### Configuration & Arguments\n\n- **`scalene_config.py`** - Version info (`scalene_version`, `scalene_date`) and constants:\n  - `SCALENE_PORT = 11235` - Default port for web UI\n  - `NEWLINE_TRIGGER_LENGTH` - Must match `src/include/sampleheap.hpp`\n- **`scalene_arguments.py`** - `ScaleneArguments` class (extends `argparse.Namespace`) with all profiler options and their defaults defined in `ScaleneArgumentsDict`\n- **`scalene_parseargs.py`** - `ScaleneParseArgs.parse_args()` builds the argument parser. `RichArgParser` provides colored help output (uses Rich on Python < 3.14, native argparse colors on 3.14+)\n\n### Signal Handling\n\n- **`scalene_signals.py`** - Signal definitions for CPU sampling\n- **`scalene_signal_manager.py`** - Manages signal handlers\n- **`scalene_sigqueue.py`** - Signal queue management\n- **`scalene_client_timer.py`** - Timer for periodic profiling\n\n### GPU Support\n\n- **`scalene_nvidia_gpu.py`** - NVIDIA GPU profiling via `pynvml`\n- **`scalene_apple_gpu.py`** - Apple GPU profiling (Metal)\n- **`scalene_accelerator.py`** - Generic accelerator interface\n- **`scalene_neuron.py`** - AWS Neuron support\n\n### Memory Profiling\n\n- **`scalene_memory_profiler.py`** - Memory profiling logic\n- **`scalene_leak_analysis.py`** - Memory leak detection (experimental, `--memory-leak-detector`)\n- **`scalene_mapfile.py`** - `ScaleneMapFile` for memory-mapped communication with native extension\n- **`scalene_preload.py`** - Sets up `LD_PRELOAD`/`DYLD_INSERT_LIBRARIES` for native memory tracking\n\n### Jupyter Integration\n\n- **`scalene_magics.py`** - Jupyter magic commands (`%scrun` for line mode, `%%scalene` for cell mode)\n- **`scalene_jupyter.py`** - Jupyter notebook support utilities\n\n### Replacement Modules (`replacement_*.py`)\n\nThese modules monkey-patch standard library functions to capture profiling data during blocking operations:\n- **`replacement_fork.py`** - Tracks `os.fork()`\n- **`replacement_exit.py`** - Tracks `sys.exit()`\n- **`replacement_lock.py`**, **`replacement_mp_lock.py`**, **`replacement_sem_lock.py`** - Lock acquisition timing\n- **`replacement_thread_join.py`**, **`replacement_pjoin.py`** - Thread/process join timing\n- **`replacement_signal_fns.py`** - Signal function replacements\n- **`replacement_poll_selector.py`** - I/O polling timing\n- **`replacement_get_context.py`** - Multiprocessing context\n\n### Utilities\n\n- **`runningstats.py`** - `RunningStats` class for online statistical calculations (mean, variance)\n- **`scalene_funcutils.py`** - Function utilities\n- **`scalene_utility.py`** - General utilities\n- **`sparkline.py`** - Sparkline generation for memory visualization\n- **`syntaxline.py`** - Syntax-highlighted source code lines\n- **`adaptive.py`** - Adaptive sampling logic\n- **`time_info.py`** - Time measurement utilities\n- **`sorted_reservoir.py`** - Reservoir sampling for bounded-size sample collection\n\n### GUI (`scalene/scalene-gui/`)\n\nWeb-based GUI built with TypeScript, bundled with esbuild.\n\n**Core Files:**\n- **`index.html.template`** - Jinja2 template for main GUI page (rendered by `scalene_utility.py`)\n- **`scalene-gui.ts`** - Main TypeScript entry point, UI event handlers, initialization\n- **`scalene-gui-bundle.js`** - Bundled JavaScript output (generated, do not edit directly)\n\n**AI Provider Modules:**\n- **`openai.ts`** - OpenAI API integration (`sendPromptToOpenAI`, `fetchOpenAIModels`)\n- **`anthropic.ts`** - Anthropic Claude API integration\n- **`gemini.ts`** - Google Gemini API integration (`sendPromptToGemini`, `fetchGeminiModels`)\n- **`optimizations.ts`** - Provider dispatch logic, prompt generation\n- **`persistence.ts`** - localStorage persistence with environment variable fallbacks\n\n**Support Files:**\n- **`launchbrowser.py`** - Opens browser to GUI (default port 11235)\n- **`find_browser.py`** - Cross-platform browser detection\n\n**Vendored Assets (for offline support):**\n- **`jquery-3.6.0.slim.min.js`** - jQuery (vendored locally, not loaded from CDN)\n- **`bootstrap.min.css`** - Bootstrap 5.1.3 CSS\n- **`bootstrap.bundle.min.js`** - Bootstrap 5.1.3 JS with Popper\n- **`prism.css`** - Syntax highlighting styles\n- **`favicon.ico`** - Scalene favicon\n- **`scalene-image.png`** - Scalene logo\n\nThese assets are copied, with the page, into a fresh private (0700) temp directory that `launchbrowser.py` serves and deletes on exit, enabling the GUI to work in air-gapped/offline environments. Never serve the shared system temp dir itself.\n\n**Building the GUI:**\n```bash\nnpm --prefix scalene/scalene-gui run build\n```\nThe `build` script in `scalene/scalene-gui/package.json` invokes esbuild\nwith `--minify --sourcemap --target=es2020`. The minified bundle is what\ngets checked in (≈1.1 MB vs ≈2.5 MB unminified). A `build:dev` variant\n(no minification) is available for debugging.\n\n### Native Extensions (`src/`)\n\nC++ code for low-overhead memory allocation tracking:\n\n**Headers (`src/include/`):**\n- **`sampleheap.hpp`** - Sampling heap allocator. Key constant `NEWLINE` must match Python config.\n- **`memcpysampler.hpp`** - Intercepts `memcpy` to track copy volume\n- **`pywhere.hpp`** - Tracks Python file/line info for allocations\n- **`samplefile.hpp`** - File-based communication with Python\n- **`sampler.hpp`**, **`poissonsampler.hpp`**, **`thresholdsampler.hpp`** - Sampling strategies\n- **`scaleneheader.hpp`** - Common header definitions\n\n**Sources (`src/source/`):**\n- **`libscalene.cpp`** - Main native library (loaded via `LD_PRELOAD`)\n- **`pywhere.cpp`** - Python location tracking implementation\n- **`get_line_atomic.cpp`** - Atomic line number access\n- **`traceconfig.cpp`** - Trace configuration\n\n### Vendor Libraries (`vendor/`)\n\n- **`Heap-Layers/`** - Memory allocator infrastructure (by Emery Berger)\n- **`printf/`** - Async-signal-safe printf implementation\n\n## Key Patterns\n\n### Python Version Compatibility\n\nThe codebase supports Python 3.8-3.14. Version-specific code uses:\n\n```python\nif sys.version_info >= (3, 14):\n    # Python 3.14+ specific code\nelse:\n    # Older Python versions\n```\n\n**Type Annotation Compatibility (Python 3.8/3.9):**\n- **Do NOT use `X | Y` union syntax** in runtime-evaluated annotations (PEP 604 requires Python 3.10+). Use `Optional[X]` or `Union[X, Y]` from `typing` instead.\n- **Do NOT use `list[X]`, `dict[K, V]`, `tuple[X, ...]`** in runtime-evaluated annotations (PEP 585 lowercase generics require Python 3.9+). Use `List`, `Dict`, `Tuple` from `typing` for 3.8 support.\n- Adding `from __future__ import annotations` makes all annotations strings (not evaluated at runtime), which allows modern syntax on older Python. However, this can break code that inspects annotations at runtime (e.g., dataclasses, pydantic).\n- The safest approach for this codebase: use `typing.Optional`, `typing.Union`, `typing.List`, `typing.Tuple`, `typing.Dict` in all annotation positions that are evaluated at runtime (function signatures, variable annotations outside `if TYPE_CHECKING` blocks).\n\n**Python 3.13 Changes (`dis` module):**\n- `dis.Instruction.starts_line` changed from `int | None` (line number) to `bool`\n- New `dis.Instruction.line_number` attribute (`int | None`) added for the actual line number\n- On Python < 3.13, `starts_line` is only set on the **first** instruction of each source line; use a line-tracking loop to propagate line numbers to subsequent instructions\n\n**Bytecode/Opcode Compatibility (`dis` module):**\n- **Never match specific opcode names** (e.g., `JUMP_BACKWARD`, `JUMP_ABSOLUTE`, `POP_JUMP_IF_TRUE`). Opcode names change across Python versions — for example, Python 3.10 while loops use `POP_JUMP_IF_TRUE` for backward jumps, Python 3.11+ uses `JUMP_BACKWARD`, and `JUMP_ABSOLUTE` was removed in 3.12.\n- **Always use abstract `dis` module categories** when possible: `dis.hasjabs` (absolute jump opcodes), `dis.hasjrel` (relative jump opcodes), `dis.hasconst`, `dis.hasname`, etc. These are maintained by CPython and work across all versions.\n- For call detection, matching `opname.startswith(\"CALL\")` is acceptable since that prefix has been stable, but prefer opcode integer sets over name strings for hot paths.\n- When checking jump direction (forward vs backward), use `instr.argval` (which `dis` resolves to an absolute offset) and compare against `instr.offset`, rather than relying on opcode names to imply direction.\n\n**Python 3.14 Changes:**\n- `argparse` now has built-in colored help output (`color=True` parameter)\n- `RichArgParser` uses Rich for colors on Python < 3.14, native argparse colors on 3.14+\n\n### Argument Parsing (`scalene_parseargs.py`)\n\n```python\nclass RichArgParser(argparse.ArgumentParser):\n    \"\"\"ArgumentParser that uses Rich for colored output on Python < 3.14.\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        if sys.version_info < (3, 14):\n            from rich.console import Console\n            self._console = Console()\n        else:\n            self._console = None\n        super().__init__(*args, **kwargs)\n```\n\nThe `_colorize_help_for_rich()` function applies Python 3.14-style colors using Rich markup:\n- `usage:` and `options:` → bold blue\n- Program name → bold magenta\n- Long options (`--foo`) → bold cyan\n- Short options (`-h`) → bold green\n- Metavars (`FOO`) → bold yellow\n\n### GUI Patterns\n\n**Preventing Browser Password Prompts:**\nUse `autocomplete=\"one-time-code\"` on password/API key inputs to prevent browsers from offering to save them:\n```html\n<input type=\"password\" id=\"api-key\" autocomplete=\"one-time-code\">\n```\n\n**Show/Hide Password Toggle:**\n```typescript\nfunction togglePassword(inputId: string, button: HTMLButtonElement): void {\n  const input = document.getElementById(inputId) as HTMLInputElement;\n  if (input.type === \"password\") {\n    input.type = \"text\";\n    button.textContent = \"Hide\";\n  } else {\n    input.type = \"password\";\n    button.textContent = \"Show\";\n  }\n}\n```\n\n**Provider Field Visibility:**\nUse CSS classes to show/hide provider-specific fields:\n```typescript\nfunction toggleServiceFields(): void {\n  const service = (document.getElementById(\"service\") as HTMLSelectElement).value;\n  // Hide all provider sections\n  document.querySelectorAll(\".provider-section\").forEach((el) => {\n    (el as HTMLElement).style.display = \"none\";\n  });\n  // Show selected provider section\n  const section = document.querySelector(`.${service}-fields`);\n  if (section) (section as HTMLElement).style.display = \"block\";\n}\n```\n\n**Persistent Form Elements:**\nAdd class `persistent` to inputs that should be saved/restored from localStorage:\n```html\n<input type=\"text\" id=\"api-key\" class=\"persistent\">\n```\nThe `persistence.ts` module handles save/restore automatically.\n\n**Standalone HTML Generation:**\nThe `generate_html()` function in `scalene_utility.py` supports a `standalone` parameter:\n- When `standalone=False` (default): Assets are referenced as local files (e.g., `<script src=\"jquery-3.6.0.slim.min.js\">`)\n- When `standalone=True`: All assets are embedded inline (JS/CSS as text, images as base64)\n\nThe Jinja2 template uses conditionals:\n```html\n{% if standalone %}\n<script>{{ jquery_js }}</script>\n<style>{{ bootstrap_css }}</style>\n{% else %}\n<script src=\"jquery-3.6.0.slim.min.js\"></script>\n<link href=\"bootstrap.min.css\" rel=\"stylesheet\">\n{% endif %}\n```\n\n### Module Imports\n\nWhen importing submodules, be explicit:\n\n```python\n# Correct - mypy can verify this\nimport importlib.util\nimportlib.util.find_spec(mod_name)\n\n# Wrong - mypy error: Module has no attribute \"util\"\nimport importlib\nimportlib.util.find_spec(mod_name)\n```\n\n## Testing\n\n### Test Files (`tests/`)\n\n- **`test_coverup_*.py`** - Auto-generated coverage tests\n- **`test_runningstats.py`** - Statistics tests (requires `hypothesis`)\n- **`test_scalene_json.py`** - JSON output tests (requires `hypothesis`)\n- **`test_nested_package_relative_import.py`** - Import handling tests\n\n### Test Dependencies\n\n```bash\npip install pytest pytest-asyncio hypothesis\n```\n\n### Running Tests Across Python Versions\n\n```bash\nfor v in 3.9 3.10 3.11 3.12 3.13 3.14; do\n    python$v -m pytest tests/test_coverup_83.py -v\ndone\n```\n\n### Flaky Smoketests\n\nThe smoketests in `test/` can be flaky due to timing/sampling issues inherent to profiling:\n\n- **\"No non-zero lines in X\"** - The profiler didn't collect enough samples. This happens when the test runs too quickly or signal delivery timing varies.\n- **\"Expected function 'X' not returned\"** - A function wasn't sampled. Common with short-running functions.\n\nThese failures are usually timing-related and pass on re-run. They're more common on CI due to variable machine load.\n\n### Port Binding in Tests\n\nWhen testing port availability, never use hardcoded ports - they may already be in use on CI runners:\n\n```python\n# Bad - port 49200 might be in use\nport = 49200\nsock.bind((\"\", port))\n\n# Good - find an available port first\nport = find_available_port(49200, 49300)\nif port is None:\n    return  # Skip test if no ports available\nsock.bind((\"\", port))\n```\n\n## CI/CD (`.github/workflows/`)\n\n- **`run-linters.yml`** - Runs mypy and ruff on Python 3.9-3.14\n- **`tests.yml`** - Runs pytest on Python 3.9-3.14\n- **`build-and-upload.yml`** - Build and publish to PyPI\n\n## Common Tasks\n\n### Adding a New CLI Option\n\n1. Add default value in `scalene_arguments.py`:\n   ```python\n   class ScaleneArgumentsDict(TypedDict, total=False):\n       my_option: bool\n   ```\n\n2. Add argument in `scalene_parseargs.py`:\n   ```python\n   parser.add_argument(\n       \"--my-option\",\n       dest=\"my_option\",\n       action=\"store_true\",\n       default=defaults.my_option,\n       help=\"Description of option\",\n   )\n   ```\n\n### Adding a New AI Provider\n\n1. **Create provider module** (`scalene/scalene-gui/newprovider.ts`):\n   ```typescript\n   export async function sendPromptToNewProvider(\n     prompt: string,\n     apiKey: string\n   ): Promise<string> {\n     // API call implementation\n   }\n\n   export async function fetchNewProviderModels(apiKey: string): Promise<string[]> {\n     // Optional: fetch available models from API\n   }\n   ```\n\n2. **Update `optimizations.ts`**:\n   - Import the new module\n   - Add case in `sendPromptToService()` switch statement\n\n3. **Update `index.html.template`**:\n   - Add option to `#service` select dropdown\n   - Add provider section with API key input, model selector, etc.\n   - Add CSS for `.newprovider-fields` visibility\n\n4. **Update `scalene-gui.ts`**:\n   - Add provider to `toggleServiceFields()` function\n   - Add refresh handler if dynamic model fetching is supported\n   - Update `getDefaultProvider()` if env var support is needed\n\n5. **Update `persistence.ts`** (for env var support):\n   - Add mapping in `envKeyMap` for new fields\n\n6. **Update `launchbrowser.py`** (for env var support):\n   - Add the field and its environment variable(s) to `ENV_API_KEY_VARIABLES`\n   - Add the field to `EnvApiKeys` / `envApiKeyFields` in `persistence.ts`\n\n7. **Rebuild the bundle**:\n   ```bash\n   npm --prefix scalene/scalene-gui run build\n   ```\n\n### Environment Variable API Keys\n\nWith `scalene view --api-keys-from-env` (opt-in), the GUI prepopulates API\nkeys from environment variables:\n\n| Element ID | Environment Variable | Provider |\n|------------|---------------------|----------|\n| `api-key` | `OPENAI_API_KEY` | OpenAI |\n| `anthropic-api-key` | `ANTHROPIC_API_KEY` | Anthropic |\n| `gemini-api-key` | `GEMINI_API_KEY` or `GOOGLE_API_KEY` | Gemini |\n| `azure-api-key` | `AZURE_OPENAI_API_KEY` | Azure OpenAI |\n| `azure-api-url` | `AZURE_OPENAI_ENDPOINT` | Azure OpenAI |\n| `aws-access-key` | `AWS_ACCESS_KEY_ID` | Amazon Bedrock |\n| `aws-secret-key` | `AWS_SECRET_ACCESS_KEY` | Amazon Bedrock |\n| `aws-region` | `AWS_DEFAULT_REGION` or `AWS_REGION` | Amazon Bedrock |\n\n**Flow:**\n1. `scalene view --api-keys-from-env` passes the flag to `launchbrowser.py`,\n   whose local server reads the env vars and serves them from memory at\n   `/env-api-keys.json` (an empty object without the flag)\n2. `loadEnvApiKeys()` in `persistence.ts` fetches that on page load (only when\n   the page is served over HTTP), before persistent fields are restored\n3. `persistence.ts` uses env vars as fallbacks when localStorage is empty;\n   `prefillNonPersistentEnvFields()` fills the non-persistent OpenAI key\n\n**Never embed credentials in the generated HTML.** `scalene-profile.html` is\nwritten to the working directory, copied to the system temp dir, and\n`--standalone` files exist to be shared (#1095;\n`tests/test_html_credential_security.py` enforces this). The endpoint answers\nonly requests whose `Host` is `localhost:<port>`/`127.0.0.1:<port>` (blocks DNS\nrebinding) and whose `Sec-Fetch-Site`, if sent, is same-origin; it sends no\nCORS headers. The flag is rejected with `--cli`, `--html`, and `--standalone`.\n\n### Updating Version\n\nEdit `scalene/scalene_config.py`:\n```python\nscalene_version = \"X.Y.Z\"\nscalene_date = \"YYYY.MM.DD\"\n```\n\n## Dependencies\n\nKey runtime dependencies:\n- `rich` - Terminal formatting and colors\n- `cloudpickle` - Serialization\n- `pynvml` - NVIDIA GPU support (optional)\n\nSee `requirements.txt` for full list.\n\n## CLI Structure\n\nScalene uses a verb-based CLI with two main subcommands:\n\n```bash\n# Profile a program (saves to scalene-profile.json by default)\nscalene run [options] yourprogram.py\n\n# View an existing profile\nscalene view [options] [profile.json]\n```\n\n### Run Subcommand Options\n\n```bash\nscalene run prog.py                      # profile, save to scalene-profile.json\nscalene run -o my.json prog.py           # save to custom file\nscalene run --cpu-only prog.py           # profile CPU only (faster)\nscalene run -c config.yaml prog.py       # load options from config file\nscalene run prog.py --- --arg            # pass args to program\n```\n\n### View Subcommand Options\n\n```bash\nscalene view                             # open in browser\nscalene view --cli                       # view in terminal\nscalene view --html                      # save to scalene-profile.html\nscalene view --standalone                # save as self-contained HTML (all assets embedded)\nscalene view myprofile.json              # open specific profile\n```\n\n### Profile Completion Message\n\nAfter profiling completes, Scalene prints instructions for viewing the profile:\n```\nScalene: profile saved to scalene-profile.json\n  To view in browser:  scalene view\n  To view in terminal: scalene view --cli\n```\n\nThe filename is only included in the command if a non-default output file was used.\n\n### YAML Configuration\n\nCreate a `scalene.yaml` file with options:\n\n```yaml\noutfile: my-profile.json\ncpu-only: true\nprofile-only: \"mypackage,utils\"\ncpu-percent-threshold: 5\n```\n\nLoad with: `scalene run -c scalene.yaml prog.py`\n\n### Advanced Options\n\nUse `scalene run --help-advanced` to see all options including:\n- `--profile-all` - profile all code, not just the target program\n- `--profile-only PATH` - only profile files containing these strings\n- `--profile-exclude PATH` - exclude files containing these strings\n- `--profile-system-libraries` - profile Python stdlib and installed packages (skipped by default)\n- `--gpu` - profile GPU time and memory\n- `--memory` - profile memory usage\n- `--stacks` - collect stack traces\n- `--profile-interval N` - output profiles every N seconds\n\n### Smoke Tests\n\nSmoke tests in `test/` use the new CLI syntax:\n\n```python\n# test/smoketest.py\ncmd = [sys.executable, \"-m\", \"scalene\", \"run\", \"-o\", str(outfile), *rest, fname]\n```\n\n### GitHub Workflows\n\nWorkflows in `.github/workflows/` use the new CLI:\n\n```yaml\n# Profile with interval, then view\n- run: python -m scalene run --profile-interval=2 test/testme.py && python -m scalene view --cli\n\n# Profile with module invocation\n- run: python -m scalene run --- -m import_stress_test && python -m scalene view --cli\n```\n\n## Signal Handling\n\nScalene uses several Unix signals for profiling. The signal assignments are in `scalene_signals.py`:\n\n| Signal | Purpose | Platform |\n|--------|---------|----------|\n| `SIGVTALRM` | CPU profiling timer (default) | Unix |\n| `SIGALRM` | CPU profiling timer (real time mode) | Unix |\n| `SIGILL` | Start profiling (`--on`) | Unix |\n| `SIGBUS` | Stop profiling (`--off`) | Unix |\n| `SIGPROF` | memcpy tracking | Unix |\n| `SIGXCPU` | malloc tracking | Unix |\n| `SIGXFSZ` | free tracking | Unix |\n\n### Signal Conflicts with Libraries\n\nLibraries like PyTorch Lightning may also use these signals. The `replacement_signal_fns.py` module handles conflicts:\n\n**On Linux:** Uses real-time signals (`SIGRTMIN+1` to `SIGRTMIN+5`) for redirection. When user code sets a handler for a Scalene signal, their handler is redirected to a real-time signal. Calls to `raise_signal()` and `kill()` are also redirected transparently.\n\n**On macOS/other platforms:** Uses handler chaining. Both Scalene's handler and the user's handler are called when the signal fires.\n\n```python\n# Platform-specific signal handling\n_use_rt_signals = sys.platform == \"linux\" and hasattr(signal, \"SIGRTMIN\")\n\nif _use_rt_signals:\n    # Linux: redirect to real-time signals\n    rt_base = signal.SIGRTMIN + 1\n    _signal_redirects[signal.SIGILL] = rt_base\nelse:\n    # macOS: chain handlers\n    def chained_handler(sig, frame):\n        scalene_handler(sig, frame)\n        user_handler(sig, frame)\n```\n\n### Frame Line Number Can Be None (Python 3.11+)\n\nIn Python 3.11+, `frame.f_lineno` can be `None` in edge cases (e.g., during multiprocessing cleanup). Always use a fallback:\n\n```python\nlineno = frame.f_lineno if frame.f_lineno is not None else frame.f_code.co_firstlineno\n```\n\n## Native Extension Build Issues\n\n### C++ Standard Library Conflicts with vendor/printf\n\nThe `vendor/printf/printf.h` header defines macros that conflict with C++ standard library:\n\n```c\n#define vsnprintf vsnprintf_\n#define snprintf  snprintf_\n```\n\nThis breaks `std::vsnprintf` in `<string>` and other headers. **Fix:** Include C++ standard headers BEFORE vendor headers in `src/source/libscalene.cpp`:\n\n```cpp\n// Include C++ standard headers FIRST\n#include <cstddef>\n#include <string>\n\n// Then vendor headers that define conflicting macros\n#include <heaplayers.h>  // Eventually includes printf.h\n```\n\n## Profiling Guide\n\nSee [Scalene-Agents.md](Scalene-Agents.md) for detailed information about interpreting Scalene's profiling output, including Python vs C time, memory metrics, and optimization strategies.\n\n## Debugging Guide\n\nSee [Scalene-Debugging.md](Scalene-Debugging.md) for signal handler debugging, async profiling debugging, the profile output pipeline (three separate renderers!), and unbounded growth prevention patterns.\n\n## GUI Development Guide\n\nSee [Scalene-GUI.md](Scalene-GUI.md) for adding new columns, Vega-Lite chart types, pie chart best practices (two-wedge rendering, rotating pies), and the chart rendering flow.\n"},"items":[{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/plasma-umass/scalene/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# Scalene Development Guide\n\n## Project Overview\n\nScalene is a high-performance CPU, GPU, and memory profiler for Python with AI-powered optimization proposals. It runs significantly faster than other Python profilers while providing detailed performance information. See the paper `docs/osdi23-berger.pdf` for technical details on Scalene's design.\n\n**Key features:**\n- CPU, GPU (NVIDIA/Apple), and memory profiling\n- AI-powered optimization suggestions (OpenAI, Anthropic, Azure, Amazon Bedrock, Gemini, Ollama)\n- Web-based GUI and CLI interfaces\n- Jupyter notebook support via magic commands (`%scrun`, `%%scalene`)\n- Line-by-line profiling with low overhead\n- Separates Python time from native/C time\n\n**Platform support:** Linux, macOS, WSL 2 (full support); Windows (partial support)\n\n## Build & Test Commands\n\n```bash\n# Install in development mode\npip install -e .\n\n# Run all tests\npython3 -m pytest tests/\n\n# Run tests for a specific Python version\npython3.X -m pytest tests/\n\n# Run linters\nmypy scalene\nruff check scalene\n\n# Run a single test file\npython3 -m pytest tests/test_coverup_83.py -v\n```\n\n## Project Structure\n\n### Core Profiler Components (`scalene/`)\n\n- **`scalene_profiler.py`** - Main profiler class (`Scalene`). Entry point for profiling. Uses signal-based sampling for CPU profiling. Coordinates all profiling subsystems.\n- **`scalene_statistics.py`** - `ScaleneStatistics` class. Collects and aggregates profiling data. Key types: `ProfilingSample`, `MemcpyProfilingSample`. Uses `RunningStats` for statistical aggregation.\n- **`scalene_output.py`** - Profile output formatting for CLI/HTML\n- **`scalene_json.py`** - `ScaleneJSON` class for JSON output format\n- **`scalene_analysis.py`** - Profile analysis logic\n\n### Entry Points\n\n- **`__main__.py`** - Entry point for `python -m scalene`\n- **`profile.py`** - Entry point for `--on`/`--off` control of background profiling\n\n### Configuration & Arguments\n\n- **`scalene_config.py`** - Version info (`scalene_version`, `scalene_date`) and constants:\n  - `SCALENE_PORT = 11235` - Default port for web UI\n  - `NEWLINE_TRIGGER_LENGTH` - Must match `src/include/sampleheap.hpp`\n- **`scalene_arguments.py`** - `ScaleneArguments` class (extends `argparse.Namespace`) with all profiler options and their defaults defined in `ScaleneArgumentsDict`\n- **`scalene_parseargs.py`** - `ScaleneParseArgs.parse_args()` builds the argument parser. `RichArgParser` provides colored help output (uses Rich on Python < 3.14, native argparse colors on 3.14+)\n\n### Signal Handling\n\n- **`scalene_signals.py`** - Signal definitions for CPU sampling\n- **`scalene_signal_manager.py`** - Manages signal handlers\n- **`scalene_sigqueue.py`** - Signal queue management\n- **`scalene_client_timer.py`** - Timer for periodic profiling\n\n### GPU Support\n\n- **`scalene_nvidia_gpu.py`** - NVIDIA GPU profiling via `pynvml`\n- **`scalene_apple_gpu.py`** - Apple GPU profiling (Metal)\n- **`scalene_accelerator.py`** - Generic accelerator interface\n- **`scalene_neuron.py`** - AWS Neuron support\n\n### Memory Profiling\n\n- **`scalene_memory_profiler.py`** - Memory profiling logic\n- **`scalene_leak_analysis.py`** - Memory leak detection (experimental, `--memory-leak-detector`)\n- **`scalene_mapfile.py`** - `ScaleneMapFile` for memory-mapped communication with native extension\n- **`scalene_preload.py`** - Sets up `LD_PRELOAD`/`DYLD_INSERT_LIBRARIES` for native memory tracking\n\n### Jupyter Integration\n\n- **`scalene_magics.py`** - Jupyter magic commands (`%scrun` for line mode, `%%scalene` for cell mode)\n- **`scalene_jupyter.py`** - Jupyter notebook support utilities\n\n### Replacement Modules (`replacement_*.py`)\n\nThese modules monkey-patch standard library functions to capture profiling data during blocking operations:\n- **`replacement_fork.py`** - Tracks `os.fork()`\n- **`replacement_exit.py`** - Tracks `sys.exit()`\n- **`replacement_lock.py`**, **`replacement_mp_lock.py`**, **`replacement_sem_lock.py`** - Lock acquisition timing\n- **`replacement_thread_join.py`**, **`replacement_pjoin.py`** - Thread/process join timing\n- **`replacement_signal_fns.py`** - Signal function replacements\n- **`replacement_poll_selector.py`** - I/O polling timing\n- **`replacement_get_context.py`** - Multiprocessing context\n\n### Utilities\n\n- **`runningstats.py`** - `RunningStats` class for online statistical calculations (mean, variance)\n- **`scalene_funcutils.py`** - Function utilities\n- **`scalene_utility.py`** - General utilities\n- **`sparkline.py`** - Sparkline generation for memory visualization\n- **`syntaxline.py`** - Syntax-highlighted source code lines\n- **`adaptive.py`** - Adaptive sampling logic\n- **`time_info.py`** - Time measurement utilities\n- **`sorted_reservoir.py`** - Reservoir sampling for bounded-size sample collection\n\n### GUI (`scalene/scalene-gui/`)\n\nWeb-based GUI built with TypeScript, bundled with esbuild.\n\n**Core Files:**\n- **`index.html.template`** - Jinja2 template for main GUI page (rendered by `scalene_utility.py`)\n- **`scalene-gui.ts`** - Main TypeScript entry point, UI event handlers, initialization\n- **`scalene-gui-bundle.js`** - Bundled JavaScript output (generated, do not edit directly)\n\n**AI Provider Modules:**\n- **`openai.ts`** - OpenAI API integration (`sendPromptToOpenAI`, `fetchOpenAIModels`)\n- **`anthropic.ts`** - Anthropic Claude API integration\n- **`gemini.ts`** - Google Gemini API integration (`sendPromptToGemini`, `fetchGeminiModels`)\n- **`optimizations.ts`** - Provider dispatch logic, prompt generation\n- **`persistence.ts`** - localStorage persistence with environment variable fallbacks\n\n**Support Files:**\n- **`launchbrowser.py`** - Opens browser to GUI (default port 11235)\n- **`find_browser.py`** - Cross-platform browser detection\n\n**Vendored Assets (for offline support):**\n- **`jquery-3.6.0.slim.min.js`** - jQuery (vendored locally, not loaded from CDN)\n- **`bootstrap.min.css`** - Bootstrap 5.1.3 CSS\n- **`bootstrap.bundle.min.js`** - Bootstrap 5.1.3 JS with Popper\n- **`prism.css`** - Syntax highlighting styles\n- **`favicon.ico`** - Scalene favicon\n- **`scalene-image.png`** - Scalene logo\n\nThese assets are copied, with the page, into a fresh private (0700) temp directory that `launchbrowser.py` serves and deletes on exit, enabling the GUI to work in air-gapped/offline environments. Never serve the shared system temp dir itself.\n\n**Building the GUI:**\n```bash\nnpm --prefix scalene/scalene-gui run build\n```\nThe `build` script in `scalene/scalene-gui/package.json` invokes esbuild\nwith `--minify --sourcemap --target=es2020`. The minified bundle is what\ngets checked in (≈1.1 MB vs ≈2.5 MB unminified). A `build:dev` variant\n(no minification) is available for debugging.\n\n### Native Extensions (`src/`)\n\nC++ code for low-overhead memory allocation tracking:\n\n**Headers (`src/include/`):**\n- **`sampleheap.hpp`** - Sampling heap allocator. Key constant `NEWLINE` must match Python config.\n- **`memcpysampler.hpp`** - Intercepts `memcpy` to track copy volume\n- **`pywhere.hpp`** - Tracks Python file/line info for allocations\n- **`samplefile.hpp`** - File-based communication with Python\n- **`sampler.hpp`**, **`poissonsampler.hpp`**, **`thresholdsampler.hpp`** - Sampling strategies\n- **`scaleneheader.hpp`** - Common header definitions\n\n**Sources (`src/source/`):**\n- **`libscalene.cpp`** - Main native library (loaded via `LD_PRELOAD`)\n- **`pywhere.cpp`** - Python location tracking implementation\n- **`get_line_atomic.cpp`** - Atomic line number access\n- **`traceconfig.cpp`** - Trace configuration\n\n### Vendor Libraries (`vendor/`)\n\n- **`Heap-Layers/`** - Memory allocator infrastructure (by Emery Berger)\n- **`printf/`** - Async-signal-safe printf implementation\n\n## Key Patterns\n\n### Python Version Compatibility\n\nThe codebase supports Python 3.8-3.14. Version-specific code uses:\n\n```python\nif sys.version_info >= (3, 14):\n    # Python 3.14+ specific code\nelse:\n    # Older Python versions\n```\n\n**Type Annotation Compatibility (Python 3.8/3.9):**\n- **Do NOT use `X | Y` union syntax** in runtime-evaluated annotations (PEP 604 requires Python 3.10+). Use `Optional[X]` or `Union[X, Y]` from `typing` instead.\n- **Do NOT use `list[X]`, `dict[K, V]`, `tuple[X, ...]`** in runtime-evaluated annotations (PEP 585 lowercase generics require Python 3.9+). Use `List`, `Dict`, `Tuple` from `typing` for 3.8 support.\n- Adding `from __future__ import annotations` makes all annotations strings (not evaluated at runtime), which allows modern syntax on older Python. However, this can break code that inspects annotations at runtime (e.g., dataclasses, pydantic).\n- The safest approach for this codebase: use `typing.Optional`, `typing.Union`, `typing.List`, `typing.Tuple`, `typing.Dict` in all annotation positions that are evaluated at runtime (function signatures, variable annotations outside `if TYPE_CHECKING` blocks).\n\n**Python 3.13 Changes (`dis` module):**\n- `dis.Instruction.starts_line` changed from `int | None` (line number) to `bool`\n- New `dis.Instruction.line_number` attribute (`int | None`) added for the actual line number\n- On Python < 3.13, `starts_line` is only set on the **first** instruction of each source line; use a line-tracking loop to propagate line numbers to subsequent instructions\n\n**Bytecode/Opcode Compatibility (`dis` module):**\n- **Never match specific opcode names** (e.g., `JUMP_BACKWARD`, `JUMP_ABSOLUTE`, `POP_JUMP_IF_TRUE`). Opcode names change across Python versions — for example, Python 3.10 while loops use `POP_JUMP_IF_TRUE` for backward jumps, Python 3.11+ uses `JUMP_BACKWARD`, and `JUMP_ABSOLUTE` was removed in 3.12.\n- **Always use abstract `dis` module categories** when possible: `dis.hasjabs` (absolute jump opcodes), `dis.hasjrel` (relative jump opcodes), `dis.hasconst`, `dis.hasname`, etc. These are maintained by CPython and work across all versions.\n- For call detection, matching `opname.startswith(\"CALL\")` is acceptable since that prefix has been stable, but prefer opcode integer sets over name strings for hot paths.\n- When checking jump direction (forward vs backward), use `instr.argval` (which `dis` resolves to an absolute offset) and compare against `instr.offset`, rather than relying on opcode names to imply direction.\n\n**Python 3.14 Changes:**\n- `argparse` now has built-in colored help output (`color=True` parameter)\n- `RichArgParser` uses Rich for colors on Python < 3.14, native argparse colors on 3.14+\n\n### Argument Parsing (`scalene_parseargs.py`)\n\n```python\nclass RichArgParser(argparse.ArgumentParser):\n    \"\"\"ArgumentParser that uses Rich for colored output on Python < 3.14.\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        if sys.version_info < (3, 14):\n            from rich.console import Console\n            self._console = Console()\n        else:\n            self._console = None\n        super().__init__(*args, **kwargs)\n```\n\nThe `_colorize_help_for_rich()` function applies Python 3.14-style colors using Rich markup:\n- `usage:` and `options:` → bold blue\n- Program name → bold magenta\n- Long options (`--foo`) → bold cyan\n- Short options (`-h`) → bold green\n- Metavars (`FOO`) → bold yellow\n\n### GUI Patterns\n\n**Preventing Browser Password Prompts:**\nUse `autocomplete=\"one-time-code\"` on password/API key inputs to prevent browsers from offering to save them:\n```html\n<input type=\"password\" id=\"api-key\" autocomplete=\"one-time-code\">\n```\n\n**Show/Hide Password Toggle:**\n```typescript\nfunction togglePassword(inputId: string, button: HTMLButtonElement): void {\n  const input = document.getElementById(inputId) as HTMLInputElement;\n  if (input.type === \"password\") {\n    input.type = \"text\";\n    button.textContent = \"Hide\";\n  } else {\n    input.type = \"password\";\n    button.textContent = \"Show\";\n  }\n}\n```\n\n**Provider Field Visibility:**\nUse CSS classes to show/hide provider-specific fields:\n```typescript\nfunction toggleServiceFields(): void {\n  const service = (document.getElementById(\"service\") as HTMLSelectElement).value;\n  // Hide all provider sections\n  document.querySelectorAll(\".provider-section\").forEach((el) => {\n    (el as HTMLElement).style.display = \"none\";\n  });\n  // Show selected provider section\n  const section = document.querySelector(`.${service}-fields`);\n  if (section) (section as HTMLElement).style.display = \"block\";\n}\n```\n\n**Persistent Form Elements:**\nAdd class `persistent` to inputs that should be saved/restored from localStorage:\n```html\n<input type=\"text\" id=\"api-key\" class=\"persistent\">\n```\nThe `persistence.ts` module handles save/restore automatically.\n\n**Standalone HTML Generation:**\nThe `generate_html()` function in `scalene_utility.py` supports a `standalone` parameter:\n- When `standalone=False` (default): Assets are referenced as local files (e.g., `<script src=\"jquery-3.6.0.slim.min.js\">`)\n- When `standalone=True`: All assets are embedded inline (JS/CSS as text, images as base64)\n\nThe Jinja2 template uses conditionals:\n```html\n{% if standalone %}\n<script>{{ jquery_js }}</script>\n<style>{{ bootstrap_css }}</style>\n{% else %}\n<script src=\"jquery-3.6.0.slim.min.js\"></script>\n<link href=\"bootstrap.min.css\" rel=\"stylesheet\">\n{% endif %}\n```\n\n### Module Imports\n\nWhen importing submodules, be explicit:\n\n```python\n# Correct - mypy can verify this\nimport importlib.util\nimportlib.util.find_spec(mod_name)\n\n# Wrong - mypy error: Module has no attribute \"util\"\nimport importlib\nimportlib.util.find_spec(mod_name)\n```\n\n## Testing\n\n### Test Files (`tests/`)\n\n- **`test_coverup_*.py`** - Auto-generated coverage tests\n- **`test_runningstats.py`** - Statistics tests (requires `hypothesis`)\n- **`test_scalene_json.py`** - JSON output tests (requires `hypothesis`)\n- **`test_nested_package_relative_import.py`** - Import handling tests\n\n### Test Dependencies\n\n```bash\npip install pytest pytest-asyncio hypothesis\n```\n\n### Running Tests Across Python Versions\n\n```bash\nfor v in 3.9 3.10 3.11 3.12 3.13 3.14; do\n    python$v -m pytest tests/test_coverup_83.py -v\ndone\n```\n\n### Flaky Smoketests\n\nThe smoketests in `test/` can be flaky due to timing/sampling issues inherent to profiling:\n\n- **\"No non-zero lines in X\"** - The profiler didn't collect enough samples. This happens when the test runs too quickly or signal delivery timing varies.\n- **\"Expected function 'X' not returned\"** - A function wasn't sampled. Common with short-running functions.\n\nThese failures are usually timing-related and pass on re-run. They're more common on CI due to variable machine load.\n\n### Port Binding in Tests\n\nWhen testing port availability, never use hardcoded ports - they may already be in use on CI runners:\n\n```python\n# Bad - port 49200 might be in use\nport = 49200\nsock.bind((\"\", port))\n\n# Good - find an available port first\nport = find_available_port(49200, 49300)\nif port is None:\n    return  # Skip test if no ports available\nsock.bind((\"\", port))\n```\n\n## CI/CD (`.github/workflows/`)\n\n- **`run-linters.yml`** - Runs mypy and ruff on Python 3.9-3.14\n- **`tests.yml`** - Runs pytest on Python 3.9-3.14\n- **`build-and-upload.yml`** - Build and publish to PyPI\n\n## Common Tasks\n\n### Adding a New CLI Option\n\n1. Add default value in `scalene_arguments.py`:\n   ```python\n   class ScaleneArgumentsDict(TypedDict, total=False):\n       my_option: bool\n   ```\n\n2. Add argument in `scalene_parseargs.py`:\n   ```python\n   parser.add_argument(\n       \"--my-option\",\n       dest=\"my_option\",\n       action=\"store_true\",\n       default=defaults.my_option,\n       help=\"Description of option\",\n   )\n   ```\n\n### Adding a New AI Provider\n\n1. **Create provider module** (`scalene/scalene-gui/newprovider.ts`):\n   ```typescript\n   export async function sendPromptToNewProvider(\n     prompt: string,\n     apiKey: string\n   ): Promise<string> {\n     // API call implementation\n   }\n\n   export async function fetchNewProviderModels(apiKey: string): Promise<string[]> {\n     // Optional: fetch available models from API\n   }\n   ```\n\n2. **Update `optimizations.ts`**:\n   - Import the new module\n   - Add case in `sendPromptToService()` switch statement\n\n3. **Update `index.html.template`**:\n   - Add option to `#service` select dropdown\n   - Add provider section with API key input, model selector, etc.\n   - Add CSS for `.newprovider-fields` visibility\n\n4. **Update `scalene-gui.ts`**:\n   - Add provider to `toggleServiceFields()` function\n   - Add refresh handler if dynamic model fetching is supported\n   - Update `getDefaultProvider()` if env var support is needed\n\n5. **Update `persistence.ts`** (for env var support):\n   - Add mapping in `envKeyMap` for new fields\n\n6. **Update `launchbrowser.py`** (for env var support):\n   - Add the field and its environment variable(s) to `ENV_API_KEY_VARIABLES`\n   - Add the field to `EnvApiKeys` / `envApiKeyFields` in `persistence.ts`\n\n7. **Rebuild the bundle**:\n   ```bash\n   npm --prefix scalene/scalene-gui run build\n   ```\n\n### Environment Variable API Keys\n\nWith `scalene view --api-keys-from-env` (opt-in), the GUI prepopulates API\nkeys from environment variables:\n\n| Element ID | Environment Variable | Provider |\n|------------|---------------------|----------|\n| `api-key` | `OPENAI_API_KEY` | OpenAI |\n| `anthropic-api-key` | `ANTHROPIC_API_KEY` | Anthropic |\n| `gemini-api-key` | `GEMINI_API_KEY` or `GOOGLE_API_KEY` | Gemini |\n| `azure-api-key` | `AZURE_OPENAI_API_KEY` | Azure OpenAI |\n| `azure-api-url` | `AZURE_OPENAI_ENDPOINT` | Azure OpenAI |\n| `aws-access-key` | `AWS_ACCESS_KEY_ID` | Amazon Bedrock |\n| `aws-secret-key` | `AWS_SECRET_ACCESS_KEY` | Amazon Bedrock |\n| `aws-region` | `AWS_DEFAULT_REGION` or `AWS_REGION` | Amazon Bedrock |\n\n**Flow:**\n1. `scalene view --api-keys-from-env` passes the flag to `launchbrowser.py`,\n   whose local server reads the env vars and serves them from memory at\n   `/env-api-keys.json` (an empty object without the flag)\n2. `loadEnvApiKeys()` in `persistence.ts` fetches that on page load (only when\n   the page is served over HTTP), before persistent fields are restored\n3. `persistence.ts` uses env vars as fallbacks when localStorage is empty;\n   `prefillNonPersistentEnvFields()` fills the non-persistent OpenAI key\n\n**Never embed credentials in the generated HTML.** `scalene-profile.html` is\nwritten to the working directory, copied to the system temp dir, and\n`--standalone` files exist to be shared (#1095;\n`tests/test_html_credential_security.py` enforces this). The endpoint answers\nonly requests whose `Host` is `localhost:<port>`/`127.0.0.1:<port>` (blocks DNS\nrebinding) and whose `Sec-Fetch-Site`, if sent, is same-origin; it sends no\nCORS headers. The flag is rejected with `--cli`, `--html`, and `--standalone`.\n\n### Updating Version\n\nEdit `scalene/scalene_config.py`:\n```python\nscalene_version = \"X.Y.Z\"\nscalene_date = \"YYYY.MM.DD\"\n```\n\n## Dependencies\n\nKey runtime dependencies:\n- `rich` - Terminal formatting and colors\n- `cloudpickle` - Serialization\n- `pynvml` - NVIDIA GPU support (optional)\n\nSee `requirements.txt` for full list.\n\n## CLI Structure\n\nScalene uses a verb-based CLI with two main subcommands:\n\n```bash\n# Profile a program (saves to scalene-profile.json by default)\nscalene run [options] yourprogram.py\n\n# View an existing profile\nscalene view [options] [profile.json]\n```\n\n### Run Subcommand Options\n\n```bash\nscalene run prog.py                      # profile, save to scalene-profile.json\nscalene run -o my.json prog.py           # save to custom file\nscalene run --cpu-only prog.py           # profile CPU only (faster)\nscalene run -c config.yaml prog.py       # load options from config file\nscalene run prog.py --- --arg            # pass args to program\n```\n\n### View Subcommand Options\n\n```bash\nscalene view                             # open in browser\nscalene view --cli                       # view in terminal\nscalene view --html                      # save to scalene-profile.html\nscalene view --standalone                # save as self-contained HTML (all assets embedded)\nscalene view myprofile.json              # open specific profile\n```\n\n### Profile Completion Message\n\nAfter profiling completes, Scalene prints instructions for viewing the profile:\n```\nScalene: profile saved to scalene-profile.json\n  To view in browser:  scalene view\n  To view in terminal: scalene view --cli\n```\n\nThe filename is only included in the command if a non-default output file was used.\n\n### YAML Configuration\n\nCreate a `scalene.yaml` file with options:\n\n```yaml\noutfile: my-profile.json\ncpu-only: true\nprofile-only: \"mypackage,utils\"\ncpu-percent-threshold: 5\n```\n\nLoad with: `scalene run -c scalene.yaml prog.py`\n\n### Advanced Options\n\nUse `scalene run --help-advanced` to see all options including:\n- `--profile-all` - profile all code, not just the target program\n- `--profile-only PATH` - only profile files containing these strings\n- `--profile-exclude PATH` - exclude files containing these strings\n- `--profile-system-libraries` - profile Python stdlib and installed packages (skipped by default)\n- `--gpu` - profile GPU time and memory\n- `--memory` - profile memory usage\n- `--stacks` - collect stack traces\n- `--profile-interval N` - output profiles every N seconds\n\n### Smoke Tests\n\nSmoke tests in `test/` use the new CLI syntax:\n\n```python\n# test/smoketest.py\ncmd = [sys.executable, \"-m\", \"scalene\", \"run\", \"-o\", str(outfile), *rest, fname]\n```\n\n### GitHub Workflows\n\nWorkflows in `.github/workflows/` use the new CLI:\n\n```yaml\n# Profile with interval, then view\n- run: python -m scalene run --profile-interval=2 test/testme.py && python -m scalene view --cli\n\n# Profile with module invocation\n- run: python -m scalene run --- -m import_stress_test && python -m scalene view --cli\n```\n\n## Signal Handling\n\nScalene uses several Unix signals for profiling. The signal assignments are in `scalene_signals.py`:\n\n| Signal | Purpose | Platform |\n|--------|---------|----------|\n| `SIGVTALRM` | CPU profiling timer (default) | Unix |\n| `SIGALRM` | CPU profiling timer (real time mode) | Unix |\n| `SIGILL` | Start profiling (`--on`) | Unix |\n| `SIGBUS` | Stop profiling (`--off`) | Unix |\n| `SIGPROF` | memcpy tracking | Unix |\n| `SIGXCPU` | malloc tracking | Unix |\n| `SIGXFSZ` | free tracking | Unix |\n\n### Signal Conflicts with Libraries\n\nLibraries like PyTorch Lightning may also use these signals. The `replacement_signal_fns.py` module handles conflicts:\n\n**On Linux:** Uses real-time signals (`SIGRTMIN+1` to `SIGRTMIN+5`) for redirection. When user code sets a handler for a Scalene signal, their handler is redirected to a real-time signal. Calls to `raise_signal()` and `kill()` are also redirected transparently.\n\n**On macOS/other platforms:** Uses handler chaining. Both Scalene's handler and the user's handler are called when the signal fires.\n\n```python\n# Platform-specific signal handling\n_use_rt_signals = sys.platform == \"linux\" and hasattr(signal, \"SIGRTMIN\")\n\nif _use_rt_signals:\n    # Linux: redirect to real-time signals\n    rt_base = signal.SIGRTMIN + 1\n    _signal_redirects[signal.SIGILL] = rt_base\nelse:\n    # macOS: chain handlers\n    def chained_handler(sig, frame):\n        scalene_handler(sig, frame)\n        user_handler(sig, frame)\n```\n\n### Frame Line Number Can Be None (Python 3.11+)\n\nIn Python 3.11+, `frame.f_lineno` can be `None` in edge cases (e.g., during multiprocessing cleanup). Always use a fallback:\n\n```python\nlineno = frame.f_lineno if frame.f_lineno is not None else frame.f_code.co_firstlineno\n```\n\n## Native Extension Build Issues\n\n### C++ Standard Library Conflicts with vendor/printf\n\nThe `vendor/printf/printf.h` header defines macros that conflict with C++ standard library:\n\n```c\n#define vsnprintf vsnprintf_\n#define snprintf  snprintf_\n```\n\nThis breaks `std::vsnprintf` in `<string>` and other headers. **Fix:** Include C++ standard headers BEFORE vendor headers in `src/source/libscalene.cpp`:\n\n```cpp\n// Include C++ standard headers FIRST\n#include <cstddef>\n#include <string>\n\n// Then vendor headers that define conflicting macros\n#include <heaplayers.h>  // Eventually includes printf.h\n```\n\n## Profiling Guide\n\nSee [Scalene-Agents.md](Scalene-Agents.md) for detailed information about interpreting Scalene's profiling output, including Python vs C time, memory metrics, and optimization strategies.\n\n## Debugging Guide\n\nSee [Scalene-Debugging.md](Scalene-Debugging.md) for signal handler debugging, async profiling debugging, the profile output pipeline (three separate renderers!), and unbounded growth prevention patterns.\n\n## GUI Development Guide\n\nSee [Scalene-GUI.md](Scalene-GUI.md) for adding new columns, Vega-Lite chart types, pie chart best practices (two-wedge rendering, rotating pies), and the chart rendering flow.\n","isInternal":false,"tokens":6267,"sizeBytes":24563},{"name":"windows-memory-profiling-port.md","path":"agents/windows-memory-profiling-port.md","rawUrl":"https://raw.githubusercontent.com/plasma-umass/scalene/HEAD/agents/windows-memory-profiling-port.md","title":"Subagent: windows-memory-profiling-port","category":"subagent-persona","format":"markdown","content":"# Windows Memory Profiling Port - Progress and Plans\n\n## Overview\n\nPorting Scalene's memory profiling from Linux/macOS to Windows. On Unix systems, Scalene uses `LD_PRELOAD`/`DYLD_INSERT_LIBRARIES` to interpose on malloc/free. Windows doesn't support this mechanism, so we use Python's allocator API instead.\n\n## Architecture\n\n### Unix Approach\n- `libscalene.so`/`libscalene.dylib` is preloaded via environment variables\n- Interposes on malloc/free/realloc at the C library level\n- Uses Unix signals (SIGXCPU, SIGXFSZ) to communicate with Python\n- Uses POSIX shared memory (`/tmp/scalene-*`) for data transfer\n\n### Windows Approach\n- `libscalene.dll` is loaded explicitly via ctypes\n- Uses Python's `PyMem_SetAllocator` API to intercept allocations\n- Uses Windows Events instead of Unix signals (but currently using polling)\n- Uses Windows Named Shared Memory (`Local\\scalene-*`) for data transfer\n\n## Files Created/Modified\n\n### New Files\n- `src/source/libscalene_windows.cpp` - Main Windows DLL implementation (includes Detours native hooks)\n- `src/include/samplefile_win.hpp` - Windows shared memory implementation\n- `src/include/common_win.hpp` - Windows compatibility macros\n- `src/include/mallocrecursionguard_win.hpp` - Windows TLS-based recursion guard\n- `scalene/scalene_windows.py` - Python helper for Windows memory profiling\n- `vendor/Detours/` - Microsoft Detours library for native malloc/free hooking (vendored)\n\n### Modified Files\n- `CMakeLists.txt` - Cross-platform build configuration with Detours integration\n- `src/include/pywhere.hpp` - Windows DLL export/import macros\n- `src/source/pywhere.cpp` - Windows symbol lookup via accessor functions\n- `scalene/scalene_profiler.py` - Windows DLL loading and initialization\n- `scalene/scalene_mapfile.py` - Windows named shared memory support\n- `scalene/scalene_signal_manager.py` - Windows memory polling thread\n- `scalene/scalene_preload.py` - Case-insensitive ARM64 detection\n- `scalene/scalene_arguments.py` - Enable memory profiling on Windows\n\n## Current Status (2024-12-17) - FULLY WORKING WITH NATIVE LIBRARY SUPPORT!\n\n### All Core Features Working\n1. ✅ DLL builds successfully with CMake/MSBuild for ARM64\n2. ✅ DLL loads without crashing\n3. ✅ Python allocator hooks are installed via `PyMem_SetAllocator`\n4. ✅ Allocation size tracking implemented (hash map with critical section)\n5. ✅ Shared memory objects created successfully (both malloc and memcpy)\n6. ✅ ScaleneMapFile opens shared memory from Python side successfully\n7. ✅ All `thread_local` variables removed (caused crashes with dynamically loaded DLLs on Windows)\n8. ✅ Data format updated to match Unix (comma-separated, 9 fields for malloc, 6 for memcpy)\n9. ✅ Windows memory polling thread added to `scalene_signal_manager.py`\n10. ✅ **64-bit pointer handling fixed in ctypes code**\n11. ✅ Memory profiling working - samples being collected and logged\n12. ✅ **Native library tracking (numpy, etc.) via Microsoft Detours**\n13. ✅ **Proper free tracking with manual size tracking hash map**\n\n### Test Results (2024-12-17)\n```\nalloc_samples: 203\nmax_footprint_mb: 152.71\nmax_footprint_python_fraction: 0.000288\nNative fraction: 99.97%\n```\n\nThe profiler now correctly tracks memory allocations from native libraries like numpy, with proper line attribution.\n\n### Known Limitation\n- Console output may fail with `UnicodeEncodeError` on Windows consoles using CP1252 encoding\n- This is not a profiler bug - it's a Windows console limitation\n- **Workaround**: Set `PYTHONIOENCODING=utf-8` environment variable or use `--html` or `--json` output\n\n## The Critical Bug Fix (2024-12-16)\n\n### Root Cause: ctypes 64-bit Pointer Truncation\n\nThe Access Violation crash (exit code 3221225477 = 0xC0000005) was caused by a **ctypes bug** where 64-bit pointers were being truncated to 32-bit values.\n\n**Problem**: By default, ctypes assumes Windows API functions return `c_int` (32-bit). On 64-bit Windows, `MapViewOfFile` returns a 64-bit pointer. Without explicit return type declaration, the high 32 bits were truncated, causing invalid memory addresses.\n\n**Example of bug**:\n```\nMapViewOfFile returns: 0x00007FFBF2650B90  (valid 64-bit pointer)\nctypes stored as:      0x00000000F2650B90  (truncated to 32-bit, invalid!)\n```\n\n### The Fix (scalene_mapfile.py)\n\nAdded proper return type declarations before calling Windows API functions:\n\n```python\nfrom ctypes import wintypes\n\nkernel32 = ctypes.windll.kernel32\n\n# IMPORTANT: Set proper return types for Windows API functions\n# Default ctypes return type is c_int (32-bit) which truncates 64-bit pointers\nkernel32.OpenFileMappingW.restype = wintypes.HANDLE\nkernel32.MapViewOfFile.restype = ctypes.c_void_p\nkernel32.UnmapViewOfFile.argtypes = [ctypes.c_void_p]\nkernel32.CloseHandle.argtypes = [wintypes.HANDLE]\n```\n\nThis fix is in `scalene/scalene_mapfile.py` in the `_init_windows()` method.\n\n## Native Library Memory Tracking (2024-12-17)\n\n### The Problem\n\nThe original Windows implementation only tracked Python allocations via `PyMem_SetAllocator`. Native library allocations (numpy arrays, pandas dataframes, etc.) went untracked because they bypass Python's allocator and call the C runtime `malloc`/`free` directly.\n\n**Before**: Only Python allocations tracked (0.03% of actual memory usage)\n**After**: All allocations tracked including native libraries (99.97% native, 0.03% Python)\n\n### Solution: Microsoft Detours\n\nWe use [Microsoft Detours](https://github.com/microsoft/Detours) to intercept `malloc`/`free`/`realloc`/`calloc` at the C runtime level. Detours works by rewriting the first few bytes of target functions with a jump to our hooks.\n\n**Why Detours over alternatives:**\n- **MinHook**: Does NOT support ARM64 (only x86/x64)\n- **IAT Hooking**: Complex, requires hooking each loaded module separately\n- **ETW (Event Tracing)**: Requires admin privileges, high overhead\n- **Detours**: Supports ARM64, x64, x86, ARM - official Microsoft library, MIT licensed\n\n### Implementation\n\n#### Files Modified/Added\n- `vendor/Detours/` - Microsoft Detours source (vendored)\n- `CMakeLists.txt` - Added Detours to Windows build with architecture-specific disassembler\n- `src/source/libscalene_windows.cpp` - Added native hooks using Detours\n\n#### CMakeLists.txt Changes\n```cmake\n# Microsoft Detours sources for native malloc/free hooking\nset(DETOURS_SOURCES\n    vendor/Detours/src/detours.cpp\n    vendor/Detours/src/modules.cpp\n    vendor/Detours/src/disasm.cpp\n    vendor/Detours/src/image.cpp\n    vendor/Detours/src/creatwth.cpp\n)\n\n# Add architecture-specific disassembler\nif(SCALENE_ARCH STREQUAL \"ARM64\")\n    list(APPEND DETOURS_SOURCES vendor/Detours/src/disolarm64.cpp)\nelseif(SCALENE_ARCH STREQUAL \"X64\")\n    list(APPEND DETOURS_SOURCES vendor/Detours/src/disolx64.cpp)\nelseif(SCALENE_ARCH STREQUAL \"X86\")\n    list(APPEND DETOURS_SOURCES vendor/Detours/src/disolx86.cpp)\nendif()\n\ntarget_compile_definitions(scalene PRIVATE DETOURS_INTERNAL)\n```\n\n#### Native Hook Implementation\n```cpp\n#include \"detours.h\"\n\n// Original function pointers (Detours fills these with trampolines)\nstatic void* (__cdecl *Real_malloc)(size_t) = malloc;\nstatic void (__cdecl *Real_free)(void*) = free;\nstatic void* (__cdecl *Real_realloc)(void*, size_t) = realloc;\nstatic void* (__cdecl *Real_calloc)(size_t, size_t) = calloc;\n\n// Recursion guard - CRITICAL for preventing infinite loops\nstatic bool g_in_native_hook = false;\n\n// Coordination with Python allocator hooks\nstatic bool g_in_python_allocator = false;\n\nstatic void* __cdecl Hooked_malloc(size_t size) {\n    // Check recursion guard FIRST - track_native_alloc may call malloc internally\n    if (g_in_native_hook || g_in_python_allocator) {\n        return Real_malloc(size);\n    }\n\n    g_in_native_hook = true;\n    void* ptr = Real_malloc(size);\n    if (ptr) {\n        track_native_alloc(ptr, size);\n        if (!p_scalene_done) {\n            TheHeapWrapper::register_malloc(size, ptr, false);  // false = native\n        }\n    }\n    g_in_native_hook = false;\n    return ptr;\n}\n\nbool install_native_hooks() {\n    DetourRestoreAfterWith();\n    DetourTransactionBegin();\n    DetourUpdateThread(GetCurrentThread());\n\n    DetourAttach(&(PVOID&)Real_malloc, Hooked_malloc);\n    DetourAttach(&(PVOID&)Real_free, Hooked_free);\n    DetourAttach(&(PVOID&)Real_realloc, Hooked_realloc);\n    DetourAttach(&(PVOID&)Real_calloc, Hooked_calloc);\n\n    return DetourTransactionCommit() == NO_ERROR;\n}\n```\n\n### Critical Bug Fix: Recursion Guard Order\n\n**Problem**: The native hooks use `std::unordered_map` for size tracking, which internally calls `malloc`. If the recursion guard was checked AFTER calling tracking functions, infinite recursion occurred:\n\n1. User code calls `malloc`\n2. `Hooked_malloc` is called\n3. `track_native_alloc(ptr, size)` is called (hash map insert)\n4. Hash map internally calls `malloc` for bucket allocation\n5. `Hooked_malloc` is called again (recursion!)\n6. Since `g_in_native_hook` wasn't set yet, we recurse infinitely → stack overflow\n\n**Fix**: Check and set `g_in_native_hook` at the VERY BEGINNING of each hook, BEFORE any operations that might allocate:\n\n```cpp\nstatic void* __cdecl Hooked_malloc(size_t size) {\n    // MUST check recursion guard FIRST\n    if (g_in_native_hook || g_in_python_allocator) {\n        return Real_malloc(size);  // Bypass hook\n    }\n\n    g_in_native_hook = true;  // Set BEFORE any allocating operations\n    // ... rest of hook logic ...\n    g_in_native_hook = false;\n    return ptr;\n}\n```\n\n### Free Size Tracking\n\n**Problem**: `_msize()` doesn't work reliably for custom allocators (numpy uses aligned allocations with `_aligned_malloc`).\n\n**Solution**: Manual size tracking with a hash map:\n\n```cpp\nstatic std::unordered_map<void*, size_t> g_native_alloc_sizes;\nstatic CRITICAL_SECTION g_native_alloc_sizes_lock;\n\nstatic void track_native_alloc(void* ptr, size_t size) {\n    EnterCriticalSection(&g_native_alloc_sizes_lock);\n    g_native_alloc_sizes[ptr] = size;\n    LeaveCriticalSection(&g_native_alloc_sizes_lock);\n}\n\nstatic size_t untrack_native_alloc(void* ptr) {\n    EnterCriticalSection(&g_native_alloc_sizes_lock);\n    auto it = g_native_alloc_sizes.find(ptr);\n    size_t size = (it != g_native_alloc_sizes.end()) ? it->second : 0;\n    if (it != g_native_alloc_sizes.end()) g_native_alloc_sizes.erase(it);\n    LeaveCriticalSection(&g_native_alloc_sizes_lock);\n    return size;\n}\n```\n\n### Coordination Between Python and Native Hooks\n\nTo prevent double-counting allocations that go through both Python's allocator AND the native malloc:\n\n1. Python allocator hooks set `g_in_python_allocator = true` when active\n2. Native malloc hooks check this flag and skip if Python allocator is handling it\n3. Each allocation is counted exactly once\n\n```cpp\n// In Python allocator hook:\nstatic void* scalene_malloc(void* ctx, size_t len) {\n    g_in_python_allocator = true;  // Prevent native hooks\n    void* ptr = g_original_mem_allocator.malloc(...);\n    // ... tracking code ...\n    g_in_python_allocator = false;\n    return ptr;\n}\n```\n\n## Fixes Applied\n\n### 1. **ctypes 64-bit Pointer Fix** (Critical)\n- Added `restype` declarations for all Windows API functions that return handles or pointers\n- `MapViewOfFile.restype = ctypes.c_void_p` - returns memory address\n- `OpenFileMappingW.restype = wintypes.HANDLE` - returns handle\n- Also set `argtypes` for functions taking pointers\n\n### 2. Data Format Fix\nChanged malloc/free output from tab-separated 4-field format to comma-separated 9-field format matching Unix:\n```\nM,alloc_time,count,python_fraction,pid,pointer,filename,lineno,bytei\\n\\n\n```\n\n### 3. Removed `thread_local` Variables\nWindows DLLs loaded with `LoadLibrary`/ctypes don't properly support `thread_local` variables. Changed all to static variables:\n- `mallocSampler` and `memcpySampler`\n- `g_pythonCount`, `g_cCount`\n- `g_lastMallocTrigger`, `g_freedLastMallocTrigger`\n- `g_memcpyOps`\n- `inMalloc` (for recursion guard)\n\n### 4. Added Windows Memory Polling\nSince Unix signals (SIGXCPU/SIGXFSZ) don't exist on Windows, added a polling thread in `scalene_signal_manager.py`:\n- `_windows_memory_poll_loop()` - polls every 10ms\n- Triggers `_alloc_sigqueue_processor` and `_memcpy_sigqueue_processor`\n- Started when memory profiling is enabled on Windows\n\n### 5. Safety Checks in Allocator Hooks\nAdded null checks for original allocator function pointers to prevent crashes if allocation hooks are called before initialization.\n\n## Test Results\n\nRunning the profiler now shows memory profiling data being collected:\n```\nmalloc_calls=113807912, samples=595, logged=595\n```\n\nThe profiler correctly tracks:\n- Python vs native time\n- Memory allocations with line attribution\n- Memory timeline/growth rate\n\n## Build Instructions\n\n```bash\n# Configure with CMake (for ARM64 native)\ncmake -B build -A ARM64 -DPython3_ROOT_DIR=\"C:\\Users\\emery\\AppData\\Local\\Programs\\Python\\Python311-arm64\"\n\n# Build the DLL\nMSBuild.exe build/scalene.sln -p:Configuration=Release -p:Platform=ARM64 -t:scalene\n\n# DLL is automatically placed in scalene\\ directory\n\n# Run profiler\npython -m scalene --cli --cpu --memory test\\testme.py\n```\n\n## Key Differences from Unix Implementation\n\n| Aspect | Unix | Windows |\n|--------|------|---------|\n| Native malloc hooking | LD_PRELOAD | **Microsoft Detours** (inline function hooking) |\n| Python allocator | PyMem_SetAllocator | PyMem_SetAllocator (same) |\n| Signals | SIGXCPU/SIGXFSZ | Polling thread |\n| Shared Memory | /tmp files + mmap | Named shared memory |\n| Symbol Lookup | dlsym(RTLD_DEFAULT) | GetProcAddress + accessor functions |\n| Size Tracking | malloc_usable_size | Manual hash map (\\_msize unreliable) |\n| Thread-local | `thread_local` | Static variables (GIL protects) |\n| Pointer handling | Native 64-bit | **Requires explicit ctypes declarations** |\n| Architecture support | All | ARM64, x64, x86 (via Detours) |\n\n## Learnings\n\n### 1. ctypes Default Return Types Are Dangerous on 64-bit Windows\n- **Always** set `restype` for any Windows API function that returns a handle or pointer\n- Default `c_int` (32-bit) silently truncates 64-bit values\n- This causes delayed crashes that are hard to debug (crash happens when truncated pointer is used, not when it's returned)\n- Use `ctypes.c_void_p` for memory addresses, `wintypes.HANDLE` for handles\n\n### 2. Debugging Methodology That Worked\nThe crash was isolated using **systematic component elimination**:\n1. First confirmed DLL was the cause by renaming it (profiler ran without crash)\n2. Tested with allocator hooks disabled - still crashed (ruled out hooks as cause)\n3. Tested with only shared memory initialization - still crashed (narrowed to shared memory)\n4. Examined shared memory code and found the ctypes issue\n\n### 3. Windows DLL Loading Constraints\n- `thread_local` variables don't work reliably in DLLs loaded via `LoadLibrary`/ctypes\n- Static variables work fine since Python's GIL serializes access\n- DLL entry point (`DllMain`) has severe restrictions - avoid complex initialization there\n\n### 4. Build System Considerations\n- CMake generates Visual Studio solutions that work well for cross-platform builds\n- ARM64 native builds require explicit `-A ARM64` and matching Python version\n- MSBuild can be invoked directly for rebuilds without re-running CMake\n\n### 5. Windows vs Unix IPC\n- Named shared memory on Windows uses `Local\\` prefix for per-session namespace\n- Windows Events can replace Unix signals but polling is simpler for this use case\n- Mutexes on Windows are more heavyweight than Unix futexes\n\n### 6. Recursion Guards in malloc Hooks Must Come FIRST\n- **Critical**: Any code in a malloc hook that uses STL containers (std::unordered_map, std::vector, etc.) may internally call malloc\n- The recursion guard (`g_in_native_hook`) MUST be checked and set BEFORE any other operations\n- Failing to do this causes infinite recursion → stack overflow → exit code 127 or crash\n- Pattern: `if (guard) return original(); guard = true; ... ; guard = false; return result;`\n\n### 7. MinHook Does NOT Support ARM64\n- MinHook is a popular inline hooking library but only supports x86 and x64\n- Microsoft Detours supports ARM64, ARM, x86, x64, and IA64\n- Detours is MIT licensed (open source since 2018) and very well tested\n- Use architecture-specific disassemblers: `disolarm64.cpp` for ARM64, `disolx64.cpp` for x64\n\n### 8. _msize() Is Unreliable for Custom Allocators\n- `_msize()` only works for standard CRT heap allocations\n- numpy and other libraries use aligned allocations (`_aligned_malloc`) which `_msize` doesn't handle\n- Solution: Manual size tracking with a hash map, storing size at allocation time\n- This adds ~10% overhead but is necessary for accurate free tracking\n\n### 9. Coordination Between Multiple Hook Layers\n- When hooking at both Python allocator level AND native malloc level, coordination is essential\n- Use a flag (`g_in_python_allocator`) to prevent native hooks from double-counting Python allocations\n- Pattern: Set flag before calling original allocator, clear after\n- This ensures each allocation is tracked exactly once\n\n## Next Steps\n\n### High Priority\n1. **x64 Build and Testing**: Current implementation tested only on ARM64. Need to verify x64 builds work correctly with Detours.\n\n2. **Unicode Console Output**: The `UnicodeEncodeError` for sparkline characters could be handled more gracefully:\n   - Detect console encoding and fall back to ASCII sparklines\n   - Or auto-set `PYTHONIOENCODING=utf-8` when running on Windows\n\n3. **Performance Optimization**: The polling thread polls every 10ms. Consider:\n   - Using Windows Events for more efficient signaling\n   - Adaptive polling rate based on allocation frequency\n\n### Medium Priority\n4. **Multi-Process Support**: Test and fix any issues with profiling child processes on Windows (the Unix `redirect_python` mechanism may need Windows adaptation).\n\n5. **GPU Profiling on Windows**: Verify NVIDIA GPU profiling works on Windows (pynvml should work but needs testing).\n\n6. **CI/CD Integration**: Add Windows builds to GitHub Actions workflow:\n   - Build DLL for both x64 and ARM64\n   - Run tests on Windows\n   - Include DLL in wheel packages\n\n### Low Priority\n7. **Windows Event-Based Signaling**: Replace polling with proper Windows Events for lower latency and CPU usage:\n   - DLL already creates events (`ScaleneMallocEvent`, etc.)\n   - Python side would use `WaitForMultipleObjects` instead of polling\n\n8. **Memory Leak Detection**: The `--memory-leak-detector` feature may need Windows-specific testing.\n\n9. **Web UI Testing**: Verify the web-based GUI works correctly on Windows (browser launching, port binding).\n\n### Completed\n- ~~**Native Library Memory Tracking**: Implement malloc/free hooking for numpy, pandas, etc.~~ ✅ Done (2024-12-17) - Using Microsoft Detours\n\n## Debugging Tips for Future Issues\n\n### Useful Debug Techniques\n```python\n# Add to scalene_mapfile.py to debug shared memory issues\nprint(f\"Handle value: {handle:#x}\", file=sys.stderr)\nprint(f\"View address: {view:#x}\", file=sys.stderr)\n\n# Check if pointer looks valid (should have high bits set on 64-bit)\nif view < 0x100000000:\n    print(\"WARNING: Pointer may be truncated!\", file=sys.stderr)\n```\n\n### Building with Debug Output\n```cpp\n// In libscalene_windows.cpp, temporarily add:\nfprintf(stderr, \"DEBUG: ptr=%p, size=%zu\\n\", ptr, size);\n```\n\nThen rebuild with:\n```bash\nMSBuild.exe build/scalene.sln -p:Configuration=Release -p:Platform=ARM64 -t:scalene\n```\n\n### Common Windows Error Codes\n- `0xC0000005` (3221225477) - Access Violation (invalid memory access)\n- `0xC0000008` - Invalid Handle\n- `0xC000001D` - Illegal Instruction (wrong architecture)\n- Error 193 - \"Not a valid Win32 application\" (architecture mismatch)\n\n## References\n\n- Python Memory Allocator API: https://docs.python.org/3/c-api/memory.html\n- Windows Named Shared Memory: https://docs.microsoft.com/en-us/windows/win32/memory/creating-named-shared-memory\n- CMake Windows DLL: https://cmake.org/cmake/help/latest/prop_tgt/WINDOWS_EXPORT_ALL_SYMBOLS.html\n- Windows thread_local issues: https://devblogs.microsoft.com/cppblog/c11-thread-local-storage-and-dll-load-failure/\n- **ctypes 64-bit pointers**: https://docs.python.org/3/library/ctypes.html#return-types\n- **Microsoft Detours**: https://github.com/microsoft/Detours - Official Microsoft library for inline function hooking (MIT license)\n- Detours Wiki: https://github.com/microsoft/Detours/wiki - API documentation and examples\n","isInternal":false,"tokens":4938,"sizeBytes":20421}],"systemPromptSnippet":"<yakaai_skills repo=\"plasma-umass/scalene\">\n<!-- File: CLAUDE.md (Tokens: ~6267 | Category: claude-rule) -->\n# Scalene Development Guide\n\n## Project Overview\n\nScalene is a high-performance CPU, GPU, and memory profiler for Python with AI-powered optimization proposals. It runs significantly faster than other Python profilers while providing detailed performance information. See the paper `docs/osdi23-berger.pdf` for technical details on Scalene's design.\n\n**Key features:**\n- CPU, GPU (NVIDIA/Apple), and memory profiling\n- AI-powered optimization suggestions (OpenAI, Anthropic, Azure, Amazon Bedrock, Gemini, Ollama)\n- Web-based GUI and CLI interfaces\n- Jupyter notebook support via magic commands (`%scrun`, `%%scalene`)\n- Line-by-line profiling with low overhead\n- Separates Python time from native/C time\n\n**Platform support:** Linux, macOS, WSL 2 (full support); Windows (partial support)\n\n## Build & Test Commands\n\n```bash\n# Install in development mode\npip install -e .\n\n# Run all tests\npython3 -m pytest tests/\n\n# Run tests for a specific Python version\npython3.X -m pytest tests/\n\n# Run linters\nmypy scalene\nruff check scalene\n\n# Run a single test file\npython3 -m pytest tests/test_coverup_83.py -v\n```\n\n## Project Structure\n\n### Core Profiler Components (`scalene/`)\n\n- **`scalene_profiler.py`** - Main profiler class (`Scalene`). Entry point for profiling. Uses signal-based sampling for CPU profiling. Coordinates all profiling subsystems.\n- **`scalene_statistics.py`** - `ScaleneStatistics` class. Collects and aggregates profiling data. Key types: `ProfilingSample`, `MemcpyProfilingSample`. Uses `RunningStats` for statistical aggregation.\n- **`scalene_output.py`** - Profile output formatting for CLI/HTML\n- **`scalene_json.py`** - `ScaleneJSON` class for JSON output format\n- **`scalene_analysis.py`** - Profile analysis logic\n\n### Entry Points\n\n- **`__main__.py`** - Entry point for `python -m scalene`\n- **`profile.py`** - Entry point for `--on`/`--off` control of background profiling\n\n### Configuration & Arguments\n\n- **`scalene_config.py`** - Version info (`scalene_version`, `scalene_date`) and constants:\n  - `SCALENE_PORT = 11235` - Default port for web UI\n  - `NEWLINE_TRIGGER_LENGTH` - Must match `src/include/sampleheap.hpp`\n- **`scalene_arguments.py`** - `ScaleneArguments` class (extends `argparse.Namespace`) with all profiler options and their defaults defined in `ScaleneArgumentsDict`\n- **`scalene_parseargs.py`** - `ScaleneParseArgs.parse_args()` builds the argument parser. `RichArgParser` provides colored help output (uses Rich on Python < 3.14, native argparse colors on 3.14+)\n\n### Signal Handling\n\n- **`scalene_signals.py`** - Signal definitions for CPU sampling\n- **`scalene_signal_manager.py`** - Manages signal handlers\n- **`scalene_sigqueue.py`** - Signal queue management\n- **`scalene_client_timer.py`** - Timer for periodic profiling\n\n### GPU Support\n\n- **`scalene_nvidia_gpu.py`** - NVIDIA GPU profiling via `pynvml`\n- **`scalene_apple_gpu.py`** - Apple GPU profiling (Metal)\n- **`scalene_accelerator.py`** - Generic accelerator interface\n- **`scalene_neuron.py`** - AWS Neuron support\n\n### Memory Profiling\n\n- **`scalene_memory_profiler.py`** - Memory profiling logic\n- **`scalene_leak_analysis.py`** - Memory leak detection (experimental, `--memory-leak-detector`)\n- **`scalene_mapfile.py`** - `ScaleneMapFile` for memory-mapped communication with native extension\n- **`scalene_preload.py`** - Sets up `LD_PRELOAD`/`DYLD_INSERT_LIBRARIES` for native memory tracking\n\n### Jupyter Integration\n\n- **`scalene_magics.py`** - Jupyter magic commands (`%scrun` for line mode, `%%scalene` for cell mode)\n- **`scalene_jupyter.py`** - Jupyter notebook support utilities\n\n### Replacement Modules (`replacement_*.py`)\n\nThese modules monkey-patch standard library functions to capture profiling data during blocking operations:\n- **`replacement_fork.py`** - Tracks `os.fork()`\n- **`replacement_exit.py`** - Tracks `sys.exit()`\n- **`replacement_lock.py`**, **`replacement_mp_lock.py`**, **`replacement_sem_lock.py`** - Lock acquisition timing\n- **`replacement_thread_join.py`**, **`replacement_pjoin.py`** - Thread/process join timing\n- **`replacement_signal_fns.py`** - Signal function replacements\n- **`replacement_poll_selector.py`** - I/O polling timing\n- **`replacement_get_context.py`** - Multiprocessing context\n\n### Utilities\n\n- **`runningstats.py`** - `RunningStats` class for online statistical calculations (mean, variance)\n- **`scalene_funcutils.py`** - Function utilities\n- **`scalene_utility.py`** - General utilities\n- **`sparkline.py`** - Sparkline generation for memory visualization\n- **`syntaxline.py`** - Syntax-highlighted source code lines\n- **`adaptive.py`** - Adaptive sampling logic\n- **`time_info.py`** - Time measurement utilities\n- **`sorted_reservoir.py`** - Reservoir sampling for bounded-size sample collection\n\n### GUI (`scalene/scalene-gui/`)\n\nWeb-based GUI built with TypeScript, bundled with esbuild.\n\n**Core Files:**\n- **`index.html.template`** - Jinja2 template for main GUI page (rendered by `scalene_utility.py`)\n- **`scalene-gui.ts`** - Main TypeScript entry point, UI event handlers, initialization\n- **`scalene-gui-bundle.js`** - Bundled JavaScript output (generated, do not edit directly)\n\n**AI Provider Modules:**\n- **`openai.ts`** - OpenAI API integration (`sendPromptToOpenAI`, `fetchOpenAIModels`)\n- **`anthropic.ts`** - Anthropic Claude API integration\n- **`gemini.ts`** - Google Gemini API integration (`sendPromptToGemini`, `fetchGeminiModels`)\n- **`optimizations.ts`** - Provider dispatch logic, prompt generation\n- **`persistence.ts`** - localStorage persistence with environment variable fallbacks\n\n**Support Files:**\n- **`launchbrowser.py`** - Opens browser to GUI (default port 11235)\n- **`find_browser.py`** - Cross-platform browser detection\n\n**Vendored Assets (for offline support):**\n- **`jquery-3.6.0.slim.min.js`** - jQuery (vendored locally, not loaded from CDN)\n- **`bootstrap.min.css`** - Bootstrap 5.1.3 CSS\n- **`bootstrap.bundle.min.js`** - Bootstrap 5.1.3 JS with Popper\n- **`prism.css`** - Syntax highlighting styles\n- **`favicon.ico`** - Scalene favicon\n- **`scalene-image.png`** - Scalene logo\n\nThese assets are copied, with the page, into a fresh private (0700) temp directory that `launchbrowser.py` serves and deletes on exit, enabling the GUI to work in air-gapped/offline environments. Never serve the shared system temp dir itself.\n\n**Building the GUI:**\n```bash\nnpm --prefix scalene/scalene-gui run build\n```\nThe `build` script in `scalene/scalene-gui/package.json` invokes esbuild\nwith `--minify --sourcemap --target=es2020`. The minified bundle is what\ngets checked in (≈1.1 MB vs ≈2.5 MB unminified). A `build:dev` variant\n(no minification) is available for debugging.\n\n### Native Extensions (`src/`)\n\nC++ code for low-overhead memory allocation tracking:\n\n**Headers (`src/include/`):**\n- **`sampleheap.hpp`** - Sampling heap allocator. Key constant `NEWLINE` must match Python config.\n- **`memcpysampler.hpp`** - Intercepts `memcpy` to track copy volume\n- **`pywhere.hpp`** - Tracks Python file/line info for allocations\n- **`samplefile.hpp`** - File-based communication with Python\n- **`sampler.hpp`**, **`poissonsampler.hpp`**, **`thresholdsampler.hpp`** - Sampling strategies\n- **`scaleneheader.hpp`** - Common header definitions\n\n**Sources (`src/source/`):**\n- **`libscalene.cpp`** - Main native library (loaded via `LD_PRELOAD`)\n- **`pywhere.cpp`** - Python location tracking implementation\n- **`get_line_atomic.cpp`** - Atomic line number access\n- **`traceconfig.cpp`** - Trace configuration\n\n### Vendor Libraries (`vendor/`)\n\n- **`Heap-Layers/`** - Memory allocator infrastructure (by Emery Berger)\n- **`printf/`** - Async-signal-safe printf implementation\n\n## Key Patterns\n\n### Python Version Compatibility\n\nThe codebase supports Python 3.8-3.14. Version-specific code uses:\n\n```python\nif sys.version_info >= (3, 14):\n    # Python 3.14+ specific code\nelse:\n    # Older Python versions\n```\n\n**Type Annotation Compatibility (Python 3.8/3.9):**\n- **Do NOT use `X | Y` union syntax** in runtime-evaluated annotations (PEP 604 requires Python 3.10+). Use `Optional[X]` or `Union[X, Y]` from `typing` instead.\n- **Do NOT use `list[X]`, `dict[K, V]`, `tuple[X, ...]`** in runtime-evaluated annotations (PEP 585 lowercase generics require Python 3.9+). Use `List`, `Dict`, `Tuple` from `typing` for 3.8 support.\n- Adding `from __future__ import annotations` makes all annotations strings (not evaluated at runtime), which allows modern syntax on older Python. However, this can break code that inspects annotations at runtime (e.g., dataclasses, pydantic).\n- The safest approach for this codebase: use `typing.Optional`, `typing.Union`, `typing.List`, `typing.Tuple`, `typing.Dict` in all annotation positions that are evaluated at runtime (function signatures, variable annotations outside `if TYPE_CHECKING` blocks).\n\n**Python 3.13 Changes (`dis` module):**\n- `dis.Instruction.starts_line` changed from `int | None` (line number) to `bool`\n- New `dis.Instruction.line_number` attribute (`int | None`) added for the actual line number\n- On Python < 3.13, `starts_line` is only set on the **first** instruction of each source line; use a line-tracking loop to propagate line numbers to subsequent instructions\n\n**Bytecode/Opcode Compatibility (`dis` module):**\n- **Never match specific opcode names** (e.g., `JUMP_BACKWARD`, `JUMP_ABSOLUTE`, `POP_JUMP_IF_TRUE`). Opcode names change across Python versions — for example, Python 3.10 while loops use `POP_JUMP_IF_TRUE` for backward jumps, Python 3.11+ uses `JUMP_BACKWARD`, and `JUMP_ABSOLUTE` was removed in 3.12.\n- **Always use abstract `dis` module categories** when possible: `dis.hasjabs` (absolute jump opcodes), `dis.hasjrel` (relative jump opcodes), `dis.hasconst`, `dis.hasname`, etc. These are maintained by CPython and work across all versions.\n- For call detection, matching `opname.startswith(\"CALL\")` is acceptable since that prefix has been stable, but prefer opcode integer sets over name strings for hot paths.\n- When checking jump direction (forward vs backward), use `instr.argval` (which `dis` resolves to an absolute offset) and compare against `instr.offset`, rather than relying on opcode names to imply direction.\n\n**Python 3.14 Changes:**\n- `argparse` now has built-in colored help output (`color=True` parameter)\n- `RichArgParser` uses Rich for colors on Python < 3.14, native argparse colors on 3.14+\n\n### Argument Parsing (`scalene_parseargs.py`)\n\n```python\nclass RichArgParser(argparse.ArgumentParser):\n    \"\"\"ArgumentParser that uses Rich for colored output on Python < 3.14.\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        if sys.version_info < (3, 14):\n            from rich.console import Console\n            self._console = Console()\n        else:\n            self._console = None\n        super().__init__(*args, **kwargs)\n```\n\nThe `_colorize_help_for_rich()` function applies Python 3.14-style colors using Rich markup:\n- `usage:` and `options:` → bold blue\n- Program name → bold magenta\n- Long options (`--foo`) → bold cyan\n- Short options (`-h`) → bold green\n- Metavars (`FOO`) → bold yellow\n\n### GUI Patterns\n\n**Preventing Browser Password Prompts:**\nUse `autocomplete=\"one-time-code\"` on password/API key inputs to prevent browsers from offering to save them:\n```html\n<input type=\"password\" id=\"api-key\" autocomplete=\"one-time-code\">\n```\n\n**Show/Hide Password Toggle:**\n```typescript\nfunction togglePassword(inputId: string, button: HTMLButtonElement): void {\n  const input = document.getElementById(inputId) as HTMLInputElement;\n  if (input.type === \"password\") {\n    input.type = \"text\";\n    button.textContent = \"Hide\";\n  } else {\n    input.type = \"password\";\n    button.textContent = \"Show\";\n  }\n}\n```\n\n**Provider Field Visibility:**\nUse CSS classes to show/hide provider-specific fields:\n```typescript\nfunction toggleServiceFields(): void {\n  const service = (document.getElementById(\"service\") as HTMLSelectElement).value;\n  // Hide all provider sections\n  document.querySelectorAll(\".provider-section\").forEach((el) => {\n    (el as HTMLElement).style.display = \"none\";\n  });\n  // Show selected provider section\n  const section = document.querySelector(`.${service}-fields`);\n  if (section) (section as HTMLElement).style.display = \"block\";\n}\n```\n\n**Persistent Form Elements:**\nAdd class `persistent` to inputs that should be saved/restored from localStorage:\n```html\n<input type=\"text\" id=\"api-key\" class=\"persistent\">\n```\nThe `persistence.ts` module handles save/restore automatically.\n\n**Standalone HTML Generation:**\nThe `generate_html()` function in `scalene_utility.py` supports a `standalone` parameter:\n- When `standalone=False` (default): Assets are referenced as local files (e.g., `<script src=\"jquery-3.6.0.slim.min.js\">`)\n- When `standalone=True`: All assets are embedded inline (JS/CSS as text, images as base64)\n\nThe Jinja2 template uses conditionals:\n```html\n{% if standalone %}\n<script>{{ jquery_js }}</script>\n<style>{{ bootstrap_css }}</style>\n{% else %}\n<script src=\"jquery-3.6.0.slim.min.js\"></script>\n<link href=\"bootstrap.min.css\" rel=\"stylesheet\">\n{% endif %}\n```\n\n### Module Imports\n\nWhen importing submodules, be explicit:\n\n```python\n# Correct - mypy can verify this\nimport importlib.util\nimportlib.util.find_spec(mod_name)\n\n# Wrong - mypy error: Module has no attribute \"util\"\nimport importlib\nimportlib.util.find_spec(mod_name)\n```\n\n## Testing\n\n### Test Files (`tests/`)\n\n- **`test_coverup_*.py`** - Auto-generated coverage tests\n- **`test_runningstats.py`** - Statistics tests (requires `hypothesis`)\n- **`test_scalene_json.py`** - JSON output tests (requires `hypothesis`)\n- **`test_nested_package_relative_import.py`** - Import handling tests\n\n### Test Dependencies\n\n```bash\npip install pytest pytest-asyncio hypothesis\n```\n\n### Running Tests Across Python Versions\n\n```bash\nfor v in 3.9 3.10 3.11 3.12 3.13 3.14; do\n    python$v -m pytest tests/test_coverup_83.py -v\ndone\n```\n\n### Flaky Smoketests\n\nThe smoketests in `test/` can be flaky due to timing/sampling issues inherent to profiling:\n\n- **\"No non-zero lines in X\"** - The profiler didn't collect enough samples. This happens when the test runs too quickly or signal delivery timing varies.\n- **\"Expected function 'X' not returned\"** - A function wasn't sampled. Common with short-running functions.\n\nThese failures are usually timing-related and pass on re-run. They're more common on CI due to variable machine load.\n\n### Port Binding in Tests\n\nWhen testing port availability, never use hardcoded ports - they may already be in use on CI runners:\n\n```python\n# Bad - port 49200 might be in use\nport = 49200\nsock.bind((\"\", port))\n\n# Good - find an available port first\nport = find_available_port(49200, 49300)\nif port is None:\n    return  # Skip test if no ports available\nsock.bind((\"\", port))\n```\n\n## CI/CD (`.github/workflows/`)\n\n- **`run-linters.yml`** - Runs mypy and ruff on Python 3.9-3.14\n- **`tests.yml`** - Runs pytest on Python 3.9-3.14\n- **`build-and-upload.yml`** - Build and publish to PyPI\n\n## Common Tasks\n\n### Adding a New CLI Option\n\n1. Add default value in `scalene_arguments.py`:\n   ```python\n   class ScaleneArgumentsDict(TypedDict, total=False):\n       my_option: bool\n   ```\n\n2. Add argument in `scalene_parseargs.py`:\n   ```python\n   parser.add_argument(\n       \"--my-option\",\n       dest=\"my_option\",\n       action=\"store_true\",\n       default=defaults.my_option,\n       help=\"Description of option\",\n   )\n   ```\n\n### Adding a New AI Provider\n\n1. **Create provider module** (`scalene/scalene-gui/newprovider.ts`):\n   ```typescript\n   export async function sendPromptToNewProvider(\n     prompt: string,\n     apiKey: string\n   ): Promise<string> {\n     // API call implementation\n   }\n\n   export async function fetchNewProviderModels(apiKey: string): Promise<string[]> {\n     // Optional: fetch available models from API\n   }\n   ```\n\n2. **Update `optimizations.ts`**:\n   - Import the new module\n   - Add case in `sendPromptToService()` switch statement\n\n3. **Update `index.html.template`**:\n   - Add option to `#service` select dropdown\n   - Add provider section with API key input, model selector, etc.\n   - Add CSS for `.newprovider-fields` visibility\n\n4. **Update `scalene-gui.ts`**:\n   - Add provider to `toggleServiceFields()` function\n   - Add refresh handler if dynamic model fetching is supported\n   - Update `getDefaultProvider()` if env var support is needed\n\n5. **Update `persistence.ts`** (for env var support):\n   - Add mapping in `envKeyMap` for new fields\n\n6. **Update `launchbrowser.py`** (for env var support):\n   - Add the field and its environment variable(s) to `ENV_API_KEY_VARIABLES`\n   - Add the field to `EnvApiKeys` / `envApiKeyFields` in `persistence.ts`\n\n7. **Rebuild the bundle**:\n   ```bash\n   npm --prefix scalene/scalene-gui run build\n   ```\n\n### Environment Variable API Keys\n\nWith `scalene view --api-keys-from-env` (opt-in), the GUI prepopulates API\nkeys from environment variables:\n\n| Element ID | Environment Variable | Provider |\n|------------|---------------------|----------|\n| `api-key` | `OPENAI_API_KEY` | OpenAI |\n| `anthropic-api-key` | `ANTHROPIC_API_KEY` | Anthropic |\n| `gemini-api-key` | `GEMINI_API_KEY` or `GOOGLE_API_KEY` | Gemini |\n| `azure-api-key` | `AZURE_OPENAI_API_KEY` | Azure OpenAI |\n| `azure-api-url` | `AZURE_OPENAI_ENDPOINT` | Azure OpenAI |\n| `aws-access-key` | `AWS_ACCESS_KEY_ID` | Amazon Bedrock |\n| `aws-secret-key` | `AWS_SECRET_ACCESS_KEY` | Amazon Bedrock |\n| `aws-region` | `AWS_DEFAULT_REGION` or `AWS_REGION` | Amazon Bedrock |\n\n**Flow:**\n1. `scalene view --api-keys-from-env` passes the flag to `launchbrowser.py`,\n   whose local server reads the env vars and serves them from memory at\n   `/env-api-keys.json` (an empty object without the flag)\n2. `loadEnvApiKeys()` in `persistence.ts` fetches that on page load (only when\n   the page is served over HTTP), before persistent fields are restored\n3. `persistence.ts` uses env vars as fallbacks when localStorage is empty;\n   `prefillNonPersistentEnvFields()` fills the non-persistent OpenAI key\n\n**Never embed credentials in the generated HTML.** `scalene-profile.html` is\nwritten to the working directory, copied to the system temp dir, and\n`--standalone` files exist to be shared (#1095;\n`tests/test_html_credential_security.py` enforces this). The endpoint answers\nonly requests whose `Host` is `localhost:<port>`/`127.0.0.1:<port>` (blocks DNS\nrebinding) and whose `Sec-Fetch-Site`, if sent, is same-origin; it sends no\nCORS headers. The flag is rejected with `--cli`, `--html`, and `--standalone`.\n\n### Updating Version\n\nEdit `scalene/scalene_config.py`:\n```python\nscalene_version = \"X.Y.Z\"\nscalene_date = \"YYYY.MM.DD\"\n```\n\n## Dependencies\n\nKey runtime dependencies:\n- `rich` - Terminal formatting and colors\n- `cloudpickle` - Serialization\n- `pynvml` - NVIDIA GPU support (optional)\n\nSee `requirements.txt` for full list.\n\n## CLI Structure\n\nScalene uses a verb-based CLI with two main subcommands:\n\n```bash\n# Profile a program (saves to scalene-profile.json by default)\nscalene run [options] yourprogram.py\n\n# View an existing profile\nscalene view [options] [profile.json]\n```\n\n### Run Subcommand Options\n\n```bash\nscalene run prog.py                      # profile, save to scalene-profile.json\nscalene run -o my.json prog.py           # save to custom file\nscalene run --cpu-only prog.py           # profile CPU only (faster)\nscalene run -c config.yaml prog.py       # load options from config file\nscalene run prog.py --- --arg            # pass args to program\n```\n\n### View Subcommand Options\n\n```bash\nscalene view                             # open in browser\nscalene view --cli                       # view in terminal\nscalene view --html                      # save to scalene-profile.html\nscalene view --standalone                # save as self-contained HTML (all assets embedded)\nscalene view myprofile.json              # open specific profile\n```\n\n### Profile Completion Message\n\nAfter profiling completes, Scalene prints instructions for viewing the profile:\n```\nScalene: profile saved to scalene-profile.json\n  To view in browser:  scalene view\n  To view in terminal: scalene view --cli\n```\n\nThe filename is only included in the command if a non-default output file was used.\n\n### YAML Configuration\n\nCreate a `scalene.yaml` file with options:\n\n```yaml\noutfile: my-profile.json\ncpu-only: true\nprofile-only: \"mypackage,utils\"\ncpu-percent-threshold: 5\n```\n\nLoad with: `scalene run -c scalene.yaml prog.py`\n\n### Advanced Options\n\nUse `scalene run --help-advanced` to see all options including:\n- `--profile-all` - profile all code, not just the target program\n- `--profile-only PATH` - only profile files containing these strings\n- `--profile-exclude PATH` - exclude files containing these strings\n- `--profile-system-libraries` - profile Python stdlib and installed packages (skipped by default)\n- `--gpu` - profile GPU time and memory\n- `--memory` - profile memory usage\n- `--stacks` - collect stack traces\n- `--profile-interval N` - output profiles every N seconds\n\n### Smoke Tests\n\nSmoke tests in `test/` use the new CLI syntax:\n\n```python\n# test/smoketest.py\ncmd = [sys.executable, \"-m\", \"scalene\", \"run\", \"-o\", str(outfile), *rest, fname]\n```\n\n### GitHub Workflows\n\nWorkflows in `.github/workflows/` use the new CLI:\n\n```yaml\n# Profile with interval, then view\n- run: python -m scalene run --profile-interval=2 test/testme.py && python -m scalene view --cli\n\n# Profile with module invocation\n- run: python -m scalene run --- -m import_stress_test && python -m scalene view --cli\n```\n\n## Signal Handling\n\nScalene uses several Unix signals for profiling. The signal assignments are in `scalene_signals.py`:\n\n| Signal | Purpose | Platform |\n|--------|---------|----------|\n| `SIGVTALRM` | CPU profiling timer (default) | Unix |\n| `SIGALRM` | CPU profiling timer (real time mode) | Unix |\n| `SIGILL` | Start profiling (`--on`) | Unix |\n| `SIGBUS` | Stop profiling (`--off`) | Unix |\n| `SIGPROF` | memcpy tracking | Unix |\n| `SIGXCPU` | malloc tracking | Unix |\n| `SIGXFSZ` | free tracking | Unix |\n\n### Signal Conflicts with Libraries\n\nLibraries like PyTorch Lightning may also use these signals. The `replacement_signal_fns.py` module handles conflicts:\n\n**On Linux:** Uses real-time signals (`SIGRTMIN+1` to `SIGRTMIN+5`) for redirection. When user code sets a handler for a Scalene signal, their handler is redirected to a real-time signal. Calls to `raise_signal()` and `kill()` are also redirected transparently.\n\n**On macOS/other platforms:** Uses handler chaining. Both Scalene's handler and the user's handler are called when the signal fires.\n\n```python\n# Platform-specific signal handling\n_use_rt_signals = sys.platform == \"linux\" and hasattr(signal, \"SIGRTMIN\")\n\nif _use_rt_signals:\n    # Linux: redirect to real-time signals\n    rt_base = signal.SIGRTMIN + 1\n    _signal_redirects[signal.SIGILL] = rt_base\nelse:\n    # macOS: chain handlers\n    def chained_handler(sig, frame):\n        scalene_handler(sig, frame)\n        user_handler(sig, frame)\n```\n\n### Frame Line Number Can Be None (Python 3.11+)\n\nIn Python 3.11+, `frame.f_lineno` can be `None` in edge cases (e.g., during multiprocessing cleanup). Always use a fallback:\n\n```python\nlineno = frame.f_lineno if frame.f_lineno is not None else frame.f_code.co_firstlineno\n```\n\n## Native Extension Build Issues\n\n### C++ Standard Library Conflicts with vendor/printf\n\nThe `vendor/printf/printf.h` header defines macros that conflict with C++ standard library:\n\n```c\n#define vsnprintf vsnprintf_\n#define snprintf  snprintf_\n```\n\nThis breaks `std::vsnprintf` in `<string>` and other headers. **Fix:** Include C++ standard headers BEFORE vendor headers in `src/source/libscalene.cpp`:\n\n```cpp\n// Include C++ standard headers FIRST\n#include <cstddef>\n#include <string>\n\n// Then vendor headers that define conflicting macros\n#include <heaplayers.h>  // Eventually includes printf.h\n```\n\n## Profiling Guide\n\nSee [Scalene-Agents.md](Scalene-Agents.md) for detailed information about interpreting Scalene's profiling output, including Python vs C time, memory metrics, and optimization strategies.\n\n## Debugging Guide\n\nSee [Scalene-Debugging.md](Scalene-Debugging.md) for signal handler debugging, async profiling debugging, the profile output pipeline (three separate renderers!), and unbounded growth prevention patterns.\n\n## GUI Development Guide\n\nSee [Scalene-GUI.md](Scalene-GUI.md) for adding new columns, Vega-Lite chart types, pie chart best practices (two-wedge rendering, rotating pies), and the chart rendering flow.\n\n<!-- File: agents/windows-memory-profiling-port.md (Tokens: ~4938 | Category: subagent-persona) -->\n# Windows Memory Profiling Port - Progress and Plans\n\n## Overview\n\nPorting Scalene's memory profiling from Linux/macOS to Windows. On Unix systems, Scalene uses `LD_PRELOAD`/`DYLD_INSERT_LIBRARIES` to interpose on malloc/free. Windows doesn't support this mechanism, so we use Python's allocator API instead.\n\n## Architecture\n\n### Unix Approach\n- `libscalene.so`/`libscalene.dylib` is preloaded via environment variables\n- Interposes on malloc/free/realloc at the C library level\n- Uses Unix signals (SIGXCPU, SIGXFSZ) to communicate with Python\n- Uses POSIX shared memory (`/tmp/scalene-*`) for data transfer\n\n### Windows Approach\n- `libscalene.dll` is loaded explicitly via ctypes\n- Uses Python's `PyMem_SetAllocator` API to intercept allocations\n- Uses Windows Events instead of Unix signals (but currently using polling)\n- Uses Windows Named Shared Memory (`Local\\scalene-*`) for data transfer\n\n## Files Created/Modified\n\n### New Files\n- `src/source/libscalene_windows.cpp` - Main Windows DLL implementation (includes Detours native hooks)\n- `src/include/samplefile_win.hpp` - Windows shared memory implementation\n- `src/include/common_win.hpp` - Windows compatibility macros\n- `src/include/mallocrecursionguard_win.hpp` - Windows TLS-based recursion guard\n- `scalene/scalene_windows.py` - Python helper for Windows memory profiling\n- `vendor/Detours/` - Microsoft Detours library for native malloc/free hooking (vendored)\n\n### Modified Files\n- `CMakeLists.txt` - Cross-platform build configuration with Detours integration\n- `src/include/pywhere.hpp` - Windows DLL export/import macros\n- `src/source/pywhere.cpp` - Windows symbol lookup via accessor functions\n- `scalene/scalene_profiler.py` - Windows DLL loading and initialization\n- `scalene/scalene_mapfile.py` - Windows named shared memory support\n- `scalene/scalene_signal_manager.py` - Windows memory polling thread\n- `scalene/scalene_preload.py` - Case-insensitive ARM64 detection\n- `scalene/scalene_arguments.py` - Enable memory profiling on Windows\n\n## Current Status (2024-12-17) - FULLY WORKING WITH NATIVE LIBRARY SUPPORT!\n\n### All Core Features Working\n1. ✅ DLL builds successfully with CMake/MSBuild for ARM64\n2. ✅ DLL loads without crashing\n3. ✅ Python allocator hooks are installed via `PyMem_SetAllocator`\n4. ✅ Allocation size tracking implemented (hash map with critical section)\n5. ✅ Shared memory objects created successfully (both malloc and memcpy)\n6. ✅ ScaleneMapFile opens shared memory from Python side successfully\n7. ✅ All `thread_local` variables removed (caused crashes with dynamically loaded DLLs on Windows)\n8. ✅ Data format updated to match Unix (comma-separated, 9 fields for malloc, 6 for memcpy)\n9. ✅ Windows memory polling thread added to `scalene_signal_manager.py`\n10. ✅ **64-bit pointer handling fixed in ctypes code**\n11. ✅ Memory profiling working - samples being collected and logged\n12. ✅ **Native library tracking (numpy, etc.) via Microsoft Detours**\n13. ✅ **Proper free tracking with manual size tracking hash map**\n\n### Test Results (2024-12-17)\n```\nalloc_samples: 203\nmax_footprint_mb: 152.71\nmax_footprint_python_fraction: 0.000288\nNative fraction: 99.97%\n```\n\nThe profiler now correctly tracks memory allocations from native libraries like numpy, with proper line attribution.\n\n### Known Limitation\n- Console output may fail with `UnicodeEncodeError` on Windows consoles using CP1252 encoding\n- This is not a profiler bug - it's a Windows console limitation\n- **Workaround**: Set `PYTHONIOENCODING=utf-8` environment variable or use `--html` or `--json` output\n\n## The Critical Bug Fix (2024-12-16)\n\n### Root Cause: ctypes 64-bit Pointer Truncation\n\nThe Access Violation crash (exit code 3221225477 = 0xC0000005) was caused by a **ctypes bug** where 64-bit pointers were being truncated to 32-bit values.\n\n**Problem**: By default, ctypes assumes Windows API functions return `c_int` (32-bit). On 64-bit Windows, `MapViewOfFile` returns a 64-bit pointer. Without explicit return type declaration, the high 32 bits were truncated, causing invalid memory addresses.\n\n**Example of bug**:\n```\nMapViewOfFile returns: 0x00007FFBF2650B90  (valid 64-bit pointer)\nctypes stored as:      0x00000000F2650B90  (truncated to 32-bit, invalid!)\n```\n\n### The Fix (scalene_mapfile.py)\n\nAdded proper return type declarations before calling Windows API functions:\n\n```python\nfrom ctypes import wintypes\n\nkernel32 = ctypes.windll.kernel32\n\n# IMPORTANT: Set proper return types for Windows API functions\n# Default ctypes return type is c_int (32-bit) which truncates 64-bit pointers\nkernel32.OpenFileMappingW.restype = wintypes.HANDLE\nkernel32.MapViewOfFile.restype = ctypes.c_void_p\nkernel32.UnmapViewOfFile.argtypes = [ctypes.c_void_p]\nkernel32.CloseHandle.argtypes = [wintypes.HANDLE]\n```\n\nThis fix is in `scalene/scalene_mapfile.py` in the `_init_windows()` method.\n\n## Native Library Memory Tracking (2024-12-17)\n\n### The Problem\n\nThe original Windows implementation only tracked Python allocations via `PyMem_SetAllocator`. Native library allocations (numpy arrays, pandas dataframes, etc.) went untracked because they bypass Python's allocator and call the C runtime `malloc`/`free` directly.\n\n**Before**: Only Python allocations tracked (0.03% of actual memory usage)\n**After**: All allocations tracked including native libraries (99.97% native, 0.03% Python)\n\n### Solution: Microsoft Detours\n\nWe use [Microsoft Detours](https://github.com/microsoft/Detours) to intercept `malloc`/`free`/`realloc`/`calloc` at the C runtime level. Detours works by rewriting the first few bytes of target functions with a jump to our hooks.\n\n**Why Detours over alternatives:**\n- **MinHook**: Does NOT support ARM64 (only x86/x64)\n- **IAT Hooking**: Complex, requires hooking each loaded module separately\n- **ETW (Event Tracing)**: Requires admin privileges, high overhead\n- **Detours**: Supports ARM64, x64, x86, ARM - official Microsoft library, MIT licensed\n\n### Implementation\n\n#### Files Modified/Added\n- `vendor/Detours/` - Microsoft Detours source (vendored)\n- `CMakeLists.txt` - Added Detours to Windows build with architecture-specific disassembler\n- `src/source/libscalene_windows.cpp` - Added native hooks using Detours\n\n#### CMakeLists.txt Changes\n```cmake\n# Microsoft Detours sources for native malloc/free hooking\nset(DETOURS_SOURCES\n    vendor/Detours/src/detours.cpp\n    vendor/Detours/src/modules.cpp\n    vendor/Detours/src/disasm.cpp\n    vendor/Detours/src/image.cpp\n    vendor/Detours/src/creatwth.cpp\n)\n\n# Add architecture-specific disassembler\nif(SCALENE_ARCH STREQUAL \"ARM64\")\n    list(APPEND DETOURS_SOURCES vendor/Detours/src/disolarm64.cpp)\nelseif(SCALENE_ARCH STREQUAL \"X64\")\n    list(APPEND DETOURS_SOURCES vendor/Detours/src/disolx64.cpp)\nelseif(SCALENE_ARCH STREQUAL \"X86\")\n    list(APPEND DETOURS_SOURCES vendor/Detours/src/disolx86.cpp)\nendif()\n\ntarget_compile_definitions(scalene PRIVATE DETOURS_INTERNAL)\n```\n\n#### Native Hook Implementation\n```cpp\n#include \"detours.h\"\n\n// Original function pointers (Detours fills these with trampolines)\nstatic void* (__cdecl *Real_malloc)(size_t) = malloc;\nstatic void (__cdecl *Real_free)(void*) = free;\nstatic void* (__cdecl *Real_realloc)(void*, size_t) = realloc;\nstatic void* (__cdecl *Real_calloc)(size_t, size_t) = calloc;\n\n// Recursion guard - CRITICAL for preventing infinite loops\nstatic bool g_in_native_hook = false;\n\n// Coordination with Python allocator hooks\nstatic bool g_in_python_allocator = false;\n\nstatic void* __cdecl Hooked_malloc(size_t size) {\n    // Check recursion guard FIRST - track_native_alloc may call malloc internally\n    if (g_in_native_hook || g_in_python_allocator) {\n        return Real_malloc(size);\n    }\n\n    g_in_native_hook = true;\n    void* ptr = Real_malloc(size);\n    if (ptr) {\n        track_native_alloc(ptr, size);\n        if (!p_scalene_done) {\n            TheHeapWrapper::register_malloc(size, ptr, false);  // false = native\n        }\n    }\n    g_in_native_hook = false;\n    return ptr;\n}\n\nbool install_native_hooks() {\n    DetourRestoreAfterWith();\n    DetourTransactionBegin();\n    DetourUpdateThread(GetCurrentThread());\n\n    DetourAttach(&(PVOID&)Real_malloc, Hooked_malloc);\n    DetourAttach(&(PVOID&)Real_free, Hooked_free);\n    DetourAttach(&(PVOID&)Real_realloc, Hooked_realloc);\n    DetourAttach(&(PVOID&)Real_calloc, Hooked_calloc);\n\n    return DetourTransactionCommit() == NO_ERROR;\n}\n```\n\n### Critical Bug Fix: Recursion Guard Order\n\n**Problem**: The native hooks use `std::unordered_map` for size tracking, which internally calls `malloc`. If the recursion guard was checked AFTER calling tracking functions, infinite recursion occurred:\n\n1. User code calls `malloc`\n2. `Hooked_malloc` is called\n3. `track_native_alloc(ptr, size)` is called (hash map insert)\n4. Hash map internally calls `malloc` for bucket allocation\n5. `Hooked_malloc` is called again (recursion!)\n6. Since `g_in_native_hook` wasn't set yet, we recurse infinitely → stack overflow\n\n**Fix**: Check and set `g_in_native_hook` at the VERY BEGINNING of each hook, BEFORE any operations that might allocate:\n\n```cpp\nstatic void* __cdecl Hooked_malloc(size_t size) {\n    // MUST check recursion guard FIRST\n    if (g_in_native_hook || g_in_python_allocator) {\n        return Real_malloc(size);  // Bypass hook\n    }\n\n    g_in_native_hook = true;  // Set BEFORE any allocating operations\n    // ... rest of hook logic ...\n    g_in_native_hook = false;\n    return ptr;\n}\n```\n\n### Free Size Tracking\n\n**Problem**: `_msize()` doesn't work reliably for custom allocators (numpy uses aligned allocations with `_aligned_malloc`).\n\n**Solution**: Manual size tracking with a hash map:\n\n```cpp\nstatic std::unordered_map<void*, size_t> g_native_alloc_sizes;\nstatic CRITICAL_SECTION g_native_alloc_sizes_lock;\n\nstatic void track_native_alloc(void* ptr, size_t size) {\n    EnterCriticalSection(&g_native_alloc_sizes_lock);\n    g_native_alloc_sizes[ptr] = size;\n    LeaveCriticalSection(&g_native_alloc_sizes_lock);\n}\n\nstatic size_t untrack_native_alloc(void* ptr) {\n    EnterCriticalSection(&g_native_alloc_sizes_lock);\n    auto it = g_native_alloc_sizes.find(ptr);\n    size_t size = (it != g_native_alloc_sizes.end()) ? it->second : 0;\n    if (it != g_native_alloc_sizes.end()) g_native_alloc_sizes.erase(it);\n    LeaveCriticalSection(&g_native_alloc_sizes_lock);\n    return size;\n}\n```\n\n### Coordination Between Python and Native Hooks\n\nTo prevent double-counting allocations that go through both Python's allocator AND the native malloc:\n\n1. Python allocator hooks set `g_in_python_allocator = true` when active\n2. Native malloc hooks check this flag and skip if Python allocator is handling it\n3. Each allocation is counted exactly once\n\n```cpp\n// In Python allocator hook:\nstatic void* scalene_malloc(void* ctx, size_t len) {\n    g_in_python_allocator = true;  // Prevent native hooks\n    void* ptr = g_original_mem_allocator.malloc(...);\n    // ... tracking code ...\n    g_in_python_allocator = false;\n    return ptr;\n}\n```\n\n## Fixes Applied\n\n### 1. **ctypes 64-bit Pointer Fix** (Critical)\n- Added `restype` declarations for all Windows API functions that return handles or pointers\n- `MapViewOfFile.restype = ctypes.c_void_p` - returns memory address\n- `OpenFileMappingW.restype = wintypes.HANDLE` - returns handle\n- Also set `argtypes` for functions taking pointers\n\n### 2. Data Format Fix\nChanged malloc/free output from tab-separated 4-field format to comma-separated 9-field format matching Unix:\n```\nM,alloc_time,count,python_fraction,pid,pointer,filename,lineno,bytei\\n\\n\n```\n\n### 3. Removed `thread_local` Variables\nWindows DLLs loaded with `LoadLibrary`/ctypes don't properly support `thread_local` variables. Changed all to static variables:\n- `mallocSampler` and `memcpySampler`\n- `g_pythonCount`, `g_cCount`\n- `g_lastMallocTrigger`, `g_freedLastMallocTrigger`\n- `g_memcpyOps`\n- `inMalloc` (for recursion guard)\n\n### 4. Added Windows Memory Polling\nSince Unix signals (SIGXCPU/SIGXFSZ) don't exist on Windows, added a polling thread in `scalene_signal_manager.py`:\n- `_windows_memory_poll_loop()` - polls every 10ms\n- Triggers `_alloc_sigqueue_processor` and `_memcpy_sigqueue_processor`\n- Started when memory profiling is enabled on Windows\n\n### 5. Safety Checks in Allocator Hooks\nAdded null checks for original allocator function pointers to prevent crashes if allocation hooks are called before initialization.\n\n## Test Results\n\nRunning the profiler now shows memory profiling data being collected:\n```\nmalloc_calls=113807912, samples=595, logged=595\n```\n\nThe profiler correctly tracks:\n- Python vs native time\n- Memory allocations with line attribution\n- Memory timeline/growth rate\n\n## Build Instructions\n\n```bash\n# Configure with CMake (for ARM64 native)\ncmake -B build -A ARM64 -DPython3_ROOT_DIR=\"C:\\Users\\emery\\AppData\\Local\\Programs\\Python\\Python311-arm64\"\n\n# Build the DLL\nMSBuild.exe build/scalene.sln -p:Configuration=Release -p:Platform=ARM64 -t:scalene\n\n# DLL is automatically placed in scalene\\ directory\n\n# Run profiler\npython -m scalene --cli --cpu --memory test\\testme.py\n```\n\n## Key Differences from Unix Implementation\n\n| Aspect | Unix | Windows |\n|--------|------|---------|\n| Native malloc hooking | LD_PRELOAD | **Microsoft Detours** (inline function hooking) |\n| Python allocator | PyMem_SetAllocator | PyMem_SetAllocator (same) |\n| Signals | SIGXCPU/SIGXFSZ | Polling thread |\n| Shared Memory | /tmp files + mmap | Named shared memory |\n| Symbol Lookup | dlsym(RTLD_DEFAULT) | GetProcAddress + accessor functions |\n| Size Tracking | malloc_usable_size | Manual hash map (\\_msize unreliable) |\n| Thread-local | `thread_local` | Static variables (GIL protects) |\n| Pointer handling | Native 64-bit | **Requires explicit ctypes declarations** |\n| Architecture support | All | ARM64, x64, x86 (via Detours) |\n\n## Learnings\n\n### 1. ctypes Default Return Types Are Dangerous on 64-bit Windows\n- **Always** set `restype` for any Windows API function that returns a handle or pointer\n- Default `c_int` (32-bit) silently truncates 64-bit values\n- This causes delayed crashes that are hard to debug (crash happens when truncated pointer is used, not when it's returned)\n- Use `ctypes.c_void_p` for memory addresses, `wintypes.HANDLE` for handles\n\n### 2. Debugging Methodology That Worked\nThe crash was isolated using **systematic component elimination**:\n1. First confirmed DLL was the cause by renaming it (profiler ran without crash)\n2. Tested with allocator hooks disabled - still crashed (ruled out hooks as cause)\n3. Tested with only shared memory initialization - still crashed (narrowed to shared memory)\n4. Examined shared memory code and found the ctypes issue\n\n### 3. Windows DLL Loading Constraints\n- `thread_local` variables don't work reliably in DLLs loaded via `LoadLibrary`/ctypes\n- Static variables work fine since Python's GIL serializes access\n- DLL entry point (`DllMain`) has severe restrictions - avoid complex initialization there\n\n### 4. Build System Considerations\n- CMake generates Visual Studio solutions that work well for cross-platform builds\n- ARM64 native builds require explicit `-A ARM64` and matching Python version\n- MSBuild can be invoked directly for rebuilds without re-running CMake\n\n### 5. Windows vs Unix IPC\n- Named shared memory on Windows uses `Local\\` prefix for per-session namespace\n- Windows Events can replace Unix signals but polling is simpler for this use case\n- Mutexes on Windows are more heavyweight than Unix futexes\n\n### 6. Recursion Guards in malloc Hooks Must Come FIRST\n- **Critical**: Any code in a malloc hook that uses STL containers (std::unordered_map, std::vector, etc.) may internally call malloc\n- The recursion guard (`g_in_native_hook`) MUST be checked and set BEFORE any other operations\n- Failing to do this causes infinite recursion → stack overflow → exit code 127 or crash\n- Pattern: `if (guard) return original(); guard = true; ... ; guard = false; return result;`\n\n### 7. MinHook Does NOT Support ARM64\n- MinHook is a popular inline hooking library but only supports x86 and x64\n- Microsoft Detours supports ARM64, ARM, x86, x64, and IA64\n- Detours is MIT licensed (open source since 2018) and very well tested\n- Use architecture-specific disassemblers: `disolarm64.cpp` for ARM64, `disolx64.cpp` for x64\n\n### 8. _msize() Is Unreliable for Custom Allocators\n- `_msize()` only works for standard CRT heap allocations\n- numpy and other libraries use aligned allocations (`_aligned_malloc`) which `_msize` doesn't handle\n- Solution: Manual size tracking with a hash map, storing size at allocation time\n- This adds ~10% overhead but is necessary for accurate free tracking\n\n### 9. Coordination Between Multiple Hook Layers\n- When hooking at both Python allocator level AND native malloc level, coordination is essential\n- Use a flag (`g_in_python_allocator`) to prevent native hooks from double-counting Python allocations\n- Pattern: Set flag before calling original allocator, clear after\n- This ensures each allocation is tracked exactly once\n\n## Next Steps\n\n### High Priority\n1. **x64 Build and Testing**: Current implementation tested only on ARM64. Need to verify x64 builds work correctly with Detours.\n\n2. **Unicode Console Output**: The `UnicodeEncodeError` for sparkline characters could be handled more gracefully:\n   - Detect console encoding and fall back to ASCII sparklines\n   - Or auto-set `PYTHONIOENCODING=utf-8` when running on Windows\n\n3. **Performance Optimization**: The polling thread polls every 10ms. Consider:\n   - Using Windows Events for more efficient signaling\n   - Adaptive polling rate based on allocation frequency\n\n### Medium Priority\n4. **Multi-Process Support**: Test and fix any issues with profiling child processes on Windows (the Unix `redirect_python` mechanism may need Windows adaptation).\n\n5. **GPU Profiling on Windows**: Verify NVIDIA GPU profiling works on Windows (pynvml should work but needs testing).\n\n6. **CI/CD Integration**: Add Windows builds to GitHub Actions workflow:\n   - Build DLL for both x64 and ARM64\n   - Run tests on Windows\n   - Include DLL in wheel packages\n\n### Low Priority\n7. **Windows Event-Based Signaling**: Replace polling with proper Windows Events for lower latency and CPU usage:\n   - DLL already creates events (`ScaleneMallocEvent`, etc.)\n   - Python side would use `WaitForMultipleObjects` instead of polling\n\n8. **Memory Leak Detection**: The `--memory-leak-detector` feature may need Windows-specific testing.\n\n9. **Web UI Testing**: Verify the web-based GUI works correctly on Windows (browser launching, port binding).\n\n### Completed\n- ~~**Native Library Memory Tracking**: Implement malloc/free hooking for numpy, pandas, etc.~~ ✅ Done (2024-12-17) - Using Microsoft Detours\n\n## Debugging Tips for Future Issues\n\n### Useful Debug Techniques\n```python\n# Add to scalene_mapfile.py to debug shared memory issues\nprint(f\"Handle value: {handle:#x}\", file=sys.stderr)\nprint(f\"View address: {view:#x}\", file=sys.stderr)\n\n# Check if pointer looks valid (should have high bits set on 64-bit)\nif view < 0x100000000:\n    print(\"WARNING: Pointer may be truncated!\", file=sys.stderr)\n```\n\n### Building with Debug Output\n```cpp\n// In libscalene_windows.cpp, temporarily add:\nfprintf(stderr, \"DEBUG: ptr=%p, size=%zu\\n\", ptr, size);\n```\n\nThen rebuild with:\n```bash\nMSBuild.exe build/scalene.sln -p:Configuration=Release -p:Platform=ARM64 -t:scalene\n```\n\n### Common Windows Error Codes\n- `0xC0000005` (3221225477) - Access Violation (invalid memory access)\n- `0xC0000008` - Invalid Handle\n- `0xC000001D` - Illegal Instruction (wrong architecture)\n- Error 193 - \"Not a valid Win32 application\" (architecture mismatch)\n\n## References\n\n- Python Memory Allocator API: https://docs.python.org/3/c-api/memory.html\n- Windows Named Shared Memory: https://docs.microsoft.com/en-us/windows/win32/memory/creating-named-shared-memory\n- CMake Windows DLL: https://cmake.org/cmake/help/latest/prop_tgt/WINDOWS_EXPORT_ALL_SYMBOLS.html\n- Windows thread_local issues: https://devblogs.microsoft.com/cppblog/c11-thread-local-storage-and-dll-load-failure/\n- **ctypes 64-bit pointers**: https://docs.python.org/3/library/ctypes.html#return-types\n- **Microsoft Detours**: https://github.com/microsoft/Detours - Official Microsoft library for inline function hooking (MIT license)\n- Detours Wiki: https://github.com/microsoft/Detours/wiki - API documentation and examples\n</yakaai_skills>"}