kyle
1/20/2018 - 4:27 AM

profile_middleware.py

"""
Need Django and CProfile.
Just append this middleware class in MIDDLEWARE in django settings.
"""

from django.core.exceptions import MiddlewareNotUsed
from django.utils.deprecation import MiddlewareMixin
from django.conf import settings
import cProfile
import pstats
import marshal
from io import StringIO


class ProfileMiddleware(MiddlewareMixin):
    """
    view를 프로파일링하는 미들웨어 입니다. 본래는 debug가 true일 때만 실행해야 하지만, debug 상태에선 실제 배포 상태와는 다른 함수들이 실행되어
    시간 지연이 발생하기 때문에 false 상태에서도 실행되도록 해놓았습니다. 대신 debug settings 파일 내부에만 미들웨어가 추가되어 있기에 배포 설정으로
    서버를 열 경우 실행되지 않습니다.

    사용법은 실행할 url 뒤에 profile 패러미터를 추가하면 됩니다. 만약 파일을 다운로드 받고 싶다면 profilebin 패러미터를 추가하면 됩니다.
    """
    def __init__(self, get_response=None):
        self.profiler = None
        super().__init__(get_response)

    def process_view(self, request, view_func, view_args, view_kwargs):
        if 'profile' in request.GET or 'profilebin' in request.GET:
            self.profiler = cProfile.Profile()
            args = (request,) + view_args
            return self.profiler.runcall(view_func, *args, **view_kwargs)
        return None

    def process_response(self, request, response):
        if 'profile' in request.GET:
            self.profiler.create_stats()
            out = StringIO()
            stats = pstats.Stats(self.profiler, stream=out)
            # Values for stats.sort_stats():
            # - calls           call count
            # - cumulative      cumulative time
            # - file            file name
            # - module          file name
            # - pcalls          primitive call count
            # - line            line number
            # - name            function name
            # - nfl                     name/file/line
            # - stdname         standard name
            # - time            internal time
            stats.sort_stats('time').print_stats(.2)
            response.content = out.getvalue()
            response['Content-type'] = 'text/plain'
        if 'profilebin' in request.GET:
            self.profiler.create_stats()
            response.content = marshal.dumps(self.profiler.stats)
            filename = request.path.strip('/').replace('/', '_') + '.pstat'
            response['Content-Disposition'] = \
                'attachment; filename=%s' % (filename,)
            response['Content-type'] = 'application/octet-stream'
        return response