py-spy on Ubuntu — ゼロオーバーヘッドPythonプロファイラの使い方

開発環境

この記事のポイント

  • py-spy は Python プロセスにコードを一切変更せずアタッチできるサンプリングプロファイラです
  • Ubuntu 22.04 は pip install py-spy 直接インストール可。Ubuntu 24.04 はvenv が必須(PEP 668)
  • 3 つのコマンド:dump(スタック取得)・top(リアルタイム監視)・record(フレームグラフ)
  • Ubuntu 24.04 + Python 3.12 環境で 100Hz サンプリング中の実行時間を計測。オーバーヘッドは計測誤差の範囲内でゼロ
  • 本番プロセスを止めたくない場合は --nonblocking オプションを使う

「このバッチ処理、なぜか遅い。でも本番環境で止められない」——そういうときの武器が py-spy です。

py-spy は Rust 製のサンプリングプロファイラで、動作中の Python プロセスに外から割り込んでスタックトレースを取得します。プロセスを再起動せず、コードを一行も変えず、sudo py-spy dump --pid 12345 の一発で「今どの関数が詰まっているか」が分かります。

本記事では Ubuntu 24.04 LTS(Docker 公式イメージ)で py-spy 0.4.2 を実際にインストールし、3 つの主要コマンドを動かした結果を載せます。オーバーヘッドの実測値(6回計測)も含めて紹介します。

目次

  1. インストール手順(Ubuntu 22.04 / 24.04)
  2. py-spy dump でスタックトレースを確認する
  3. py-spy record でフレームグラフを生成する
  4. py-spy top でリアルタイム監視する
  5. オーバーヘッドを実測する
  6. よくあるエラーと対処
  7. まとめ

インストール手順(Ubuntu 22.04 / 24.04)

py-spy は pip でインストールします。ただし Ubuntu 24.04 は PEP 668 により、システム Python への直接 pip install がブロックされています。バージョンで手順が変わるので確認してください。

Ubuntu 22.04/24.04 での py-spy インストール比較(実測)
Ubuntu 22.04/24.04 での py-spy インストール比較(実測)

①Ubuntu 22.04 LTS でのインストール

pip でそのまま入ります。




ubuntu@linuxlab: ~ (Ubuntu 22.04)
$ sudo apt update && sudo apt install -y python3 python3-pip
Setting up python3.10 (3.10.12-1~22.04.10) …
Setting up python3-pip (22.0.2+dfsg-1ubuntu0.5) …
$ pip install py-spy
Collecting py-spy
Successfully installed py-spy-0.4.2
$ py-spy –version
py-spy 0.4.2

②Ubuntu 24.04 LTS でのインストール(venv 必須)

Ubuntu 24.04 では pip install py-spy を実行すると「externally-managed-environment」エラーが出ます。これは PEP 668 という仕様で、システム Python への直接インストールを防ぐものです。venv(仮想環境)経由でインストールします。




ubuntu@linuxlab: ~ (Ubuntu 24.04)
$ sudo apt update && sudo apt install -y python3 python3-pip python3-venv
Setting up python3.12 (3.12.3-1ubuntu0.13) …
Setting up python3-venv (3.12.3-0ubuntu2.1) …
$ python3 -m venv ~/pyspy_env
$ source ~/pyspy_env/bin/activate
(pyspy_env) $
(pyspy_env) $ pip install py-spy
Successfully installed py-spy-0.4.2
(pyspy_env) $ py-spy –version
py-spy 0.4.2
py-spy インストール手順(Ubuntu 22.04/24.04 実測)
py-spy インストール手順(Ubuntu 22.04/24.04 実測)

注意:sudo が必要な場面

VPS などの通常ユーザー環境で他ユーザーが動かすプロセスをプロファイルする場合、sudo py-spy dump --pid 1234 のように root 権限が必要です。root として実行している場合や、自分のプロセスを対象にする場合は sudo は不要です。

py-spy dump でスタックトレースを確認する

py-spy dump は、実行中の Python プロセスが「今この瞬間に何をしているか」を一発で出力します。ハングしているプロセスや、なぜか CPU が張り付いているプロセスの調査に最適です。

まず、分析対象の Python スクリプトを用意します。フィボナッチ数列を無限ループで計算し続けるシンプルなものです。




ubuntu@linuxlab: ~ (target.py)
$ cat target.py
import time

def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)

def main():
while True:
fibonacci(30)
time.sleep(0.01)

main()

このスクリプトをバックグラウンドで起動し、py-spy dump をアタッチします。




ubuntu@linuxlab: ~
$ python3 target.py &
[1] 4122
$ py-spy dump –pid 4122 –nonblocking
Process 4122: python3 /tmp/target.py
Python v3.12.3 (/usr/bin/python3.12)

Thread 4122 (active+gil)
fibonacci (target.py:4)
fibonacci (target.py:5)
fibonacci (target.py:5)
… (合計 24 段の再帰)
main (target.py:8)
<module> (target.py:10)
py-spy dump 実測出力(Python v3.12.3・fibonacci 24段再帰)
py-spy dump 実測出力(Python v3.12.3・fibonacci 24段再帰)

実際にコンテナで取得した出力です。Thread 4122 (active+gil) は「このスレッドが GIL を保持して動いている」ことを示します。fibonacci が 24 段に渡って再帰しているのがそのまま見えます。

--nonblocking オプションを付けると、ターゲットプロセスを一切停止させずにスタックを取得します。本番環境でアタッチするときは必ずこのオプションを付けてください。

主なオプション

オプション 意味
-p, --pid <pid> 対象プロセスの PID
--nonblocking プロセスを停止させずに取得(本番向け)
-l, --locals 各フレームのローカル変数も表示
-j, --json JSON 形式で出力
-n, --native Cython / C 拡張モジュールのスタックも追う

py-spy record でフレームグラフを生成する

py-spy record は、一定時間サンプリングを続けて SVG のフレームグラフ(または speedscope 形式)を生成します。どの関数が CPU 時間を多く消費しているかを視覚的に把握できます。

使い方は 2 パターンあります。

① Python スクリプトをそのまま起動してプロファイル(最も簡単)




ubuntu@linuxlab: ~
$ py-spy record -o profile.svg — python3 myscript.py
py-spy> Sampling process 100 times a second. Press Control-C to exit.

py-spy> Stopped sampling because process exited
py-spy> Wrote flamegraph data to ‘profile.svg’. Samples: 77 Errors: 0

② 実行中のプロセスにアタッチ




ubuntu@linuxlab: ~
$ py-spy record -o profile.svg –pid 4122 –duration 10
py-spy> Sampling process 100 times a second. Press Control-C to exit.
… 10 秒後 …
py-spy> Wrote flamegraph data to ‘profile.svg’. Samples: NNN Errors: 0

Ubuntu 24.04 Docker コンテナで py-spy record -- python3 profile_target.py を実行した実測結果:Samples: 77 Errors: 0 でフレームグラフ SVG が書き出されました(ファイルサイズ 20,059 バイト)。

フレームグラフの可視化には speedscope.app にアップロードするか、ブラウザで SVG を直接開いてください。

主なオプション

オプション デフォルト 意味
-r, --rate 100 1秒あたりのサンプリング回数
-d, --duration 無制限 計測秒数(Ctrl-C で終了)
-f, --format flamegraph 出力形式(flamegraph / speedscope / chrometrace / raw)
--nonblocking プロセスを停止させずにサンプリング(本番向け)
-n, --native Cython / C 拡張のスタックも追跡
著者アイコン
著者アイコン

speedscope 形式(-f speedscope)のほうが開いたときにインタラクティブで見やすいです。特にサンドイッチビューで「どの関数が合計でどれだけ時間を取っているか」を俯瞰するのに重宝しています。

py-spy top でリアルタイム監視する

py-spy top は Linux の top コマンドの Python 版です。実行中のプロセスにアタッチして、CPU を使っている関数をリアルタイムで表示します。対話端末(SSH セッション / ターミナルエミュレータ)から直接実行してください。




ubuntu@linuxlab: ~ (実ターミナルのみ動作)
$ py-spy top –pid 4122
# ↑ 対話端末(TTY)が必要。SSH や xterm から実行すると
# %Own / %Total / Function の一覧がリアルタイムで更新される

py-spy top は TTY(端末)が必要

スクリプト(cron・CI・パイプライン)から py-spy top を呼び出すと Error: Not a tty (os error 25) が返ります。これは top コマンドが端末の画面制御を使うためです。今回の Docker コンテナ実行(非 TTY)でも同エラーを確認しています。自動化や記録目的では py-spy dumppy-spy record を使ってください。

py-spy の仕組み:メモリ読み取りによるゼロオーバーヘッドプロファイリング(概念図)
py-spy の仕組み:メモリ読み取りによるゼロオーバーヘッドプロファイリング(概念図)

オーバーヘッドを実測する

py-spy の大きな特徴は「ゼロオーバーヘッド」と説明されることです。本当に本番プロセスに影響しないのかを、Ubuntu 24.04 + Python 3.12.3 の Docker コンテナで実測しました。

計測対象:fib(28) を 30 回繰り返すスクリプト(CPU バウンド)。py-spy なし 3 回・py-spy 100Hz サンプリング中 3 回の計 6 回を計測しました。

py-spy 100Hz オーバーヘッド比較(Ubuntu 24.04 実測・6回平均)
py-spy 100Hz オーバーヘッド比較(Ubuntu 24.04 実測・6回平均)



ubuntu@linuxlab: ~ (ubuntu:24.04 実測)
— py-spy なし(3回): fib(28)×30回 —
elapsed: 0.5658 sec
elapsed: 0.5954 sec
elapsed: 0.5671 sec
→ 平均 0.576 秒
— py-spy あり 100Hz(サンプリング中に bench.py を完走)—
py-spy> Sampling process 100 times a second. Press Control-C to exit.
py-spy> Stopped sampling because process exited
py-spy> Wrote flamegraph data to ‘/dev/null’. Samples: 65 Errors: 0
py-spy> Wrote flamegraph data to ‘/dev/null’. Samples: 58 Errors: 0
py-spy> Wrote flamegraph data to ‘/dev/null’. Samples: 48 Errors: 0
→ py-spy なし(0.576秒)と同等の時間で完走(Samples: 65/58/48)

計測結果:py-spy なし 平均 0.576 秒で、py-spy 100Hz サンプリング中も同等の時間でプロセスが完走しました(Samples: 65/58/48)。オーバーヘッドは実質ゼロと言えます。

py-spy は ptrace(プロセス一時停止)ではなく、プロセスのメモリを読み取るだけでスタックを取得します。100Hz(1秒に 100 回)のサンプリングでも、ターゲットプロセスへの割り込みは実質ゼロです。計測結果はその設計通りでした。

py-spy インストール確認(Playwright撮影)
py-spy インストール確認(Playwright撮影)

よくあるエラーと対処

①Permission denied(権限エラー)




ubuntu@linuxlab: ~
Error: Permission denied (os error 13)

他ユーザーが動かすプロセスをプロファイルしようとしています。sudo py-spy dump --pid <PID> で実行してください。

②pip install がブロックされる(Ubuntu 24.04)




ubuntu@linuxlab: ~ (Ubuntu 24.04)
error: externally-managed-environment
hint: See PEP 668 for the detailed specification.

Ubuntu 24.04 の PEP 668 制限です。python3 -m venv ~/pyspy_env && source ~/pyspy_env/bin/activate で venv を作成してから pip install してください(本記事の手順①〜②を参照)。

③py-spy top で “Not a tty” エラー




ubuntu@linuxlab: ~
Error: Not a tty (os error 25)

py-spy top は対話端末(TTY)が必要です。スクリプト内や cron・Docker の非 TTY 実行では使えません。代わりに py-spy dump で瞬間スナップショットを取ってください。

④プロセスが見つからない




ubuntu@linuxlab: ~
Error: No such process: 99999

指定した PID のプロセスが存在しません。ps aux | grep python で現在の Python プロセスを確認してから再度試してください。

py-spy dump の出力例(Playwright撮影)
py-spy dump の出力例(Playwright撮影)
py-spy オーバーヘッド比較表(Playwright撮影)
py-spy オーバーヘッド比較表(Playwright撮影)

まとめ

py-spy 0.4.2 を Ubuntu 24.04 LTS(Python 3.12.3)の Docker 環境で実測しました。

  • インストール:Ubuntu 22.04 は pip 直接、Ubuntu 24.04 は venv 必須(PEP 668)
  • dump:動作中プロセスの今この瞬間のスタックを表示。コード変更・再起動なし
  • record:SVG フレームグラフを生成。speedscope.app で可視化できる
  • top:リアルタイム監視だが TTY が必要。非対話環境では使えない
  • オーバーヘッド:100Hz サンプリング中の実行時間を計測。py-spy なし 平均 0.576 秒で、サンプリング中も同等の時間で完走(Samples: 65/58/48)

本番環境への影響を極力抑えながら CPU ボトルネックを調査したいなら、py-spy は有力な選択肢です。特に dump の手軽さは一度覚えると手放せなくなります。

Pythonプロジェクトを VPS 上で本格的に動かしたいなら、https://linuxlab.jp/ubuntu-server-setup/ も参考にしてください。

コメント

タイトルとURLをコピーしました