Profiler
Basilisk includes a fully integrated Python profiler. CPU hotspot heatmaps appear inline on your code, memory leaks are flagged as diagnostics, and profile results open in a flame-graph panel — without leaving your workspace.
Overview
The Basilisk profiler combines two complementary engines:
- CPU profiling — sampling-based, with no code changes required. External processes are sampled by py-spy (embedded in Basilisk as a Rust library); programs you launch through Basilisk's debugger on macOS are sampled by a cooperative in-process sampler instead, because modern macOS blocks external memory reads even for root (see Platform requirements). Default rate: 100 Hz.
- Memory profiling — powered by Python's
tracemallocandgcmodules, driven through an active Basilisk debug session: the editor injects the instrumentation via the debug adapter, so memory tracking works on programs you are debugging (or launch with "Run & Track Memory"), not on arbitrary external processes.
Both engines live in the Basilisk LSP server (Rust). Editors call LSP commands (basilisk.profiler.*, basilisk.memory.* via workspace/executeCommand) and render what the server sends back — no standalone CLI, no pip install.
CPU Profiling
Starting a session (VS Code)
The Python Processes panel (Basilisk icon in the activity bar) is the home of profiling:
- Run & Profile CPU (Current File) — launches the active Python file and profiles it from the first line. The easiest way to profile a script end-to-end.
- Profile CPU — inline button on any row of the panel, which lists every running Python process on your system. No PIDs to hand-type.
- Basilisk: Profile Debug Session — attaches the profiler to the process the debugger is currently running, so breakpoints and profiling data work together.
The palette command Basilisk: Start Profiling doesn't guess a target — it focuses the panel so you pick one explicitly.
While a session runs, the status bar shows a live sample counter; click it (or run Basilisk: Stop Profiling) to finish.
Inline heatmap annotations
As samples accumulate and when a session ends, hot lines get colored annotations directly in the editor:
| Level | Threshold | Colour |
|---|---|---|
| Critical | ≥ 20% of CPU time | 🔴 #e8500a |
| Hot | ≥ 10% | 🟠 #f97316 |
| Warm | ≥ 5% | 🟡 #fbbf24 |
| Cool | ≥ 1% | ⚫ #4a5468 |
Lines below 1% receive no annotation. Hot lines and functions also appear in the Problems panel as hint-severity diagnostics (BSK-PROF-LINE, BSK-PROF-FUNC) with the measured percentage, so the profile is navigable from the same place as type errors.
Taking a snapshot
Basilisk: Take Profile Snapshot captures the hotspots collected so far while the session keeps running — decorations and diagnostics update immediately, and the toast's View Results button opens the results panel. Snapshots are in-memory; files are written when the session stops.
Results panel
Stopping a session opens the Basilisk Profiler panel beside your source:
- Summary cards — total samples, duration, hot function and hot line counts
- Flame graph — with an Open Interactive Flame Graph button for the zoom-and-search SVG
- Hot functions / hot lines tables — click any row to jump to the source line
- Open Trace in VS Code Viewer — opens the raw trace in VS Code's built-in profile viewer (the same flame chart and bottom-up tables used for Node.js profiling)
- Open in Speedscope (external) — opens speedscope.app with the profile loaded automatically (served over a private localhost URL; a companion toast offers the file for manual drag-in if your browser blocks localhost fetches)
Closed the panel? Basilisk: Show Profile Results re-opens it for the most recent profile.
On stop, Basilisk writes three artifacts to your system temp directory: basilisk-<session>.speedscope.json, basilisk-<session>.flamegraph.svg, and basilisk-<session>.cpuprofile.
Programs too short to sample
A sampling profiler takes one snapshot per 10 ms (at 100 Hz). If your script finishes its work faster than that, no samples land in your code — and Basilisk says exactly that instead of showing an empty flame graph. Profile a longer run, or a loop that exercises the hot path repeatedly.
Profiling presets
Set basilisk.profiler.preset in your editor settings:
| Preset | Sample rate | Duration |
|---|---|---|
default |
your sampleRate setting (100 Hz) |
until stopped |
quick |
100 Hz | 10 s |
detailed |
200 Hz | 60 s |
longRunning |
50 Hz | until stopped |
Memory Profiling
Memory tracking rides the debugger: Basilisk injects tracemalloc into the debuggee through the debug adapter, so an active Basilisk debug session is required (or use the launch flow, which creates one for you).
Starting
- Run & Track Memory (Current File) — panel button; launches the active file with tracking armed from the first line. If the program runs to completion, a final snapshot is captured automatically as it exits — you always get a result.
- Basilisk: Start Memory Tracking — starts tracking in the debug session you're already in. Also one click away on the memory status-bar item whenever a Basilisk debug session is active.
While tracking, Snapshot / Compare / Stop buttons sit directly on the debug toolbar, next to Continue and Step.
Snapshots and leak detection
- Basilisk: Take Memory Snapshot — records the current allocation state, paints per-line allocation decorations, and opens the Basilisk memory dashboard; the raw V8
.heapprofileis one click away via the dashboard's Open Heap Profile in VS Code Viewer and Open in Speedscope (external) buttons (speedscope imports heap profiles too). - Basilisk: Compare Memory Snapshots — diffs the latest two snapshots and emits leak diagnostics on the allocating lines.
- Autopilot — with
basilisk.profiler.autoSnapshotOnPause(on by default), every debugger pause quietly captures a snapshot, and the diff surfaces when there's something to show.basilisk.profiler.autoSnapshotadds interval-based capture for long runs.
Leak confidence is criteria-based, not a guess:
| Badge | Criteria |
|---|---|
| Definite | Object with __del__ participating in a reference cycle — uncollectable |
| High | Same line kept growing across 3+ consecutive snapshot diffs |
| Medium | Growth across 2 consecutive diffs, or a single diff over 10 MB |
| Low | Growth observed once — worth watching |
Memory diagnostics
| Code | Severity | Meaning |
|---|---|---|
BSK-MEM-ALLOC |
Hint | Line is a top allocation site |
BSK-MEM-GROWTH |
Warning | Allocations at this line grew between snapshots |
BSK-MEM-LEAK |
Warning (Error when Definite) | Suspected leak with confidence badge |
BSK-MEM-CYCLE |
Error | Reference cycle that blocks garbage collection |
Reference graph and dashboard
- Basilisk: Show Reference Graph — walks the object graph from a leak candidate via
gc.get_referrers()and renders what's retaining it; cycles are highlighted. - The memory dashboard aggregates the session: heap timeline, top allocators, leak candidates — each row navigates to source.
- Basilisk: Force Garbage Collection — runs
gc.collect()in the debuggee, so you can confirm whether "leaked" memory is actually reclaimable.
Stop tracking with Basilisk: Stop Memory Tracking (also on the debug toolbar and the status-bar menu).
Configuration
The profiler is configured through editor settings (basilisk.profiler.* in VS Code) — it has no pyproject.toml section:
| Setting | Default | Description |
|---|---|---|
basilisk.profiler.sampleRate |
100 |
Sampling frequency in Hz (1–1000) |
basilisk.profiler.includeNative |
false |
Include C-extension frames (py-spy attach only) |
basilisk.profiler.lineThreshold |
1 |
Minimum CPU % for a line annotation |
basilisk.profiler.functionThreshold |
2 |
Minimum CPU % for a function diagnostic |
basilisk.profiler.maxDiagnosticsPerFile |
20 |
Cap on profiler diagnostics per file |
basilisk.profiler.showInlineHeatMap |
true |
Toggle the inline heat decorations |
basilisk.profiler.profileOnLaunch |
false |
Auto-profile every Basilisk debug launch |
basilisk.profiler.processRefreshMs |
2000 |
Python Processes panel refresh interval |
basilisk.profiler.preset |
"default" |
default / quick / detailed / longRunning |
basilisk.profiler.autoSnapshotOnPause |
true |
Memory snapshot on every debugger pause |
basilisk.profiler.autoSnapshot |
false |
Interval-based memory snapshots |
basilisk.profiler.autoSnapshotInterval |
30 |
Interval in seconds for the above |
Platform requirements
macOS
Two paths, chosen automatically:
- Programs you launch through Basilisk ("Run & Profile CPU", debug sessions) are profiled by the cooperative in-process sampler — no elevation, no prompts. Modern macOS refuses external memory reads even for root, so for launched programs Basilisk samples from inside the debuggee instead. Trade-off: C-extension frames are not visible on this path.
- Attaching to an external process (one you started in a terminal — even under your own user) requires elevation: Basilisk ships a privileged helper (
basilisk-profiler-helper), and macOS shows a standard administrator-password prompt the first time. The helper attaches as root and streams samples back over a local Unix socket. Cancelling the prompt stops profiling with a clear error.
Linux
ptrace access is governed by /proc/sys/kernel/yama/ptrace_scope:
| Scope | Meaning | Basilisk behaviour |
|---|---|---|
0 |
Any same-user process | Attach works directly |
1 (default on many distros) |
Ancestors only | Programs launched through Basilisk attach fine (the LSP is an ancestor); attaching to unrelated processes fails with remedies |
2 |
Admin only | Requires elevated capabilities |
3 |
Disabled | External attach unavailable |
If an attach is denied, Basilisk's error explains the options: sudo sysctl kernel.yama.ptrace_scope=0, granting the binary cap_sys_ptrace, or profiling via a Basilisk debug session instead.
Windows
Processes owned by your user attach with no elevation (standard Win32 APIs). Profiling another user's processes is not supported.
CPU diagnostic codes
| Code | Severity | Description |
|---|---|---|
BSK-PROF-LINE |
Hint | Line spends ≥ lineThreshold% of CPU time |
BSK-PROF-FUNC |
Hint | Function spends ≥ functionThreshold% of CPU time |
Profiler diagnostics appear only while a session has produced data, and clear with the session. Memory codes are listed above.
Profiling in Zed
Zed's extension API has no webviews or custom panels, so the Zed extension surfaces profiling through LSP diagnostics (hot lines appear inline as hints) and informational slash commands in the assistant panel — each command explains the matching LSP command (basilisk.profiler.* / basilisk.memory.*) and how to drive it, rather than executing it directly:
| Command | Topic |
|---|---|
/profile |
Starting CPU profiling |
/profstop |
Stopping and getting results |
/profsnapshot |
Mid-session snapshots |
/memleak |
Starting memory tracking |
/memstop |
Stopping memory tracking |
/memrefs |
Reference-graph walks |
Flame graphs open via the exported SVG or by dragging the speedscope JSON into speedscope.app.
Profiling in Neovim
The basilisk.nvim plugin exposes LSP-backed user commands:
| Command | Action |
|---|---|
:BasiliskProfile [pid] |
Start CPU profiling (bare form prompts via process list) |
:BasiliskProfileStop |
Stop CPU profiling |
:BasiliskProfileSnapshot |
Take a CPU snapshot |
:BasiliskMemLeak |
Start memory tracking |
:BasiliskMemStop |
Stop memory tracking |
:BasiliskMemRefs {type} |
Reference graph for a type (with completion) |
Default keymaps: <prefix>p starts profiling, <prefix>P stops it. Heat annotations render as virtual text.
Architecture
┌──────────────┐ workspace/executeCommand ┌──────────────────────────────┐
│ VS Code / │────────────────────────────►│ Basilisk LSP (Rust) │
│ Zed / Nvim │◄────────────────────────────│ ┌──────────────────────────┐ │
└──────────────┘ diagnostics + progress │ │ ProfileSessionManager │ │
notifications │ │ Sampler (py-spy / │ │
│ │ cooperative) │ │
│ │ SampleAggregator │ │
│ │ Speedscope / SVG / │ │
│ │ .cpuprofile exporters │ │
│ │ MemorySessionManager │ │
│ │ SnapshotDiffer │ │
│ │ LeakTracker │ │
│ └──────────────────────────┘ │
└──────────────┬───────────────┘
│ samples
▼
┌──────────────────┐
│ Python process │
│ (your code) │
└──────────────────┘
The LSP server owns all profiling state. Editors invoke basilisk.profiler.* / basilisk.memory.* commands and render results delivered through diagnostics and the basilisk/profiler/progress and basilisk/memory/timeline notifications. That's why the same engine drives VS Code, Zed, and Neovim.
Troubleshooting
"Process not found"
The target process exited before the profiler could attach. Make sure your script is still running, or use Run & Profile CPU (Current File) to launch and profile in one step.
"Not a Python process"
The profiler works with CPython. PyPy and other interpreters are not supported.
"Permission denied" (macOS)
Enter your password on the administrator prompt when attaching to an external process — or launch the program through Basilisk instead, which needs no elevation at all.
"Permission denied" (Linux)
Check cat /proc/sys/kernel/yama/ptrace_scope — see Linux requirements. Programs launched through Basilisk are unaffected.
"Already profiling"
Each process supports one CPU profiling session at a time. Stop the current session first.
"Captured N samples, but none landed in your code"
The program finished its work faster than the sampling interval — see Programs too short to sample.
How the engine compares
Basilisk embeds py-spy because it is the only Python profiler available as a Rust library crate, which is what makes a zero-dependency, in-LSP profiler possible:
| Property | py-spy | Scalene | Memray | Austin |
|---|---|---|---|---|
| Embeddable as a Rust crate | Yes | No | No | No |
| Attaches to running processes | Yes | No | No | Yes |
| Modifies the target program | No | Yes | Yes | No |
Comparison drawn from each project's own documentation — py-spy, Scalene, Memray, Austin.
Next steps
- Debugging — the integrated debugger that memory tracking (and macOS launch profiling) builds on
- Installation — editor setup
- Configuration reference — checker and LSP configuration (profiler settings are editor settings, listed above)