Azure Functions で Python アプリのメモリ使用量をプロファイルする

開発中、またはローカルの Python 関数アプリ プロジェクトを Azure にデプロイした後に、関数内で発生する可能性のあるメモリのボトルネックを分析することをお勧めします。 このようなボトルネックによって関数のパフォーマンスが低下し、エラーを招く可能性があります。 次の手順では、memory-profiler Python パッケージを使用する方法について説明します。これにより、関数の実行時のメモリ使用量分析が 1 行ずつ行われます。

注意

メモリ プロファイルは、開発環境でのメモリ占有領域の分析のみを目的としています。 運用環境の関数アプリにはメモリ プロファイラーを適用しないでください。

前提条件

Python 関数アプリの開発を開始する前に、次の要件を満たしておく必要があります。

Azure サブスクリプションをお持ちでない場合は、開始する前に Azure 無料アカウントを作成してください。

メモリ プロファイルのプロセス

  1. requirements.txt に memory-profiler を追加して、パッケージがデプロイにバンドルされるようにします。 ローカル コンピューター上で開発している場合は、Python 仮想環境をアクティブ化し、pip install -r requirements.txt によってパッケージを解決することができます。

  2. 関数スクリプト (たとえば、Python v1 プログラミング モデルの場合は __init__.py、v2 モデルの場合は function_app.py) で、次の行を main() 関数の上に追加します。 これらの行により、ルート ロガーによって子ロガー名が報告され、メモリ プロファイル ログがプレフィックス memory_profiler_logs によって識別できるようになります。

    import logging
    import memory_profiler
    root_logger = logging.getLogger()
    root_logger.handlers[0].setFormatter(logging.Formatter("%(name)s: %(message)s"))
    profiler_logstream = memory_profiler.LogFile('memory_profiler_logs', True)
    
  3. メモリ プロファイルを必要とするすべての関数よりも上に、次のデコレータを適用します。 デコレーターは、トリガー エントリポイントの main() メソッドに直接機能しません。 サブ関数を作成してサブ関数を修飾する必要があります。 また、メモリプロファイラーの既知の問題により、非同期コルーチンに適用する場合、コルーチンの戻り値は常に None になります。

    @memory_profiler.profile(stream=profiler_logstream)
    
  4. Azure Functions Core Tools コマンド func host start を使用して、ローカル コンピューター上のメモリ プロファイラーをテストします。 関数を呼び出すと、メモリ使用量レポートが生成されます。 レポートには、ファイル名、コード行、メモリの使用量、メモリの増分、および行の内容が含まれます。

  5. Azure の既存の関数アプリ インスタンスのメモリ プロファイル ログを確認するには、Application Insights のログで Kusto クエリを使用して、最近の呼び出しのメモリ プロファイル ログをクエリできます。

    Screenshot showing the query memory usage of a Python app in Application Insights.

    traces
    | where timestamp > ago(1d)
    | where message startswith_cs "memory_profiler_logs:"
    | parse message with "memory_profiler_logs: " LineNumber "  " TotalMem_MiB "  " IncreMem_MiB "  " Occurrences "  " Contents
    | union (
        traces
        | where timestamp > ago(1d)
        | where message startswith_cs "memory_profiler_logs: Filename: "
        | parse message with "memory_profiler_logs: Filename: " FileName
        | project timestamp, FileName, itemId
    )
    | project timestamp, LineNumber=iff(FileName != "", FileName, LineNumber), TotalMem_MiB, IncreMem_MiB, Occurrences, Contents, RequestId=itemId
    | order by timestamp asc
    

次に示すのは、それぞれ "HttpTriggerAsync" と "HttpTriggerSync" という名前の非同期および同期の HTTP トリガーでメモリ プロファイルを実行する例です。 作成するのは、単に Microsoft のホーム ページに GET 要求を送信する Python 関数アプリです。

Python 関数アプリを作成する

Python 関数アプリは Azure Functions で指定されたフォルダー構造に従っている必要があります。 プロジェクトをスキャフォールディングするには、次のコマンドを実行して Azure Functions Core Tools を使用することをお勧めします。

func init PythonMemoryProfilingDemo --python
cd PythonMemoryProfilingDemo
func new -l python -t HttpTrigger -n HttpTriggerAsync -a anonymous
func new -l python -t HttpTrigger -n HttpTriggerSync -a anonymous

ファイルの内容を更新する

requirements.txt では、プロジェクトで使用されるパッケージを定義します。 Azure Functions SDK と memory-profiler に加えて、非同期 HTTP 要求用の aiohttp と同期 HTTP 呼び出し用の requests を挿入します。

# requirements.txt

azure-functions
memory-profiler
aiohttp
requests

非同期 HTTP トリガーを作成します。

非同期 HTTP トリガー HttpTriggerAsync/__init__.py のコードを、次のコードに置き換えます。このコードでは、メモリ プロファイラー、ルート ロガー形式、ロガー ストリーミング バインディングを構成します。

# HttpTriggerAsync/__init__.py

import azure.functions as func
import aiohttp
import logging
import memory_profiler

# Update root logger's format to include the logger name. Ensure logs generated
# from memory profiler can be filtered by "memory_profiler_logs" prefix.
root_logger = logging.getLogger()
root_logger.handlers[0].setFormatter(logging.Formatter("%(name)s: %(message)s"))
profiler_logstream = memory_profiler.LogFile('memory_profiler_logs', True)

async def main(req: func.HttpRequest) -> func.HttpResponse:
    await get_microsoft_page_async('https://microsoft.com')
    return func.HttpResponse(
        f"Microsoft page loaded.",
        status_code=200
    )

@memory_profiler.profile(stream=profiler_logstream)
async def get_microsoft_page_async(url: str):
    async with aiohttp.ClientSession() as client:
        async with client.get(url) as response:
            await response.text()
    # @memory_profiler.profile does not support return for coroutines.
    # All returns become None in the parent functions.
    # GitHub Issue: https://github.com/pythonprofilers/memory_profiler/issues/289

同期 HTTP トリガーを作成します。

非同期 HTTP トリガー HttpTriggerSync/__init__.py のコードを次のコードに置き換えます。

# HttpTriggerSync/__init__.py

import azure.functions as func
import requests
import logging
import memory_profiler

# Update root logger's format to include the logger name. Ensure logs generated
# from memory profiler can be filtered by "memory_profiler_logs" prefix.
root_logger = logging.getLogger()
root_logger.handlers[0].setFormatter(logging.Formatter("%(name)s: %(message)s"))
profiler_logstream = memory_profiler.LogFile('memory_profiler_logs', True)

def main(req: func.HttpRequest) -> func.HttpResponse:
    content = profile_get_request('https://microsoft.com')
    return func.HttpResponse(
        f"Microsoft page response size: {len(content)}",
        status_code=200
    )

@memory_profiler.profile(stream=profiler_logstream)
def profile_get_request(url: str):
    response = requests.get(url)
    return response.content

ローカル開発環境で Python 関数アプリをプロファイルする

上記の変更を行ったら、さらにいくつかの手順を行って、Azure Functions ランタイムの Python 仮想環境を初期化します。

  1. 好みに応じて、Windows PowerShell または任意の Linux シェルを開きます。

  2. Windows では py -m venv .venv、Linux では python3 -m venv .venv を使用して、Python 仮想環境を作成します。

  3. Windows PowerShell では .venv\Scripts\Activate.ps1、Linux シェルでは source .venv/bin/activate を使用して、Python 仮想環境をアクティブ化します。

  4. pip install -r requirements.txt を使用して Python の依存関係を復元します。

  5. Azure Functions Core Tools func host start を使用して、Azure Functions ランタイムをローカルで起動します。

  6. GET 要求を https://localhost:7071/api/HttpTriggerAsync または https://localhost:7071/api/HttpTriggerSync に送信します。

  7. Azure Functions Core Tools で、次のセクションのようなメモリ プロファイル レポートが表示されます。

    Filename: <ProjectRoot>\HttpTriggerAsync\__init__.py
    Line #    Mem usage    Increment  Occurrences   Line Contents
    ============================================================
        19     45.1 MiB     45.1 MiB           1   @memory_profiler.profile
        20                                         async def get_microsoft_page_async(url: str):
        21     45.1 MiB      0.0 MiB           1       async with aiohttp.ClientSession() as client:
        22     46.6 MiB      1.5 MiB          10           async with client.get(url) as response:
        23     47.6 MiB      1.0 MiB           4               await response.text()
    

次のステップ

Python による Azure Functions 開発の詳細については、次のリソースを参照してください。