py-spy: Python Sampling Profiler

0.4.1 · active · verified Sun Apr 05

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.

Warnings

Install

Quickstart

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.

import time
import subprocess
import os

# Create a dummy Python script to profile
python_script_content = """
import time
import sys

def busy_loop(iterations):
    result = 0
    for i in range(iterations):
        result += i * i
    return result

def main():
    print(f"[{os.getpid()}] Starting my_app.py...")
    for _ in range(5):
        busy_loop(1_000_000)
        time.sleep(0.5)
    print(f"[{os.getpid()}] my_app.py finished.")

if __name__ == "__main__":
    main()
"""

with open("my_app.py", "w") as f:
    f.write(python_script_content)

print("Running my_app.py in the background...")
# Start the target Python script in the background
# In a real scenario, you'd profile an already running process by PID
# For quickstart, we launch it and then 'profile' it (though py-spy can launch directly too)
process = subprocess.Popen([sys.executable, "my_app.py"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
pid = process.pid
print(f"Target Python process PID: {pid}")

# Wait a bit for the script to start
time.sleep(2)

print("Recording profile with py-spy...")
# Example: Record a flame graph of the running process
# On Linux, you might need 'sudo' if attaching to an existing process (not a child)
# On Docker, ensure --cap-add SYS_PTRACE is used
# os.environ.get is not directly applicable here as py-spy is a CLI tool
# but the principle for quickstarts is to show a runnable example.
try:
    # Using subprocess.run for simplicity, would typically be a direct shell command
    # For this example, we assume necessary permissions are available (e.g., running as root or correct ptrace_scope)
    # In a real shell, you'd do: py-spy record -o profile.svg --pid <PID>
    result = subprocess.run(["py-spy", "record", "-o", "profile.svg", "--pid", str(pid)], capture_output=True, text=True, check=True)
    print("py-spy stdout:", result.stdout)
    print("py-spy stderr:", result.stderr)
    print("Flame graph saved to profile.svg")
except subprocess.CalledProcessError as e:
    print(f"Error running py-spy: {e}")
    print("py-spy stdout:", e.stdout)
    print("py-spy stderr:", e.stderr)
    print("HINT: You might need to run this with 'sudo' or ensure appropriate ptrace permissions.")
except FileNotFoundError:
    print("Error: py-spy command not found. Ensure py-spy is installed and in your PATH.")
finally:
    # Clean up the background process
    if process.poll() is None:
        process.terminate()
        process.wait(timeout=5)
    # Clean up the dummy script
    os.remove("my_app.py")
    if os.path.exists("profile.svg"):
        print("To view the profile, open profile.svg in a web browser.")

view raw JSON →