Профилирование системы с использованием многократно используемых декораторов
В нашей базе кода есть несколько декораторов, которые широко используются.
Когда я создаю профиль времени выполнения, большая часть графика вызовов выглядит как часовое стекло; многие функции вызывают одну функцию (декоратор), которая затем вызывает много функций. Это менее полезный профиль, чем хотелось бы.
Есть ли способ исправить эту ситуацию? Удаление декоратора не является вариантом; он обеспечивает необходимую функциональность.
Мы рассмотрели возможность вручную удалить декоратор из данных cProfile после факта, но это не представляется возможным, поскольку данные суммируются в отношениях вызывающего абонентa → вызываемого абонента, что разрушает отношения caller- > decorator- > callee.
Ответы
Ответ 1
Используя что-то вроде new
(или types
в Python 2.6+), вы могли бы теоретически динамически создать объект кода, а затем объект функции, основанный на этом объекте кода, который имел встроенное имя, которое варьировалось вместе с функцией, которую вы обертывали.
Это позволит вам манипулировать вещами так же глубоко, как <func>.__code__.co_name
(который обычно доступен только для чтения).
import functools
import types
def metadec(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# do stuff
return func(*args, **kwargs)
c = wrapper.func_code
fname = "%s__%s" % (func.__name__, wrapper.__name__)
code = types.CodeType(
c.co_argcount,
c.co_nlocals,
c.co_stacksize,
c.co_flags,
c.co_code,
c.co_consts,
c.co_names,
c.co_varnames,
c.co_filename,
fname, # change the name
c.co_firstlineno,
c.co_lnotab,
c.co_freevars,
c.co_cellvars,
)
return types.FunctionType(
code, # Use our updated code object
wrapper.func_globals,
fname, # Use the updated name
wrapper.func_defaults,
wrapper.func_closure,
)
(functools.wraps
все еще используется здесь для того, чтобы разрешить прохождение таких вещей, как docstrings, имена модулей и т.д.)
In [1]: from metadec import metadec
In [2]: @metadec
...: def foobar(x):
...: print(x)
...:
...:
In [3]: foobar.__name__
Out[3]: 'foobar__wrapper'
In [4]: foobar(1)
1
Ответ 2
Я собираюсь предположить, что это не сам декоратор, который загромождает профилирование, а скорее функцию обертки, созданную декоратором. И это происходит потому, что все функции обертки имеют одно и то же имя. Чтобы решить эту проблему, просто измените имя декоратора на имя обертки.
def decorator(func):
def wrapper(*args):
print "enter func", func.__name__
return func(*args)
wrapper.__name__ += "_" + func.__name__
return wrapper
Вы также можете использовать functools.wraps()
, но тогда имя функции-обертки будет соответствовать имени функции, которую она обертывает. Я думаю, это было бы хорошо для профилирования.
Теперь объект кода функции также имеет имя. Python не хранит ссылки на функции в стеке, а только на объекты кода, поэтому, если профилировщик получает имя функции-обертки из фрейма стека, он получит это имя. Обертки, определенные обычным способом, совместно используют объект кода (даже если объект функции отличается), если вы явно не перестраиваете объект кода и объект функции для каждой функции-обертки. Это довольно большая работа и очень специфичная для CPython (может даже быть специфичной для версии). Но вот как вы можете это сделать:
from types import FunctionType, CodeType
def decorator(func):
def wrapper(*args):
print "enter func", func.__name__
return func(*args)
name = wrapper.__name__ + "_" + func.__name__
func_code = wrapper.func_code
new_code = CodeType(
func_code.co_argcount, func_code.co_nlocals, func_code.co_stacksize,
func_code.co_flags, func_code.co_code, func_code.co_consts,
func_code.co_names, func_code.co_varnames, func_code.co_filename,
name, func_code.co_firstlineno, func_code.co_lnotab,
func_code.co_freevars, func_code.co_cellvars)
wrapper = FunctionType(
new_code, wrapper.func_globals, name, wrapper.func_defaults,
wrapper.func_closure)
return wrapper
И имя функции, и имя объекта кода здесь установлены на wrapper_originalfuncname
, и поэтому их следует учитывать отдельно от обернутой функции в профилировщике. Вы можете легко установить их только в исходное имя функции, чтобы их время выполнения было включено в исходную функцию.