{"library":"py-spy","title":"py-spy: Python Sampling Profiler","description":"py-spy is a sampling profiler for Python programs, implemented in Rust, designed for extremely low overhead. It enables visualization of Python program execution without requiring restarts or code modification, making it safe for production environments. The library actively maintains support across Linux, macOS, Windows, and FreeBSD, covering a wide range of CPython interpreter versions, with a continuous release cadence reflected by recent minor version updates.","status":"active","version":"0.4.1","language":"en","source_language":"en","source_url":"https://github.com/benfred/py-spy","tags":["profiling","performance","debugger","cli-tool","rust","flame-graph","sampling-profiler"],"install":[{"cmd":"pip install py-spy","lang":"bash","label":"Install with pip"}],"dependencies":[{"reason":"py-spy relies on OS-level system calls (e.g., `process_vm_readv` on Linux, `vm_read` on macOS, `ReadProcessMemory` on Windows) to read memory from the profiled Python process. The `SYS_PTRACE` capability is crucial on Linux for attaching to processes, especially in containerized environments.","package":"Operating System ptrace capabilities","optional":false}],"imports":[],"quickstart":{"code":"import time\nimport subprocess\nimport os\n\n# Create a dummy Python script to profile\npython_script_content = \"\"\"\nimport time\nimport sys\n\ndef busy_loop(iterations):\n    result = 0\n    for i in range(iterations):\n        result += i * i\n    return result\n\ndef main():\n    print(f\"[{os.getpid()}] Starting my_app.py...\")\n    for _ in range(5):\n        busy_loop(1_000_000)\n        time.sleep(0.5)\n    print(f\"[{os.getpid()}] my_app.py finished.\")\n\nif __name__ == \"__main__\":\n    main()\n\"\"\"\n\nwith open(\"my_app.py\", \"w\") as f:\n    f.write(python_script_content)\n\nprint(\"Running my_app.py in the background...\")\n# Start the target Python script in the background\n# In a real scenario, you'd profile an already running process by PID\n# For quickstart, we launch it and then 'profile' it (though py-spy can launch directly too)\nprocess = subprocess.Popen([sys.executable, \"my_app.py\"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)\npid = process.pid\nprint(f\"Target Python process PID: {pid}\")\n\n# Wait a bit for the script to start\ntime.sleep(2)\n\nprint(\"Recording profile with py-spy...\")\n# Example: Record a flame graph of the running process\n# On Linux, you might need 'sudo' if attaching to an existing process (not a child)\n# On Docker, ensure --cap-add SYS_PTRACE is used\n# os.environ.get is not directly applicable here as py-spy is a CLI tool\n# but the principle for quickstarts is to show a runnable example.\ntry:\n    # Using subprocess.run for simplicity, would typically be a direct shell command\n    # For this example, we assume necessary permissions are available (e.g., running as root or correct ptrace_scope)\n    # In a real shell, you'd do: py-spy record -o profile.svg --pid <PID>\n    result = subprocess.run([\"py-spy\", \"record\", \"-o\", \"profile.svg\", \"--pid\", str(pid)], capture_output=True, text=True, check=True)\n    print(\"py-spy stdout:\", result.stdout)\n    print(\"py-spy stderr:\", result.stderr)\n    print(\"Flame graph saved to profile.svg\")\nexcept subprocess.CalledProcessError as e:\n    print(f\"Error running py-spy: {e}\")\n    print(\"py-spy stdout:\", e.stdout)\n    print(\"py-spy stderr:\", e.stderr)\n    print(\"HINT: You might need to run this with 'sudo' or ensure appropriate ptrace permissions.\")\nexcept FileNotFoundError:\n    print(\"Error: py-spy command not found. Ensure py-spy is installed and in your PATH.\")\nfinally:\n    # Clean up the background process\n    if process.poll() is None:\n        process.terminate()\n        process.wait(timeout=5)\n    # Clean up the dummy script\n    os.remove(\"my_app.py\")\n    if os.path.exists(\"profile.svg\"):\n        print(\"To view the profile, open profile.svg in a web browser.\")","lang":"python","description":"This quickstart demonstrates how to use `py-spy` to record a flame graph (`profile.svg`) of a running Python process. It first starts a simple Python script in the background, then attaches `py-spy` to its process ID (PID) to capture profiling data. For live viewing, `py-spy top --pid <PID>` is also a common command. Remember that `py-spy` often requires elevated privileges (e.g., `sudo`) or specific system capabilities (like `SYS_PTRACE` in Docker) to function correctly when attaching to existing processes."},"warnings":[{"fix":"Run `py-spy` with `sudo` when attaching to existing processes. For Docker, add `--cap-add SYS_PTRACE` to your `docker run` or `docker-compose.yml` configuration. Ensure `ptrace_scope` is configured appropriately on Linux if `sudo` is not desired.","message":"py-spy frequently requires elevated permissions to profile running processes. On Linux, attaching to an existing process usually necessitates `sudo` or modification of `ptrace_scope`. For Docker or Kubernetes containers, the container must be launched with `--cap-add SYS_PTRACE` to allow `py-spy` to read process memory. On macOS, running `py-spy` as root (`sudo`) is generally required.","severity":"breaking","affected_versions":"All versions"},{"fix":"Always use the latest available `py-spy` version to ensure compatibility with the Python interpreter you are profiling. `pip install --upgrade py-spy`.","message":"Older versions of `py-spy` might not fully support profiling newer Python interpreters due to changes in CPython's internal ABI. For example, Python 3.12 and 3.13 support was added in recent `py-spy` releases (v0.4.0+), and using older `py-spy` versions with these Python versions could lead to errors or inaccurate profiling.","severity":"gotcha","affected_versions":"< 0.4.0"},{"fix":"Profile Python interpreters installed via alternative methods (e.g., Homebrew, Anaconda, pyenv) or within an active virtual environment, as these are typically located outside `/usr/bin`.","message":"On macOS, System Integrity Protection (SIP) prevents `py-spy` from profiling Python interpreters installed at `/usr/bin`. Attempting to profile such interpreters will fail.","severity":"gotcha","affected_versions":"All versions on macOS"},{"fix":"Use the `--idle` flag to include idle frames in the profile, or the `--nonblocking` option to avoid pausing the target (though this may introduce occasional sampling errors). Consider adjusting the sampling rate (`-r` option) to a non-standard value to mitigate aliasing, although it might not fully eliminate the issue.","message":"Profiling very idle Python programs or those with highly regular, periodic activity can result in misleading or inaccurate flame graphs and `top` output due to sampling aliasing. `py-spy`'s default sampling rate might coincidentally align with program's internal ticks, leading to over- or under-reporting of activity.","severity":"gotcha","affected_versions":"All versions"},{"fix":"For memory analysis, use dedicated Python memory profiling tools (e.g., `memory_profiler`, `objgraph`, built-in `tracemalloc`) in conjunction with `py-spy` for a comprehensive performance overview.","message":"py-spy is a CPU sampling profiler only and does not provide functionality for memory profiling. It cannot diagnose memory leaks, track memory allocation/deallocation, or optimize memory usage.","severity":"gotcha","affected_versions":"All versions"}],"env_vars":null,"last_verified":"2026-04-05T00:00:00.000Z","next_check":"2026-07-04T00:00:00.000Z"}