剖析 Python 应用的性能

本页面介绍如何修改 Python 应用以捕获性能剖析数据,并将该数据发送到您的 Google Cloud 项目。如需了解性能剖析的常规信息,请参阅性能剖析相关概念

Python 的性能剖析文件类型:

  • CPU 时间
  • 实际用时(主线程,需要 Python 3.6 或更高版本)

支持的 Python 语言版本:

  • Python 3.2 或更高版本

支持的性能剖析代理版本:

  • 支持代理的最新版本。通常,不支持发布超过一年的版本。我们建议您使用最近发布的代理版本。

支持的操作系统:

  • Linux。Linux 内核支持剖析 Python 应用的性能,其标准 C 库是使用 glibcmusl 实现的。如需了解针对 Linux Alpine 内核的特定配置信息,请参阅在 Linux Alpine 上运行

支持的环境:

启用 Profiler API

在使用性能剖析代理之前,请确保已启用底层 Profiler API。您可以使用 Cloud SDK gcloud 命令行工具或 Cloud Console 查看该 API 的状态,还可以在必要时启用该 API:

Cloud SDK

  1. 如果您尚未在工作站上安装 Cloud SDK,请参阅 Google Cloud SDK

  2. 运行以下命令:

    gcloud services enable cloudprofiler.googleapis.com
    

如需了解详情,请参阅 gcloud services

Cloud Console

  1. 转到 API 和服务信息中心:

    转到 API 和服务

  2. 选择您将使用哪个项目访问该 API。

  3. 点击添加 API 和服务按钮。

    添加 API 和服务

  4. 搜索 Profiler API

  5. 在搜索结果中,选择 Cloud Profiler API

    如果系统未列出 Cloud Profiler API,请选择 Stackdriver Profiler API

  6. 如果系统显示 API 已启用,则表示此 API 已经启用。如未显示,请点击启用按钮。

使用 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. google-cloud-profiler 添加到 requirements.txt 文件中。

  2. 导入 googlecloudprofiler 模块,并尽早在初始化代码中调用 googlecloudprofiler.start 函数:

    import googlecloudprofiler
    
    # Profiler initialization. It starts a daemon thread which continuously
    # collects and uploads profiles. Best done as early as possible.
    try:
        # service and service_version can be automatically inferred when
        # running on App Engine. project_id must be set if not running
        # on GCP.
        googlecloudprofiler.start(verbose=3)
    except (ValueError, NotImplementedError) as exc:
        print(exc)  # Handle errors here
    

对于 App Engine,serviceservice_version 由您的操作环境生成。如需了解问题排查和异常信息,请参阅问题排查

标准环境

对于 App Engine 标准环境(需要使用 Python 3 运行时环境),请执行以下操作:

  1. google-cloud-profiler 添加到 requirements.txt 文件中。

  2. 导入 googlecloudprofiler 模块,并尽早在初始化代码中调用 googlecloudprofiler.start 函数:

    import googlecloudprofiler
    
    # Profiler initialization. It starts a daemon thread which continuously
    # collects and uploads profiles. Best done as early as possible.
    try:
        # service and service_version can be automatically inferred when
        # running on App Engine. project_id must be set if not running
        # on GCP.
        googlecloudprofiler.start(verbose=3)
    except (ValueError, NotImplementedError) as exc:
        print(exc)  # Handle errors here
    

对于 App Engine,serviceservice_version 由您的操作环境生成。如需了解问题排查和异常信息,请参阅问题排查

start 函数

googlecloudprofiler.start 函数会创建一个守护进程线程,用于持续收集和上传性能剖析文件。您应在应用中尽早调用一次 start

参数 说明
service1 (必需)待执行性能剖析的服务的名称。如需了解服务名称的限制,请参阅服务名称和版本参数
service_version1 (可选)待执行性能剖析的服务的版本。如需了解服务版本的限制,请参阅服务名称和版本参数
verbose (可选)日志记录级别。如需详细了解日志记录级别,请参阅代理日志记录

默认值为 0 (Error)
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 界面查看和分析这些数据。要开始使用此界面,请参阅打开 Profiler 界面

服务名称和版本参数

加载 Profiler 代理时,请指定服务名称参数和可选的服务版本参数,以对其进行配置。

凭借服务名称,Profiler 可以收集该服务所有副本的性能剖析数据。对于每个组合服务版本和区域中的每个服务名称,Profiler 服务将保证平均每分钟生成一个性能剖析文件的收集速率。

例如,如果您的服务有两个版本在三个区域中的副本上运行,那么 Profiler 平均每分钟将为该服务创建 6 个性能剖析文件。

如果您为副本使用不同的服务名称,那么对服务执行性能剖析的频率将超出必要频率,相应的开销也会更高。

选择服务名称时,应注意以下几点:

  • 选择的名称应能清楚地表示应用架构中的服务。如果您只运行单个服务或应用,那么服务名称的选择没那么重要;但如果您的应用作为一组微服务运行,则服务名称的选择较为重要。

  • 切勿在服务名称字符串中使用任何进程专用的值,例如进程 ID。

  • 服务名称字符串必须与如下正则表达式相符:

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

建议使用 imageproc-service 这样的静态字符串作为服务名称。

服务版本是可选的。如果您指定服务版本,则 Profiler 可以汇总来自多个实例的性能剖析信息并将其正确显示出来。服务版本可用于在部署服务时标记不同的版本。Profiler 界面支持您按服务版本过滤数据,这样一来,您就可以比较新旧版本代码的性能。

服务版本参数的值是一个使用自由格式的字符串,但此参数的值通常看起来像版本号,例如 1.0.02.1.2

代理日志记录

默认情况下,性能剖析代理会记录严重级别为 error 的消息。要将代理配置为记录严重级别较低的消息,请在启动代理时指定 verbose 参数。verbose 有四个受支持的值:

  • 0:Error
  • 1:Warning
  • 2:Informational
  • 3:Debug

如果您在调用 start 时将 verbose 参数设置为 1,则将记录严重级别为 WarningError 的消息,而忽略 InformationalDebug 消息。

如需记录所有消息,请在启动代理时将 verbose设置为 3

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

问题排查

本部分列出了剖析 Python 应用的性能所特有的限制、例外情况和已知问题。有关常见问题的帮助,请参阅问题排查

限制

性能剖析文件类型 限制和局限
CPU 时间
  • 受 Python 3.2 及更高版本支持。
实际用时
  • 受 Python 3.6 及更高版本支持。
  • 仅限主线程性能剖析。
  • 必须从主线程调用性能剖析文件 start 函数。
  • Profiler 信号处理程序仅在主线程上执行。如果主线程无法执行,则不会捕获任何性能剖析数据。

异常

错误 原因 解决方案
start 期间抛出 NotImplementedError 应用在非 Linux 环境中执行。
  • 在 Linux 环境中执行您的应用。
start 期间抛出 ValueError start 函数的参数无效,无法通过环境变量和参数或性能剖析(如果同时停用 CPU 性能剖析和实际用时性能剖析)确定必要的信息。
  • 确保服务名称和版本符合服务名称和版本参数中指定的要求。
  • 如果启用了实际用时性能剖析,请确保从主线程调用 start
  • 确保您使用的是受支持的 Python 版本,并且已启用 CPU 时间性能剖析或实际用时性能剖析。如需了解详情,请参阅 start 函数
  • 如果您在 Google Cloud 之外运行,请检查是否已为 start 指定 project_id 参数。如需了解详情,请参阅 start 函数

已知问题

行为 原因 解决方案
在 Python 3.4 或更早版本的应用中,在剖析 CPU 时间或实际用时的性能时,系统调用会失败。 Python 3.4 及更早版本会导致系统调用失败(被包含 EINTR 的信号中断)。您的应用必须处理此情况。如需了解详情,请参阅 PEP 475
  • 使用 Python 3.5 或更高版本。
  • 让应用处理 EINTR 响应代码。
启用实际用时性能剖析后,Python 3.5 或更早版本的应用无法响应信号。 Python 3.5 及更早版本的信号处理中包含竞态条件。结果是应用停止响应信号(包括信号 Ctrl-C)。
  • 使用 Python 3.6 或更高版本。
您没有任何性能剖析数据,或者您启用了新的性能剖析类型,但缺少性能剖析数据。 常见原因与配置有关。 请参阅问题排查
您使用的是 uWSGI,但没有所有进程的 CPU 时间和实际用时性能剖析数据。

当 uWSGI 使用多个工作器处理请求时,默认行为是仅在主进程中执行应用初始化。派生进程不执行初始化序列。

如果您在应用的初始化序列中(例如,在 Django 应用的 AppConfig.ready() 中)配置性能剖析代理,则结果是没有为派生进程配置性能剖析代理。

如需在所有工作器进程中执行应用初始化,请将标记 lazy-apps 设置为 true

请参阅此表中的下一个主题,了解相关问题。

您使用的是 uWSGI,且您没有实际用时性能剖析数据,但您确实有 CPU 时间性能剖析数据。

实际用时性能剖析器依赖于 Python 信号模块。 使用线程支持编译 Python 解释器时,默认配置会停用对派生进程的自定义信号处理。

对于 uWSGI 应用,请将标记 py-call-osafterfork 设置为 true,以启用自定义信号处理。

请参阅此表中的上一主题,了解相关问题。

启用性能剖析器后,错误日志将包含新条目:

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

GitHub 问题

您的应用已注册信号唤醒文件描述符 signal.set_wakeup_fd,并设置 warn_on_full_buffer=True

在 Cloud Profiler 收集性能剖析文件时,它会触发高频率信号。此行为可能会导致文件描述符的缓冲区变满。

如需确定您的应用是否需要 warn_on_full_buffer=True,请参阅 signal.set_wakeup_fd
  • 如果不需要,请将该标志设置为 False.
  • 如果需要,我们建议您停止使用 Cloud Profiler。继续使用可能会导致错误日志中信号数丢失且条目过多。

使用 Linux Alpine 运行

只有 Google Kubernetes Engine 配置支持适用于 Linux Alpine 的 Python 性能剖析代理。

如需构建 Python 性能剖析代理,您必须安装软件包 build-base。如需在 Alpine 上使用 Python 性能剖析代理,而无需在最终的 Alpine 映像上安装其他依赖项,您可以使用两阶段式构建并在第一阶段编译 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

身份验证错误

如果您的 Docker 映像使用 Linux Alpine(如 golang:alpine 或者只是 alpine)运行,您可能会看到以下身份验证错误:

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

请注意,您必须启用代理日志记录才能看到该错误。

该错误表示使用 Linux Alpine 的 Docker 映像没有默认安装 SSL 根证书。这些证书为性能剖析代理与性能剖析器 API 进行通信所必需。如需解决此错误,请将以下 apk 命令添加到您的 Dockerfile 中:

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

然后,您需要重新构建并重新部署应用。

后续步骤

如需了解 Profiler 图表和控件,请转到使用 Cloud Profiler 界面。如需了解详情,请参阅以下内容: