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操作耗时等信息(特别是后者),所以该工具在视图函数性能分析方面也能起到非常关键的作用。
其最小化的部署方式如下:
通过
pip
安装该工具1
2
3pip install django-debug-toolbar
# 通过源代码安装开发版
# pip install -e git+https://github.com/jazzband/django-debug-toolbar.git#egg=django-debug-toolbar修改Django配置文件(默认为
settings.py
),包括修改INSTALLED_APPS
、MIDDLEWARE
项,新增INTERNAL_IPS
项。1
2
3
4
5
6
7
8INSTALLED_APPS = [
# ...
'django.contrib.staticfiles', # 确保已配置静态文件
# ...
'debug_toolbar',
]
STATIC_URL = '/static/'1
2
3
4
5MIDDLEWARE = [
# ...
'debug_toolbar.middleware.DebugToolbarMiddleware',
# ...
]1
INTERNAL_IPS = ['127.0.0.1'] # 只对本机访问启用
配置Django URL规则
1
2
3
4
5
6
7
8
9
10
11
12
13from 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 |
|
cProfile+Django中间件
虽然Django Debug Toolbar是一个非常实用的性能分析工具,但它对于视图函数内部的代码具体执行耗时依然无能为力。如果想要进行更深层次的代码性能分析,就要使用我上一篇文章介绍的cProfile
这类工具了。但与那篇文章中提到的使用方法不同,这次我们必须先编写Django中间件才能对Django视图函数进行性能分析。
注:本节内容灵感来自博文:How to profile Django views
编写中间件代码主体(假设位于
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在Django配置文件中启用该中间件
1
2
3
4
5MIDDLEWARE = [
# ...
'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框架搭配来达成类似的效果,这里就不扩展来说了。