mrkeyoor.com_
Sat 08 Aug 21:01 UTC
PyPIInfraupdated 08 Aug 2026

py-spy

py-spy is a Rust sampling profiler that reads CPython interpreter state from outside the target process. It can attach to a running PID or launch a command, then show a live top view, dump every thread's current stack, or record flamegraph, speedscope, raw, and Chrome trace files. Because no profiling code is imported into the application, it is especially useful for production diagnosis and hung processes. Low overhead does not mean zero operational risk: process-memory permissions and brief sampling pauses still matter.

Verdict

Keep it in the incident-response toolbox for live CPython processes; few profilers get useful evidence with so little application cooperation. Do not normalize permanent elevated privileges just to make attachment convenient.

API stability5/5The command model remains small and memorable: record, top, and dump accept either a PID or a launched Python command, with additive flags for output formats, sampling, subprocesses, native frames, GIL state, idle threads, and blocking behavior. Version 0.4.2 adds interpreter and option support without changing the core workflow or requiring application integration.
Docs5/5The README explains every main mode and spends most of its length on the failures that matter in practice: memory-reading design, sudo and ptrace rules, native-platform coverage, subprocesses, idle and GIL detection, macOS SIP, Docker, Kubernetes, Alpine, and nonblocking tradeoffs. Examples are copyable, and limitations are stated directly instead of hidden behind generic profiler claims.
Maintenance5/5PyPI released 0.4.2 in April 2026, GitHub shows an August 2026 push, the repository is not archived, and current source includes CPython 3.14 bindings and recent fixes. Maintaining an external profiler requires following interpreter memory-layout changes and operating-system APIs, and the project demonstrates that work rather than relying only on an old stable binary.
Ecosystem4/5Prebuilt PyPI wheels, GitHub binaries, Cargo, Homebrew, Arch, Alpine, flamegraph SVG, speedscope, raw, and Chrome trace outputs make it fit many debugging workflows. Its broad adoption and production reputation are strong. The limits are inherent: it is CPython-focused, privilege-sensitive, and not an APM integration, memory profiler, or deterministic call tracer.

Use it if

  • You need to profile a running Python service without changing code or restarting it
  • You want an immediate flamegraph, live top view, or one-shot thread dump from a PID
  • You need to include worker subprocesses, GIL-only samples, thread IDs, or supported native extension frames
  • You are diagnosing production CPU use and an in-process deterministic profiler would distort the workload
Skip it if

Setup reality

pip install py-spy normally installs a prebuilt binary wheel; the tool runs as the py-spy command and does not need to be imported into the target program. Version 0.4.2 supports recent CPython versions through 3.14 according to the README, but interpreter support and availability of a wheel are separate questions. Cargo installation builds from Rust source and on Linux or Windows requires libunwind development files. Alpine Python does not accept ordinary manylinux wheels, so upstream points to an Alpine testing package or a musl release binary rather than a normal pip path. Attaching is where setup gets operational. py-spy reads another process's memory, so Linux commonly blocks an existing PID under ptrace_scope unless the profiler has matching ownership and permission or runs with elevated privileges. Launching the target as a child of py-spy often avoids that restriction. macOS generally requires root, and System Integrity Protection can prevent profiling the system Python under /usr/bin even as root. Docker's default seccomp and capabilities block the memory-reading call; start the container with SYS_PTRACE or profile from a suitably privileged host. Kubernetes also drops SYS_PTRACE, so adding that capability changes the pod security profile and should go through the cluster's security review. Profile files can reveal function names, paths, process command lines, thread names, and, with dump --locals, application values. Treat them as potentially sensitive artifacts. By default py-spy pauses the process briefly while collecting each sample to avoid inconsistent stack reads. --nonblocking removes that pause but can increase sampling errors and partial frames, and source validation forbids combining it with --native. Sampling defaults to a rate configured by the tool; higher rates increase observation overhead and file volume, so set a bounded duration in production. Native profiling is available only on supported architectures, works best with debug symbols, and Cython line mapping needs generated C or C++ files. --gil intentionally omits extension work that releases the GIL, while --idle does the opposite of its name's common interpretation by including threads py-spy would otherwise classify as idle. For gunicorn or multiprocessing, add --subprocesses from the start so newly created children are followed. Always reproduce a hot path more than once before treating sample proportions as a precise benchmark.

Patterns

Record a flamegraph from a PIDrecord-running-process

py-spy record --pid 12345 --duration 30 --output profile.svg

Attaching to an existing process commonly needs ptrace permission on Linux and elevated privileges on macOS.

Launch and profile a Python commandprofile-new-command

py-spy record --output profile.svg -- python -m myservice.worker

Launching the process as py-spy's child often avoids the permission required to attach to an unrelated PID.

Watch hot functions livewatch-live-top

py-spy top --pid 12345 --delay 1.0

top is interactive and continuously samples until stopped; use record when you need an artifact for later comparison.

Capture all current thread stacksdump-thread-stacks

py-spy dump --pid 12345

A dump is a single snapshot, which is ideal for a hang but can miss intermittent CPU hotspots.

Include local variables in a stack dumpdump-local-values

py-spy dump --pid 12345 --locals

Local variables can contain tokens, personal data, queries, or message bodies; protect and delete the output according to incident policy.

Create a speedscope profilerecord-speedscope

py-spy record --pid 12345 --duration 60 --format speedscope --output profile.json

Open the resulting JSON in speedscope; choose flamegraph when a self-contained SVG is easier to share.

Follow worker subprocessesprofile-subprocesses

py-spy record --pid 12345 --subprocesses --duration 60 --output workers.svg

This is useful for multiprocessing and gunicorn; recorded stacks include PID and command information to distinguish workers.

Include supported native extension framesprofile-native-code

py-spy record --pid 12345 --native --duration 30 --output native.svg

Native stacks are platform-limited, work best with symbols, and cannot be combined with --nonblocking.

Sample only threads holding the GILisolate-gil-work

py-spy record --pid 12345 --gil --duration 30 --output gil.svg

This can clarify Python CPU contention but omits active extension code that releases the GIL.

Use nonblocking samplingminimize-sampling-pauses

py-spy record --pid 12345 --nonblocking --duration 30 --output nonblocking.svg

Nonblocking mode avoids pausing the target but can produce more sampling errors or partial stacks, so compare with a normal recording.

Alternatives

PackageRegistryPick it when
scalenePyPIYou want line-level CPU, memory, and copy profiling and can run the program under the profiler
viztracerPyPIYou need detailed execution timelines, function events, and an interactive trace viewer
pyinstrumentPyPIYou want a friendly in-process statistical profiler for scripts, web requests, or notebooks