分析 Azure Functions Python 应用的内存用量

在开发过程中或在将本地 Python 函数应用项目部署到 Azure 之后,最好分析函数中的潜在内存瓶颈。 此类瓶颈会削弱函数性能并导致错误。 以下说明演示了如何使用内存探查器 Python 包,其在执行时逐行分析函数的内存消耗。

注意

内存分析仅适用于开发环境的内存占用情况分析。 请不要在生产函数应用中使用内存探查器。

先决条件

开发 Python 函数应用必须满足以下要求:

如果没有 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、Logs 中的 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
    

示例

以下示例解释了如何对异步和同步 HTTP 触发器(分别名为“HttpTriggerAsync”和“HttpTriggerSync”)执行内存分析。 我们将创建仅向 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 和内存探查器外,我们还介绍了异步 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 shell。

  2. 通过 Windows 的py -m venv .venv或 Linux 的python3 -m venv .venv创建 Python 虚拟环境。

  3. 通过 Windows PowerShell 的 .venv\Scripts\Activate.ps1 或 Linux shell 的 source .venv/bin/activate 激活 Python 虚拟环境。

  4. 通过pip install -r requirements.txt还原 Python 依赖项

  5. 通过 Azure Functions Core Toolsfunc host start启动本地 Azure Functions runtime 运行时

  6. 将 GET 请求发送至https://localhost:7071/api/HttpTriggerAsynchttps://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()
    

后续步骤

有关 Azure Functions Python 开发的详细信息,请参阅以下资源: