At a glance
py-spy dump --pid 12345prints a stack trace of a stuck process right now.py-spy top --pid 12345shows live function times and a%GILcolumn in the terminal.py-spy record -o profile.svg --pid 12345 --duration 30writes a 30-second flame graph.- In Docker, add
--cap-add SYS_PTRACEor run py-spy from the host.
Install py-spy
Install py-spy from PyPI as a prebuilt binary wheel with pip install py-spy. On macOS you can also use brew install py-spy, and Rust users can run cargo install py-spy. The latest release is 0.4.2, published April 24, 2026.
pip install py-spy
py-spy --versionpy-spy runs on Linux, macOS, Windows and FreeBSD. It is written in Rust and runs in a separate process from your program, reading its memory without interrupting or modifying your Python code, which is why its README calls it safe to use against production code.
dump, top and record
py-spy dump prints the current call stack of every Python thread once and exits. py-spy top opens a live view of the functions taking the most time, like Unix top, with a %GIL column. py-spy record samples over time and writes a profile to a file, an interactive SVG flame graph by default.
| Command | What it shows | Use it when |
|---|---|---|
py-spy dump --pid 12345 | The current stack of every thread, once | A job looks stuck and you want to see where |
py-spy top --pid 12345 | Live view of the functions using the most time | You want to watch a slow process right now |
py-spy record -o profile.svg --pid 12345 | Interactive SVG flame graph | You want to find where the time goes |
py-spy record --format speedscope -o profile.json --pid 12345 | A profile for the speedscope viewer | You want to zoom into a long profile |
py-spy record -o profile.svg -- python train.py | A flame graph of a program py-spy starts | You can't attach as root on Linux |
record and top can attach to a running process with --pid or start a new program after --; dump only attaches, with --pid. With record, --duration stops after a fixed number of seconds, or press Ctrl+C to stop and write the file.
py-spy dump --pid 12345
py-spy top --pid 12345
py-spy record -o profile.svg --pid 12345 --duration 30
py-spy record -o profile.svg -- python train.pyRead the output
A dump starts with the process and interpreter lines, then lists each thread with its frames, newest call first, and marks idle threads in the thread header:
Process 3041: python job.py
Python v3.13.14 (/usr/local/bin/python3.13)
Thread 3041 (idle): "MainThread"
load_batch (job.py:5)
train (job.py:11)
<module> (job.py:13)In the flame graph from record, wider boxes took more of the sampled time. py-spy samples at a --rate of 100 per second and leaves out idle threads by default: in our test, a 3-second recording of a script that spends its time in time.sleep wrote 0 samples by default and 299 with --idle.
The flags that change what gets captured:
--idleincludes idle threads, which are excluded by default.--gilonly includes threads that hold the GIL.--nativeadds stacks from Cython, C and C++ extensions (Linux and Windows on x86-64, plus Linux on ARM).--subprocessesprofiles child processes too, formultiprocessingorgunicornworker pools.--nonblockingdoesn't pause the target while sampling, at the cost of occasional sampling errors.--localsmakesdumpprint the local variables of each frame.
Fix Permission Denied
Linux and macOS
On most Linux systems, attaching to a process that py-spy did not start requires root because of the kernel.yama.ptrace_scope setting. If you can start the job yourself, launching it under py-spy (-- python myprogram.py) avoids the need for root. macOS always requires root, and System Integrity Protection blocks profiling the system Python in /usr/bin.
Without enough privileges, py-spy prints the fix itself. sudo env "PATH=$PATH" keeps your virtualenv's py-spy on the path under sudo:
$ py-spy dump --pid 12345
Permission Denied: Try running again with elevated permissions by going 'sudo env "PATH=$PATH" !!'
$ sudo env "PATH=$PATH" py-spy dump --pid 12345Docker and Kubernetes
Inside a Docker container, py-spy usually fails with a permission error even as root, because Docker restricts the process_vm_readv call py-spy uses. Start the container with --cap-add SYS_PTRACE (or cap_add in Compose) and restart it, or run py-spy on the host against the container's process.
Kubernetes drops SYS_PTRACE by default as well. Add it under the container's securityContext (changing a Deployment re-creates its pods), or attach an ephemeral debug container with kubectl debug.
docker run --cap-add SYS_PTRACE -it my-image
kubectl debug --profile=general -n <namespace> --target=<container> <pod> --image=python:3.12-slim -it -- bashsecurityContext:
capabilities:
add:
- SYS_PTRACEPair a profile with a progress bar
py-spy shows which functions burn CPU or wait, but not which step a long training run or batch job is on, or when it will finish. A progress bar answers that, and you can use both on the same job.
FAQ
Does py-spy slow down the program it profiles?
Very little. py-spy runs in a separate process and reads memory from the outside without instrumenting your code. By default it briefly pauses the target for each sample, 100 times per second; pass --nonblocking to never pause it, at the cost of occasional sampling errors.
Why does py-spy say Permission Denied inside Docker even as root?
Docker restricts the process_vm_readv system call that py-spy uses to read another process's memory. Add --cap-add SYS_PTRACE to docker run (or cap_add in Compose) and restart the container, or run py-spy as root on the host instead.
Which Python versions does py-spy support?
py-spy 0.4.2 supports CPython 2.3 to 2.7 and 3.3 to 3.14, on Linux, macOS, Windows and FreeBSD.
Can py-spy profile C extensions and multiprocessing workers?
Yes. Pass --native to include stacks from C, C++ and Cython extensions, and --subprocesses to profile child processes started by multiprocessing or gunicorn worker pools.
How is py-spy different from cProfile?
cProfile is the deterministic profiler in the Python standard library, and you start your script with it, for example python -m cProfile script.py, because it runs inside the process from the beginning. py-spy is an external sampling profiler that can attach to a process that is already running.
Check on long runs from your phone
Change one import and every tqdm bar prints a link you can open on any device. Get a Discord or Slack message when the run finishes, fails or stalls.
pip install tqdm-liveSources
- py-spy README github.com
- py-spy releases on GitHub github.com
- py-spy on PyPI pypi.org
- Python docs: The Python Profilers docs.python.org
- Kubernetes docs: kubectl debug kubernetes.io