Python アプリケーションのプロファイリング

このページでは、プロファイリング データを収集し、そのデータを Google Cloud プロジェクトに送信するように Python アプリケーションを変更する方法について説明します。プロファイリングの全般的な情報については、プロファイリングのコンセプトをご覧ください。

Python のプロファイル タイプ:

  • CPU 時間
  • 経過時間(メインスレッド)

サポートされている Python 言語バージョン:

  • Python 3.6 以降

サポートされているプロファイリング エージェントのバージョン:

  • エージェントの最新リリースがサポートされています。一般に、1 年を超える期間が経過したリリースはサポートされません。エージェントの最新リリース バージョンを使用することをおすすめします。

サポートされているオペレーティング システム:

  • Linux。Python アプリケーションのプロファイリングは、標準 C ライブラリが glibc または musl で実装されている Linux カーネルでサポートされています。Linux Alpine カーネルに固有の構成情報については、Linux Alpine で実行するをご覧ください。

サポートされる環境:

Profiler API を有効にする

プロファイリング エージェントを使用する前に、基盤となる Profiler API が有効になっている必要があります。Google Cloud CLI または Google Cloud Console のいずれかを使用して、API のステータスを確認し、必要に応じて有効にできます。

gcloud CLI

  1. ワークステーションに Google Cloud CLI がまだインストールされていない場合は、Google Cloud CLI のドキュメントをご覧ください。

  2. 次のコマンドを実行します。

    gcloud services enable cloudprofiler.googleapis.com
    

詳細については、gcloud services をご覧ください。

Google Cloud コンソール

  1. Enable the required API.

    Enable the API

  2. [API が有効です] が表示されている場合、API はすでに有効になっています。そうでない場合は、[有効にする] ボタンをクリックします。

サービス アカウントに IAM ロールを付与する

Google Cloud リソースにアプリケーションをデプロイし、デフォルトのサービス アカウントを使用していて、そのサービス アカウントへのロール付与を変更していない場合は、このセクションをスキップできます。

次のいずれかを行う場合は、サービス アカウントに Cloud Profiler エージェント(roles/cloudprofiler.agentの IAM ロールを付与する必要があります。

  1. デフォルトのサービス アカウントを使用しているものの、ロール付与を変更している。
  2. ユーザーが作成したサービス アカウントを使用している。
  3. Workload Identity を使用している場合は、Kubernetes サービス アカウントに Cloud Profiler エージェントのロールを付与します。

サービス アカウントに IAM ロールを付与するには、Google Cloud コンソールまたは Google Cloud CLI を使用します。たとえば、gcloud projects add-iam-policy-binding コマンドを使用できます。

gcloud projects add-iam-policy-binding GCP_PROJECT_ID \
    --member serviceAccount:MY_SVC_ACCT_ID@GCP_PROJECT_ID.iam.gserviceaccount.com \
    --role roles/cloudprofiler.agent

前のコマンドを使用する前に、次のように置き換えます。

  • GCP_PROJECT_ID: プロジェクト ID。
  • MY_SVC_ACCT_ID: サービス アカウントの名前。

詳細については、プロジェクト、フォルダ、組織へのアクセスを管理するをご覧ください。

Cloud Profiler の使用

Python の使用に関するおすすめの方法については、Python 開発環境の設定をご覧ください。

Compute Engine

Compute Engine の場合は、次の操作を行います。

  1. C/C++ コンパイラとデベロッパー ツールをインストールします。

    sudo apt-get install -y build-essential
    
  2. pip をインストールします。

    sudo apt-get install -y python3-pip
    
  3. Profiler パッケージをインストールします。

    pip3 install google-cloud-profiler
    
  4. 初期化コードのできるだけ早い段階で googlecloudprofiler モジュールをインポートし、googlecloudprofiler.start 関数を呼び出します。

    import googlecloudprofiler
    
    
    def main():
        # Profiler initialization. It starts a daemon thread which continuously
        # collects and uploads profiles. Best done as early as possible.
        try:
            googlecloudprofiler.start(
                service="hello-profiler",
                service_version="1.0.1",
                # verbose is the logging level. 0-error, 1-warning, 2-info,
                # 3-debug. It defaults to 0 (error) if not set.
                verbose=3,
                # project_id must be set if not running on GCP.
                # project_id='my-project-id',
            )
        except (ValueError, NotImplementedError) as exc:
            print(exc)  # Handle errors here

    start 関数に service パラメータを指定する必要があります。Profiler インターフェースでアプリケーションのバージョンをフィルタするには、service_version パラメータを指定します。トラブルシューティングと例外の詳細については、トラブルシューティングをご覧ください。

GKE

GKE の場合は、次の操作を行います。

  1. Dockerfile を変更して Profiler パッケージをインストールします。

    FROM python:3
    ...
    RUN apt-get update && apt-get install -y build-essential python3-pip
    RUN pip3 install google-cloud-profiler
    
  2. 初期化コードのできるだけ早い段階で googlecloudprofiler モジュールをインポートし、googlecloudprofiler.start 関数を呼び出します。

    import googlecloudprofiler
    
    
    def main():
        # Profiler initialization. It starts a daemon thread which continuously
        # collects and uploads profiles. Best done as early as possible.
        try:
            googlecloudprofiler.start(
                service="hello-profiler",
                service_version="1.0.1",
                # verbose is the logging level. 0-error, 1-warning, 2-info,
                # 3-debug. It defaults to 0 (error) if not set.
                verbose=3,
                # project_id must be set if not running on GCP.
                # project_id='my-project-id',
            )
        except (ValueError, NotImplementedError) as exc:
            print(exc)  # Handle errors here

    start 関数に service パラメータを指定する必要があります。Profiler インターフェースでアプリケーションのバージョンをフィルタするには、service_version パラメータを指定します。トラブルシューティングと例外の詳細については、トラブルシューティングをご覧ください。

フレキシブル環境

App Engine フレキシブル環境の場合は、次の操作を行います。

  1. requirements.txt ファイルに google-cloud-profiler を追加します。

  2. 初期化コードのできるだけ早い段階で googlecloudprofiler モジュールをインポートし、googlecloudprofiler.start 関数を呼び出します。

App Engine の場合、serviceservice_version はオペレーティング環境から取得されます。トラブルシューティングと例外の詳細については、トラブルシューティングをご覧ください。

スタンダード環境

App Engine スタンダード環境の場合は、Python 3 ランタイム環境を使用する必要があります。次の操作を行います。

  1. requirements.txt ファイルに google-cloud-profiler を追加します。

  2. 初期化コードのできるだけ早い段階で googlecloudprofiler モジュールをインポートし、googlecloudprofiler.start 関数を呼び出します。

App Engine の場合、serviceservice_version はオペレーティング環境から取得されます。トラブルシューティングと例外の詳細については、トラブルシューティングをご覧ください。

start 関数

googlecloudprofiler.start 関数は、プロファイルを継続的に収集してアップロードするデーモン スレッドを作成します。アプリケーションのできるだけ早い段階で start を 1 回呼び出す必要があります。

パラメータ 説明
service1 (必須)プロファイリングされるサービスの名前。サービス名の制限については、サービス名とバージョンの引数をご覧ください。
service_version1 (省略可)プロファイリングされるサービスのバージョン。サービスのバージョンに関する制限については、サービス名とバージョンの引数をご覧ください。
verbose (省略可)ロギングレベル。ロギングレベルの詳細については、エージェント ロギングをご覧ください。

デフォルト値は 0(エラー)です。
project_id2 (省略可)Google Cloud プロジェクト ID。
disable_cpu_profiling (省略可)CPU プロファイリングを無効にするには、disable_cpu_profiling=True を設定します。

このパラメータは、Python バージョン 3.2 以降でのみサポートされます。他のバージョンの Python の場合、CPU プロファイリングはサポートされていないため、このパラメータは無視されます。

デフォルト値は False です。
disable_wall_profiling (省略可)経過時間プロファイリングを無効にするには、disable_wall_profiling=True を設定します。

このパラメータは Python 3.6 以降でサポートされます。他のバージョンの Python の場合、経過時間プロファイリングはサポートされていないため、このパラメータは無視されます。

経過時間プロファイリングが有効な場合の start 関数の制限については、制限事項をご覧ください。

デフォルト値は False です。

1 Compute Engine と GKE のみ。App Engine の場合、値は環境から取得されます。
2 Google Cloud の場合、値は環境から取得されます。Google Cloud 以外の環境では、値を指定する必要があります。詳細については、Google Cloud の外部で実行されているアプリケーションのプロファイリングをご覧ください。

データの分析

Profiler が収集したデータを Profiler のインターフェースに表示し、分析できます。

Google Cloud コンソールで [Profiler] ページに移動します。

[Profiler] に移動

このページは、検索バーを使用して見つけることもできます。

サービス名とバージョンの引数

Profiler エージェントを読み込むときに、service-name 引数とオプションの service-version 引数を指定して構成します。

Profiler は、サービス名で指定されたサービスのすべてのレプリカからプロファイリング データを収集します。Profiler サービスは、サービス バージョンとゾーンの組み合わせに対して、1 つのサービス名について平均で 1 分あたり 1 個のプロファイルを作成します。

たとえば、1 つのサービスの 2 つのバージョンが 3 つのゾーンのレプリカで実行されている場合、Profiler はそのサービスについて 1 分あたり平均で 6 個のプロファイルを作成します。

レプリカで異なるサービス名を使用している場合、サービスが必要以上にプロファイリングされるため、オーバーヘッドが大きくなります。

サービス名を選択する場合は、次の点に注意してください。

  • アプリケーション アーキテクチャでサービスを明確に識別できる名前を選択してください。1 つのサービスまたはアプリケーションしか実行していない場合、どのようなサービス名を選択するかはさほど問題になりません。しかし、アプリケーションが一連のマイクロサービスとして実行されている場合は、名前の選択が重要になります。

  • service-name 文字列で、プロセス ID などのプロセス固有の値を使用しないでください。

  • service-name 文字列は次の正規表現と一致する必要があります。

    ^[a-z0-9]([-a-z0-9_.]{0,253}[a-z0-9])?$

サービス名として imageproc-service のような静的な文字列を使用することをおすすめします。

サービス バージョンは省略可能です。サービス バージョンを指定すると、複数のインスタンスからプロファイリング情報が集約され、バージョンごとに表示されます。これは、複数のバージョンがデプロイされているときの識別に役立ちます。Profiler UI では、データをサービス バージョンでフィルタできます。これにより、コードの新旧バージョンのパフォーマンスを比較できます。

service-version 引数には、自由形式の文字列を指定できますが、通常は、バージョン番号のような値を使用します(例: 1.0.02.1.2)。

エージェント ロギング

デフォルトでは、プロファイリング エージェントは重大度が error のメッセージをログに記録します。より低い重大度のメッセージをログに記録するようにエージェントを構成するには、エージェントの起動時に verbose パラメータを指定します。verbose に指定できる値は次の 4 つです。

  • 0 : エラー
  • 1 : 警告
  • 2 : 情報
  • 3 : デバッグ

start の呼び出しで verbose パラメータを 1 に設定すると、重大度が Warning または Error のメッセージがログに記録され、InformationalDebug メッセージは無視されます。

すべてのメッセージをログに記録するには、エージェントの起動時に verbose3 に設定します。

googlecloudprofiler.start(service='service_name', verbose=3)

トラブルシューティング

このセクションでは、Python アプリケーションのプロファイリングに固有の制限事項、例外、既知の問題について説明します。一般的な問題のヘルプについては、トラブルシューティングをご覧ください。

制限事項

プロファイルの種類 制限事項
経過時間
  • メインスレッド プロファイリングのみ。
  • プロファイルの start 関数はメインスレッドから呼び出す必要があります。
  • Profiler シグナル ハンドラはメインスレッドでのみ実行されます。メインスレッドが実行できない場合、プロファイリング データは収集されません。

例外

エラー 原因 解決策
startNotImplementedError がスローされる アプリケーションが Linux 以外の環境で実行されています。
  • アプリケーションを Linux 環境で実行します。
startValueError がスローされる start 関数の引数が無効です。環境変数と引数から必要な情報が特定できません。CPU プロファイリングと経過時間プロファイリングの両方が無効になっているときに、プロファイリングが実行されました。
  • サービス名とバージョンが、サービス名とバージョンの引数で定義されている要件を満たしていることを確認します。
  • 経過時間プロファイリングが有効になっている場合は、start がメインスレッドから呼び出されるようにします。
  • サポートされているバージョンの Python を使用し、CPU プロファイリングまたは経過時間プロファイリングが有効になっていることを確認します。詳細については、start 関数をご覧ください。
  • Google Cloud の外部で実行している場合は、project_id パラメータを start に指定していることを確認します。詳細については、start 関数をご覧ください。

既知の問題

動作 原因 解決策
プロファイル データがまったく存在しない、または新しいプロファイル タイプを有効にしているものの、プロファイル データが欠落しています。 一般的な原因は構成に関連しています。 トラブルシューティングをご覧ください。
uWSGI を使用しているものの、一部のプロセスの CPU 時間と経過時間プロファイルのデータが存在しません。

uWSGI が複数のワーカーを使用してリクエストを処理する場合、デフォルトの動作では、プライマリ(「マスター」)プロセスでのみアプリケーションの初期化が行われます。フォークしたプロセスでは、初期化シーケンスは行われません。

アプリケーションの初期化シーケンス(Django アプリケーションの AppConfig.ready() 内など)でプロファイリング エージェントを構成すると、その結果として、フォークしたプロセスではプロファイリング エージェントは構成されません。

すべてのワーカー プロセスでアプリケーションの初期化を行うには、lazy-apps フラグを true に設定します。

関連する問題については、この表の次のトピックをご覧ください。

uWSGIを使用していて、経過時間のプロファイル データはありませんが、CPU 時間のプロファイル データはあります。

経過時間プロファイラは Python シグナル モジュールに依存します。Python インタープリタのコンパイルでスレッド サポートを有効にすると、デフォルトの構成では、フォークしたプロセスのカスタム シグナル処理が無効になります。

uWSGI アプリケーションの場合は、py-call-osafterfork フラグを true に設定してカスタム シグナル処理を有効にします。

関連する問題については、この表の前のトピックをご覧ください。

Profiler を有効にすると、エラーログに次の新しいエントリが追加されます。

BlockingIOError: [Errno 11] Resource temporarily unavailable Exception ignored when trying to write to the signal wakeup fd

GitHub の問題

シグナル wakeup ファイル記述子(signal.set_wakeup_fd)に登録されているアプリケーション。デフォルトでは、ファイル記述子のバッファに空き容量がなくなると、stderr に警告が記録されます。

Cloud Profiler がプロファイルを収集する際には、高い頻度でシグナルがトリガーされます。この動作により、ファイル記述子のバッファの空き容量がなくなる場合があります。

シグナルが失われても、アプリケーションを安全に実行できる場合は、Cloud Profiler を使用できます。Python 3.7 以降を使用している場合、警告メッセージを無効にするには、warn_on_full_buffer=False をパラメータとして signal.set_wakeup_fd に渡します。

シグナルが失われ、アプリケーションを安全に実行できない場合は、Cloud Profiler の使用を停止することをおすすめします。使用し続けると、シグナル番号がなくなり、エラーログのエントリが過剰になる可能性があります

Linux Alpine で実行する

Linux Alpine 用の Python プロファイリング エージェントは、Google Kubernetes Engine の構成でのみサポートされています。

Python プロファイリング エージェントをビルドするには、パッケージ build-base をインストールする必要があります。最終的な Alpine イメージに追加の依存関係をインストールせずに Alpine で Python プロファイリング エージェントを使用するには、2 ステージのビルドを使用し、最初のステージで Python プロファイリング エージェントをコンパイルします。たとえば、次の Docker イメージではマルチステージ ビルドを使用して、Python プロファイリング エージェントをコンパイルしてインストールします。

FROM python:3.7-alpine as builder

# Install build-base to allow for compilation of the profiling agent.
RUN apk add --update --no-cache build-base

# Compile the profiling agent, generating wheels for it.
RUN pip3 wheel --wheel-dir=/tmp/wheels google-cloud-profiler

FROM python:3.7-alpine

# Copy over the directory containing wheels for the profiling agent.
COPY --from=builder /tmp/wheels /tmp/wheels

# Install the profiling agent.
RUN pip3 install --no-index --find-links=/tmp/wheels google-cloud-profiler

# Install any other required modules or dependencies, and copy an app which
# enables the profiler as described in "Enable the profiler in your
# application".
COPY ./bench.py .

# Run the application when the docker image is run, using either CMD (as is done
# here) or ENTRYPOINT.
CMD python3 -u bench.py

認証エラー

Linux Alpine で Docker イメージ(golang:alpinealpine など)を使用すると、次の認証エラーが表示されることがあります。

connection error: desc = "transport: authentication handshake failed: x509: failed to load system roots and no roots provided"

エラーの詳細を確認するには、エージェント ロギングを有効にする必要があります。

上記のエラーは、Linux Alpine の Docker イメージに、デフォルトでインストールされているはずのルート SSL 証明書がないことを示しています。これらの証明書は、プロファイリング エージェントが Profiler API と通信するために必要になります。このエラーを解決するには、次の apk コマンドを Dockerfile に追加します。

FROM alpine
...
RUN apk add --no-cache ca-certificates

その後、アプリケーションを再度ビルドし、デプロイする必要があります。

次のステップ