665eed4e56
This patch fixes the code to make latest flake8 happy. Changes: 1. There is no xrange in python3. Import range from six.moves or simply use range from builtins, where number of iterations is fixed and small. 2. Don't use unicode and long. There are six.text_type, six.string_types and six.integer_types to avoid it. 3. Replace 'file' with 'open'. 4. You can't put function argument into tuple directly (f = lambda (x, y): x += y; f((1,2))). Change-Id: I2995ca71c94a2cbb8fe43dfeaf20f88b0b5bfa9c
109 lines
3.7 KiB
Python
109 lines
3.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2014 Mirantis, Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import os
|
|
import time
|
|
|
|
import cProfile
|
|
import gprof2dot
|
|
from pstats import Stats
|
|
import pyprof2calltree
|
|
|
|
from nailgun.settings import settings
|
|
|
|
|
|
class ProfilerMiddleware(object):
|
|
|
|
def __init__(self, app):
|
|
self._app = app
|
|
|
|
def __call__(self, environ, start_response):
|
|
response_body = []
|
|
|
|
def catching_start_response(status, headers, exc_info=None):
|
|
start_response(status, headers, exc_info)
|
|
return response_body.append
|
|
|
|
def runapp():
|
|
appiter = self._app(environ, catching_start_response)
|
|
response_body.extend(appiter)
|
|
if hasattr(appiter, 'close'):
|
|
appiter.close()
|
|
|
|
handler_name = environ.get('PATH_INFO').strip('/').replace('/', '.') \
|
|
or 'root'
|
|
profiler = Profiler(environ['REQUEST_METHOD'], handler_name)
|
|
profiler.profiler.runcall(runapp)
|
|
body = b''.join(response_body)
|
|
profiler.save_data()
|
|
return [body]
|
|
|
|
|
|
class Profiler(object):
|
|
"""Run profiler and save profile"""
|
|
def __init__(self, method='', handler_name=''):
|
|
self.method = method
|
|
self.handler_name = handler_name
|
|
if not os.path.exists(settings.
|
|
LOAD_TESTS_PATHS['last_performance_test_run']):
|
|
os.makedirs(settings.LOAD_TESTS_PATHS['last_performance_test_run'])
|
|
self.profiler = cProfile.Profile()
|
|
self.profiler.enable()
|
|
self.start = time.time()
|
|
|
|
def save_data(self):
|
|
elapsed = time.time() - self.start
|
|
pref_filename = os.path.join(
|
|
settings.LOAD_TESTS_PATHS['last_performance_test_run'],
|
|
'{method:s}.{handler_name:s}.{elapsed_time:.0f}ms.{t_time}.'.
|
|
format(
|
|
method=self.method,
|
|
handler_name=self.handler_name or 'root',
|
|
elapsed_time=elapsed * 1000.0,
|
|
t_time=time.time()))
|
|
tree_file = pref_filename + 'prof'
|
|
stats_file = pref_filename + 'txt'
|
|
callgraph_file = pref_filename + 'dot'
|
|
|
|
# write pstats
|
|
with open(stats_file, 'w') as file_o:
|
|
stats = Stats(self.profiler, stream=file_o)
|
|
stats.sort_stats('time', 'cumulative').print_stats()
|
|
|
|
# write callgraph in dot format
|
|
parser = gprof2dot.PstatsParser(self.profiler)
|
|
|
|
def get_function_name(arg):
|
|
filename, line, name = arg
|
|
module = os.path.splitext(filename)[0]
|
|
module_pieces = module.split(os.path.sep)
|
|
return "{module:s}:{line:d}:{name:s}".format(
|
|
module="/".join(module_pieces[-4:]),
|
|
line=line,
|
|
name=name)
|
|
|
|
parser.get_function_name = get_function_name
|
|
gprof = parser.parse()
|
|
|
|
with open(callgraph_file, 'w') as file_o:
|
|
dot = gprof2dot.DotWriter(file_o)
|
|
theme = gprof2dot.TEMPERATURE_COLORMAP
|
|
dot.graph(gprof, theme)
|
|
|
|
# write calltree
|
|
call_tree = pyprof2calltree.CalltreeConverter(stats)
|
|
with open(tree_file, 'wb') as file_o:
|
|
call_tree.output(file_o)
|