Profilage d'un système avec des décorateurs largement réutilisés
Question
Notre base de code contient quelques décorateurs largement utilisés.
Lorsque je crée un profil d'exécution, une grande partie du graphe d'appel ressemble à un sablier;de nombreuses fonctions appellent une fonction (le décorateur), qui appelle ensuite plusieurs fonctions.C'est un profil moins utile que je ne le souhaiterais.
Existe-t-il un moyen de remédier à cette situation?La suppression du décorateur n'est pas une option;il fournit les fonctionnalités nécessaires.
Nous avons envisagé de supprimer manuellement le décorateur des données cProfile après coup, mais cela ne semble pas possible, car les données sont résumées dans des relations appelant-> appelé, ce qui détruit la relation appelant-> décorateur-> appelé.
La solution
En utilisant quelque chose comme la bibliothèque new
(ou types
en Python 2.6+), vous pourriez théoriquement créer dynamiquement un objet code, puis un objet fonction basé sur cet objet code qui avait un nom intégré quivariait avec la fonction que vous enveloppiez.
Cela vous permettrait de manipuler des choses aussi profondément que <func>.__code__.co_name
(qui est normalement en lecture seule).
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
est toujours utilisé ici afin de permettre le passage de choses comme les docstrings, les noms de modules, etc.)
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
Autres conseils
Je suppose que ce n'est pas le décorateur lui-même qui encombre votre profilage, mais plutôt la fonction wrapper créée par le décorateur. Et cela se produit parce que toutes les fonctions de wrapper ont le même nom. Pour résoudre ce problème, demandez au décorateur de changer le nom de la fonction wrapper.
def decorator(func):
def wrapper(*args):
print "enter func", func.__name__
return func(*args)
wrapper.__name__ += "_" + func.__name__
return wrapper
Vous pouvez également utiliser functools.wraps()
, mais le nom de la fonction wrapper correspondra au nom de la fonction qu'elle encapsule. Je suppose que ce serait acceptable pour le profilage.
Maintenant, l'objet code de la fonction a également un nom. Python ne stocke pas de références aux fonctions sur la pile, uniquement aux objets de code, donc si le profileur obtient le nom de la fonction wrapper à partir d'un cadre de pile, il obtiendra ce nom. Les wrappers définis de la manière habituelle partagent l'objet code (même si l'objet fonction est différent), sauf si vous reconstruisez explicitement l'objet code et l'objet fonction pour chaque fonction wrapper. C'est un peu plus de travail et très spécifique à CPython (peut même être spécifique à la version). Mais voici comment procéder:
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
Le nom de la fonction et le nom de l'objet code sont définis ici sur wrapper_originalfuncname
et doivent donc être comptés séparément de la fonction encapsulée dans le profileur. Vous pouvez facilement les définir uniquement sur le nom de la fonction d'origine afin que leur temps d'exécution soit ajouté à celui de la fonction d'origine.