Django视图函数性能分析

前言

虽然根据经验来看,许多简单Django应用的性能问题都来源于数据库IO(而这可以通过聚合查询等手段进行优化),但premature optimization is the root of all evil,定位性能问题的具体位置仍然是最应该先做的事。
和其它相对独立的Python代码不同,Django的视图函数代码与Django框架的耦合度很高,这给对代码的性能分析带来了一些困难。但幸运的是,Django丰富的生态环境中有足够多的手段能让我们很方便地完成这件事。

注:本文所使用的运行环境为Python 3.6+Django 2.1.7

Django Debug Toolbar

Django Debug Toolbar可能是Django生态环境中最知名、最强大的页面调试工具了(官方文档连接)。这个工具可以很方便地将:页面耗时、设置、请求/响应、模板、SQL操作等一系列信息以侧边栏的形式展示在网页右侧,官方宣传图如下。

由于该工具能够展示页面耗时、SQL操作耗时等信息(特别是后者),所以该工具在视图函数性能分析方面也能起到非常关键的作用。
其最小化的部署方式如下:

  1. 通过pip安装该工具

    1
    2
    3
    pip install django-debug-toolbar
    # 通过源代码安装开发版
    # pip install -e git+https://github.com/jazzband/django-debug-toolbar.git#egg=django-debug-toolbar
  2. 修改Django配置文件(默认为settings.py),包括修改INSTALLED_APPSMIDDLEWARE项,新增INTERNAL_IPS项。

    1
    2
    3
    4
    5
    6
    7
    8
    INSTALLED_APPS = [
    # ...
    'django.contrib.staticfiles', # 确保已配置静态文件
    # ...
    'debug_toolbar',
    ]

    STATIC_URL = '/static/'
    1
    2
    3
    4
    5
    MIDDLEWARE = [
    # ...
    'debug_toolbar.middleware.DebugToolbarMiddleware',
    # ...
    ]
    1
    INTERNAL_IPS = ['127.0.0.1']  # 只对本机访问启用
  3. 配置Django URL规则

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from django.conf import settings
    from django.urls import include, path

    urlpatterns = [
    # ...
    ]

    if settings.DEBUG: # 只在DEBUG模式下启用
    import debug_toolbar

    urlpatterns.append(
    path('__debug__/', include(debug_toolbar.urls))
    )

此时打开任意一个页面即可看到对应效果,如下图所示:

另外,对于返回JSON、XML等数据的接口型视图函数来说,默认情况下是没办法使用Django Debug Tool的。不过在本地调试时,我们可以通过返回一个空的HTML页面(只需包含一个闭合的body标签)来达到类似的效果,具体操作如下:

1
2
3
4
5
6
7
from django.http.response import HttpResponse, JsonResponse

def view_func(request):
# ...
# return JsonResponse({'data': '...'}) # 无法启用Django Debug Tool

return HttpResponse('<body></body>') # 可以启用Django Debug Tool

cProfile+Django中间件

虽然Django Debug Toolbar是一个非常实用的性能分析工具,但它对于视图函数内部的代码具体执行耗时依然无能为力。如果想要进行更深层次的代码性能分析,就要使用我上一篇文章介绍的cProfile这类工具了。但与那篇文章中提到的使用方法不同,这次我们必须先编写Django中间件才能对Django视图函数进行性能分析。

注:本节内容灵感来自博文:How to profile Django views

  1. 编写中间件代码主体(假设位于django_profile/my_middleware.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    # coding:utf-8
    import cProfile
    import pstats
    from io import StringIO

    from django.conf import settings
    from django.core.exceptions import MiddlewareNotUsed
    from django.http.response import HttpResponse

    class ProfileMiddleware(object):

    def __init__(self, get_response):
    if not settings.DEBUG:
    raise MiddlewareNotUsed # 只在DEBUG模式下启用该中间件

    self.get_response = get_response

    def __call__(self, request):
    if 'profile' in request.GET: # 当请求URL中存在profile参数时进行性能分析
    profile = cProfile.Profile()
    profile.enable()
    self.get_response(request)
    profile.disable()
    ram_file = StringIO()
    sort_by = 'tottime'
    stats = pstats.Stats(profile, stream=ram_file)
    stats.strip_dirs().sort_stats(sort_by).print_stats()
    response = HttpResponse(ram_file.getvalue().encode('utf-8'), 'text/plain')
    else:
    response = self.get_response(request)

    return response
  2. 在Django配置文件中启用该中间件

    1
    2
    3
    4
    5
    MIDDLEWARE = [
    # ...
    'django_profile.my_middleware.ProfileMiddleware',
    # ...
    ]

该中间件大概的思路为:在Django真正调用视图函数(23行,self.get_response(request))时,使用cProfile对该视图函数进行性能分析,并用分析结果打包成的HttpResponse取代视图函数的Response返回给Django(或者说用户)。当然,由于我加了一系列的限制,该流程只会在DEBUG模式下、且URL中包含profile参数时才会启用。
此时在任何一个页面URL(HTTP Parameters)中增加profile参数时,即可看到对应效果,如下图所示:

这里介绍的只是cPorfile中间件的一个简单用例。实际上cProfile与Django中间件均具有比较强的可定制性,yet-another-django-profiler就是一个思路类似但更加复杂和完善的项目,有兴趣的话可以自行了解。另外,从理论上来说,cProfile可以和任意一个有类似Django中间件模式的Python Web框架搭配来达成类似的效果,这里就不扩展来说了。


Django视图函数性能分析
https://www.yooo.ltd/2019/03/11/Django视图函数性能分析/
作者
OrangeWolf
发布于
2019年3月11日
许可协议