{"owner":"plasma-umass","repo":"scalene","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["CLAUDE.md"],"skills":{"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 to a temp directory when serving via HTTP, enabling the GUI to work in air-gapped/offline environments.\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 `scalene_utility.py`**:\n   - Read environment variable in `api_keys` dict\n   - Pass to template rendering\n\n7. **Rebuild the bundle**:\n   ```bash\n   npm --prefix scalene/scalene-gui run build\n   ```\n\n### Environment Variable API Keys\n\nThe GUI supports prepopulating API keys 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_utility.py` reads env vars and passes to Jinja2 template\n2. Template injects `envApiKeys` JavaScript object into page\n3. `persistence.ts` uses env vars as fallbacks when localStorage is empty\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":{"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 to a temp directory when serving via HTTP, enabling the GUI to work in air-gapped/offline environments.\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 `scalene_utility.py`**:\n   - Read environment variable in `api_keys` dict\n   - Pass to template rendering\n\n7. **Rebuild the bundle**:\n   ```bash\n   npm --prefix scalene/scalene-gui run build\n   ```\n\n### Environment Variable API Keys\n\nThe GUI supports prepopulating API keys 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_utility.py` reads env vars and passes to Jinja2 template\n2. Template injects `envApiKeys` JavaScript object into page\n3. `persistence.ts` uses env vars as fallbacks when localStorage is empty\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","title":"CLAUDE.md","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 to a temp directory when serving via HTTP, enabling the GUI to work in air-gapped/offline environments.\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 `scalene_utility.py`**:\n   - Read environment variable in `api_keys` dict\n   - Pass to template rendering\n\n7. **Rebuild the bundle**:\n   ```bash\n   npm --prefix scalene/scalene-gui run build\n   ```\n\n### Environment Variable API Keys\n\nThe GUI supports prepopulating API keys 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_utility.py` reads env vars and passes to Jinja2 template\n2. Template injects `envApiKeys` JavaScript object into page\n3. `persistence.ts` uses env vars as fallbacks when localStorage is empty\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","category":"root","tokens":5873}]}