Aller au contenu

Commandes py-spy

py-spy est un profileur par échantillonnage pour programmes Python écrit en Rust. Il peut profiler des processus Python en cours d’exécution sans modifier le code ni les redémarrer, produisant des flame graphs et des vues top en temps réel avec un overhead minimal.

Installation

Linux/Ubuntu

# Install via pip
pip install py-spy

# Install via cargo (Rust)
cargo install py-spy

# Install via package manager (Arch Linux)
pacman -S py-spy

# Verify installation
py-spy --version

# py-spy requires root to attach to processes you don't own
# Or set ptrace permissions:
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope

py-spy record — Générer des profils

# Enregistrer un flame graph SVG depuis un script
py-spy record -o profile.svg -- python my_script.py

# Enregistrer depuis un processus en cours par PID
sudo py-spy record -o profile.svg --pid 1234

# Enregistrer pendant une durée spécifique (secondes)
sudo py-spy record -o profile.svg --pid 1234 --duration 60

# Définir le taux d'échantillonnage (défaut 100 Hz)
py-spy record -o profile.svg --rate 200 -- python my_script.py

# Enregistrer uniquement les threads inactifs (en attente/dormants)
py-spy record -o profile.svg --idle --pid 1234

# Enregistrer en incluant les cadres natifs d'extensions C/C++
py-spy record -o profile.svg --native -- python my_script.py

# Enregistrer les sous-processus (suivre les forks)
py-spy record -o profile.svg --subprocesses -- python my_script.py

# Enregistrer uniquement les threads détenant le GIL
py-spy record -o profile.svg --gil -- python my_script.py

# Enregistrer avec les numéros de ligne des fonctions
py-spy record -o profile.svg --function -- python my_script.py

# Mode non bloquant (peut manquer certains échantillons)
py-spy record -o profile.svg --nonblocking --pid 1234

Formats de sortie

# Flame graph SVG (défaut)
py-spy record -o profile.svg --format flamegraph -- python my_script.py

# Format speedscope (voir sur https://speedscope.app)
py-spy record -o profile.speedscope.json --format speedscope -- python my_script.py

# Sortie brute des échantillons
py-spy record -o profile.raw --format raw -- python my_script.py

# Format Chrome trace (voir dans chrome://tracing)
py-spy record -o profile.json --format chrometrace -- python my_script.py

# Format pprof (compatible avec les outils Go pprof)
py-spy record -o profile.pb.gz --format pprof -- python my_script.py

py-spy top — Profilage en direct

# Vue top en direct d'un processus en cours
sudo py-spy top --pid 1234

# Vue en direct d'un script
py-spy top -- python my_script.py

# Inclure les cadres natifs dans la vue top
sudo py-spy top --native --pid 1234

# Afficher les threads inactifs
sudo py-spy top --idle --pid 1234

# Inclure les sous-processus
py-spy top --subprocesses -- python my_script.py

# Taux d'échantillonnage personnalisé
sudo py-spy top --rate 50 --pid 1234

py-spy dump — Dump des threads

# Dumper les traces de pile actuelles de tous les threads
sudo py-spy dump --pid 1234

# Dumper avec les variables locales
sudo py-spy dump --locals --pid 1234

# Dumper en format JSON
sudo py-spy dump --json --pid 1234

# Dumper en incluant les cadres natifs
sudo py-spy dump --native --pid 1234

Profilage de scénarios spécifiques

Applications web

# Profiler une application Django
py-spy record -o django_profile.svg -- python manage.py runserver

# Profiler une application Flask
py-spy record -o flask_profile.svg -- python app.py

# Profiler un worker Gunicorn (s'attacher au PID du worker spécifique)
sudo py-spy record -o gunicorn_profile.svg --pid $(pgrep -f 'gunicorn.*worker')

# Profiler uvicorn (ASGI)
py-spy record -o uvicorn_profile.svg --subprocesses -- uvicorn main:app --workers 4

Traitement de données

# Profiler une charge de travail pandas/numpy avec extensions natives
py-spy record -o data_profile.svg --native -- python etl_pipeline.py

# Profiler avec sous-processus (multiprocessing)
py-spy record -o multiproc_profile.svg --subprocesses -- python parallel_job.py

Services longue durée

# S'attacher à un démon en cours d'exécution pendant 5 minutes
sudo py-spy record -o daemon_profile.svg --pid 1234 --duration 300

# Profilage périodique via cron (instantanés de 30 secondes)
# Add to crontab: */5 * * * * sudo py-spy record -o /tmp/profile_$(date +\%s).svg --pid $(cat /var/run/myapp.pid) --duration 30

Docker et conteneurs

# Profiler un processus Python dans un conteneur Docker
# 1. Trouver le PID du conteneur sur l'hôte
CONTAINER_PID=$(docker inspect --format '{{.State.Pid}}' my_container)

# 2. Attacher py-spy au namespace PID du conteneur
sudo py-spy record -o container_profile.svg --pid $CONTAINER_PID

# Ou exécuter py-spy à l'intérieur du conteneur
docker exec -it my_container py-spy top --pid 1

# Docker run avec la capacité SYS_PTRACE pour py-spy
docker run --cap-add SYS_PTRACE my_image

Kubernetes

# Profiler un pod Python
# 1. Trouver le nœud et le PID
kubectl exec -it my-pod -- pgrep -f python

# 2. Utiliser kubectl exec avec py-spy installé dans le conteneur
kubectl exec -it my-pod -- py-spy record -o /tmp/profile.svg --pid 1 --duration 30

# 3. Copier le profil
kubectl cp my-pod:/tmp/profile.svg ./profile.svg

# Pour les conteneurs de débogage éphémères
kubectl debug -it my-pod --image=py-spy-debug --target=my-container -- py-spy top --pid 1

Interprétation de la sortie

Flame Graph (SVG)

# Lecture du flame graph :
# - Axe X : proportion du temps CPU total (plus large = plus de CPU)
# - Axe Y : profondeur de la pile (bas = point d'entrée, haut = fonction feuille)
# - Chaque boîte : un appel de fonction
# - Clic pour zoomer, Ctrl+F pour chercher
# - La couleur est aléatoire (non significative par défaut)

Colonnes de la vue Top

# Explication des colonnes dans py-spy top :
# %Own    — Temps CPU dans cette fonction uniquement (temps propre)
# %Total  — Temps CPU dans cette fonction + appelés
# OwnTime — Temps propre absolu
# TotalTime — Temps total absolu
# Function — Module et nom de fonction
# Line    — Fichier source et numéro de ligne

Combinaison avec d’autres outils

# Générer des piles pliées pour les outils FlameGraph de Brendan Gregg
py-spy record -o profile.raw --format raw -- python my_script.py
# Le format raw est déjà compatible avec les piles pliées

# Convertir en speedscope pour une analyse interactive
py-spy record -o profile.speedscope.json --format speedscope -- python my_script.py
# Ouvrir sur https://speedscope.app

# Convertir la sortie pprof pour utilisation avec go tool pprof
py-spy record -o profile.pb.gz --format pprof -- python my_script.py
go tool pprof -http=:8080 profile.pb.gz

Dépannage

# Permission refusée — root ou accès ptrace nécessaire
sudo py-spy top --pid 1234
# Or: echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope

# "Error: Failed to find python version" — spécifier le chemin Python
sudo py-spy record --pid 1234 --python-program /usr/bin/python3

# Symboles manquants en mode natif — installer les paquets de débogage
sudo apt install python3-dbg

# Processus non trouvé dans le conteneur — utiliser le PID hôte
docker inspect --format '{{.State.Pid}}' my_container

Référence rapide

CommandeFonction
py-spy recordGénérer un flame graph ou fichier de profil
py-spy topVue de profilage CPU en direct type top
py-spy dumpDump ponctuel des piles de threads
--pid PIDS’attacher à un processus en cours
--nativeInclure les cadres d’extensions C/C++
--subprocessesSuivre les processus enfants
--gilProfiler uniquement les threads détenant le GIL
--rate NDéfinir la fréquence d’échantillonnage (Hz)
--formatFormat de sortie (flamegraph, speedscope, raw, chrometrace, pprof)