199 lines
6.9 KiB
Python
199 lines
6.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
from decorator import decorator
|
|
import inspect
|
|
import logging
|
|
import sys
|
|
import time
|
|
|
|
import flectra
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class _LogTracer(object):
|
|
def __init__(self, whitelist=None, blacklist=None, files=None, deep=False):
|
|
self.profiles = {}
|
|
self.whitelist = whitelist
|
|
self.blacklist = blacklist
|
|
self.files = files
|
|
self.deep = deep
|
|
self.first_frame = None
|
|
|
|
def tracer(self, frame, event, arg):
|
|
if not self.first_frame:
|
|
self.first_frame = frame.f_code
|
|
if not self.deep and self.first_frame != frame.f_code:
|
|
return self.tracer
|
|
|
|
if frame.f_code.co_name in ['<genexpr>', '__getattr__', '__iter__', '__init__']:
|
|
return
|
|
|
|
if 'self' not in frame.f_locals:
|
|
return self.tracer
|
|
|
|
if self.files and frame.f_code.co_filename not in self.files:
|
|
return self.tracer
|
|
|
|
in_self = frame.f_locals['self']
|
|
|
|
if isinstance(in_self, (flectra.sql_db.Cursor, flectra.sql_db.TestCursor, flectra.sql_db.LazyCursor)):
|
|
return self.tracer
|
|
|
|
model = getattr(in_self, '_name', None)
|
|
|
|
if self.whitelist and model not in self.whitelist:
|
|
return self.tracer
|
|
if model in self.blacklist and self.first_frame != frame.f_code:
|
|
return self.tracer
|
|
|
|
if frame.f_code not in self.profiles:
|
|
try:
|
|
lines, firstline = inspect.getsourcelines(frame)
|
|
self.profiles[frame.f_code] = {
|
|
'model': model,
|
|
'filename': frame.f_code.co_filename,
|
|
'firstline': firstline,
|
|
'code': lines,
|
|
'calls': [],
|
|
'nb': 0,
|
|
}
|
|
except Exception:
|
|
return
|
|
codeProfile = self.profiles[frame.f_code]
|
|
|
|
if not frame.f_lineno:
|
|
codeProfile['nb'] += 1
|
|
|
|
cr = getattr(in_self, '_cr', None)
|
|
codeProfile['calls'].append({
|
|
'event': event,
|
|
'lineno': frame.f_lineno,
|
|
'queries': cr and cr.sql_log_count,
|
|
'time': time.time(),
|
|
'callno': codeProfile['nb'],
|
|
})
|
|
|
|
return self.tracer
|
|
|
|
def profile(method=None, whitelist=None, blacklist=(None,), files=None,
|
|
minimum_time=0, minimum_queries=0):
|
|
"""
|
|
Decorate an entry point method.
|
|
If profile is used without params, log as shallow mode else, log
|
|
all methods for all flectra models by applying the optional filters.
|
|
|
|
:param whitelist: None or list of model names to display in the log
|
|
(Default: None)
|
|
:type whitelist: list or None
|
|
:param files: None or list of filenames to display in the log
|
|
(Default: None)
|
|
:type files: list or None
|
|
:param list blacklist: list model names to remove from the log
|
|
(Default: remove non flectra model from the log: [None])
|
|
:param int minimum_time: minimum time (ms) to display a method
|
|
(Default: 0)
|
|
:param int minimum_queries: minimum sql queries to display a method
|
|
(Default: 0)
|
|
|
|
.. code-block:: python
|
|
|
|
from flectra.tools.profiler import profile
|
|
|
|
class SaleOrder(models.Model):
|
|
...
|
|
|
|
@api.model
|
|
@profile # log only this create method
|
|
def create(self, vals):
|
|
...
|
|
@api.multi
|
|
@profile() # log all methods for all flectra models
|
|
def unlink(self):
|
|
...
|
|
@profile(whitelist=['sale.order', 'ir.model.data'])
|
|
def action_quotation_send(self):
|
|
...
|
|
@profile(files=['/home/openerp/flectra/flectra/addons/sale/models/sale.py'])
|
|
def write(self):
|
|
...
|
|
|
|
NB: The use of the profiler modifies the execution time
|
|
"""
|
|
|
|
deep = not method
|
|
|
|
def _flectraProfile(method, *args, **kwargs):
|
|
log_tracer = _LogTracer(whitelist=whitelist, blacklist=blacklist, files=files, deep=deep)
|
|
sys.settrace(log_tracer.tracer)
|
|
try:
|
|
result = method(*args, **kwargs)
|
|
finally:
|
|
sys.settrace(None)
|
|
|
|
log = ["\n%-10s%-10s%s\n" % ('calls', 'queries', 'ms')]
|
|
|
|
for v in log_tracer.profiles.values():
|
|
v['report'] = {}
|
|
l = len(v['calls'])
|
|
for k, call in enumerate(v['calls']):
|
|
if k+1 >= l:
|
|
continue
|
|
|
|
if call['lineno'] not in v['report']:
|
|
v['report'][call['lineno']] = {
|
|
'nb_queries': 0,
|
|
'delay': 0,
|
|
'nb': 0,
|
|
}
|
|
v['report'][call['lineno']]['nb'] += 1
|
|
|
|
n = k+1
|
|
while k+1 <= l and v['calls'][k+1]['callno'] != call['callno']:
|
|
n += 1
|
|
if n >= l:
|
|
continue
|
|
next_call = v['calls'][n]
|
|
if next_call['queries'] is not None:
|
|
v['report'][call['lineno']]['nb_queries'] += next_call['queries'] - call.get('queries', 0)
|
|
v['report'][call['lineno']]['delay'] += next_call['time'] - call['time']
|
|
|
|
queries = 0
|
|
delay = 0
|
|
for call in v['report'].values():
|
|
queries += call['nb_queries']
|
|
delay += call['delay']
|
|
|
|
if minimum_time and minimum_time > delay*1000:
|
|
continue
|
|
if minimum_queries and minimum_queries > queries:
|
|
continue
|
|
|
|
# todo: no color if output in a file
|
|
log.append("\033[1;33m%s %s--------------------- %s, %s\033[1;0m\n\n" % (v['model'] or '', '-' * (15-len(v['model'] or '')), v['filename'], v['firstline']))
|
|
for lineno, line in enumerate(v['code']):
|
|
if (lineno + v['firstline']) in v['report']:
|
|
data = v['report'][lineno + v['firstline']]
|
|
log.append("%-10s%-10s%-10s%s" % (
|
|
str(data['nb']) if 'nb_queries' in data else '.',
|
|
str(data.get('nb_queries', '')),
|
|
str(round(data['delay']*100000)/100) if 'delay' in data else '',
|
|
line[:-1]))
|
|
else:
|
|
log.append(" " * 30)
|
|
log.append(line[:-1])
|
|
log.append('\n')
|
|
|
|
log.append("\nTotal:\n%-10s%-10d%-10s\n\n" % (
|
|
str(data['nb']),
|
|
queries,
|
|
str(round(delay*100000)/100)))
|
|
|
|
_logger.info(''.join(log))
|
|
|
|
return result
|
|
|
|
if not method:
|
|
return lambda method: decorator(_flectraProfile, method)
|
|
|
|
wrapper = decorator(_flectraProfile, method)
|
|
return wrapper
|