callmonitor - un outil simple pour surveiller et enregistrer les appels de fonction pip install callmonitorou cloner ce repo et:
python setup.py install Il est simple à utiliser, décorez simplement n'importe quelle fonction avec le décorateur @intercept . Par exemple:
from callmonitor import intercept
@ intercept
def test_fn_2 ( x , y = 2 , z = 3 ):
pass Cela enregistrera les entrées ( args , kwargs et argspec ) avec une base de données d'appels ( callmonitor.DB ) à: call-monitor/test_fn_2/<invocation count> .
callmonitor ne remplace pas la sortie Si le dossier call-monitor existe déjà (par exemple, une analyse précédente), alors un nouveau dossier call-monitor-1 , ou call-monitor-2 , et ainsi de suite, est créé. Voir les sections sur Data Structure pour plus de détails sur la façon dont ces données sont enregistrées.
Pour éviter différents processus de l'écriture au même emplacement, callmonitor ajout -tid=<N> au dossier root ( call-monitor ). Actuellement, callmonitor prend en charge mpi4py hors de la boîte: si mpi4py.MPI.COMM_WORLD.Get_rank() > 1 , callmonitor suppose automatiquement qu'il exécute le mode IM multi-thread et les touches -tid=<Get_rank()> à la sortie. Si votre programme est multi- callmonitor.Settings avec callmonitor autre framwork (par exemple, concurrent.Futures .
from callmonitor import Settings
settings = Settings ()
settings . enable_multi_threading ( THREAD_ID ) Avant la première invocation de intercept (la base de données est créée sur le disque lorsqu'elle est nécessaire pour la première fois, c'est à ce moment-là que callmonitor.Settings est lu. Toutes les modifications apportées à callmonitor.Settings ne prendront effet que si la base de données est recréée - en utilisant callmonitor.CONTEXT.new ).
Handler d'arguments S Parfois, pickle ne le coupera pas en termes de sauvegarde des entrées de fonction - par exemple. Lorsque nous devons enregistrer nos propres types de données de fantaisie. callmonitor fournit un moyen de construire vos gestionnaires d'argument et de s'inscrire à Global callmonitor.REGISTRY . Le registre est interrogé à chaque fois que les entrées de fonction sont traitées, donc si vous créez votre propre ArgHandler et les ajoutez à l'aide de callmonitor.REGISTRY.add , il traitera tous les arguments du type de données associé à partir de ce moment. Par exemple, numpy offre ses propres fonctions save / load . Nous avons déjà construit (et enregistré) un gestionnaire d'argument Numpy comme tel:
import numpy as np
from os . path import join
from callmonitor import Handler , REGISTRY
class NPHandler ( Handler ):
def load ( self , path ):
self . data = np . load ( join ( path , f"arg_ { self . target } .npy" ))
def save ( self , path ):
np . save ( join ( path , f"arg_ { self . target } .npy" ), self . data )
@ classmethod
def accumulator_done ( cls ):
pass
REGISTRY . add ( np . ndarray , NPHandler ) (N'oubliez pas que callmonitor.REGISTRY.add doit être appelé avant la première invocation de @intercept qui a besoin de ce Handler particulier). Un gestionnaire personnalisé doit hériter de la classe callmonitor.Handler et définir save , load et accumulator_done (le dernier étant un @classmethod ).
callmonitor.load(<path>) Chargera une base de données à <path> (voir la section sur Data Structure ). Par exemple:
from callmonitor import load
db = load ( "call-monitor" ) Nous pouvons désormais obtenir des données d'appels de fonction individuelles de la base de données à l'aide de DB.get :
args , kwargs = db . get ( "function_name" , invocation_count ) (qui chargera également automatiquement les fichiers .npy et tous les gestionnaires personnalisés - n'oubliez pas de les inscrire dans callmonitor.REGISTRY avant d'exécuter db.get )
N'oubliez pas: invocation_count commence à 1. Par conséquent, pour accéder au premier appel à test_np_1 , exécutez:
In [ 4 ]: db . get ( "test_np_1" , 1 )
Out [ 4 ]: ([ 10 , array ([ 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ])], {})callmonitorNous essayons d'activer les résumés de niveau supérieur des classes orientées utilisateur suivantes:
REGISTRYDBDB.get_args et Args via les fonctions __str__ et __repr__ . Par exemple, callmonitor.REGISTRY montre quelles paires de types de données / gestionnaire sont configurées: In [ 2 ]: callmonitor . REGISTRY
Out [ 2 ]:
{
< class 'numpy.ndarray' > : < class 'callmonitor.handler.NPHandler' >
} De même, l'objet DB affiche un résumé des fonctions appelées et à quelle fréquence.
In [ 3 ]: db = callmonitor . load ( "call-monitor" )
In [ 4 ]: db
Out [ 4 ]:
{
Locked : True
test_np_1 : {
calls : 2
args : [ 'x' , 'n' ]
defaults : None
}
}Args La sélection args , kwargs et argspec.defaults peut être très fastidieux - surtout si vous essayez de découvrir la valeur d'un argument spécifique. Par conséquent, callmonitor.DB fournit un additionl Getter - get_args qui renvoie un objet Args . callmonitor.Args sont des classes de conteneurs qui stockent chaque argument d'entrée par nom comme un attribué. Par exemple:
In [ 3 ]: args = db . get_args ( "test_fn_1" , 1 )
In [ 4 ]: args
Out [ 4 ]: dict_keys ([ 'x' , 'y' , 'z' ])
In [ 5 ]: args . x
Out [ 5 ]: 1 Remarque: Le constructeur callmonitor.Args remplira tous les arguments qui ne sont pas dans args et kwargs à partir des défauts de défaut FullArgSpec . Si vous souhaitez simplement recréer la fonction d'origine, appelez les args et kwargs renvoyés par callmonitor.DB.get devrait être suffisant.
Bien qu'il ne soit pas techniquement une base de données , appelons les répertoires générés par callmonitor une base de données pour l'absence d'un meilleur terme. Chaque base de données se compose d'un fichier db.pkl (contenant des métadonnées), ainsi que des dossiers pour chaque fonction (chaque appel de fonction est énuméré). Par exemple:
call-monitor
├── db.pkl
├── test_fn_1
│ ├── 1
│ │ └── input_descriptor.pkl
│ └── 2
│ └── input_descriptor.pkl
└── test_fn_2
└── 1
└── input_descriptor.pkl
Une attention particulière est accordée aux entrées numpy - celles-ci sont appelées arg_<label>.npy , où <label> est soit l'index de l'argument d'entrée, soit le kw pour Kwargs. Par exemple:
call-monitor
├── db.pkl
└── test_np_1
├── 1
│ ├── arg_1.npy
│ └── input_descriptor.pkl
└── 2
├── arg_n.npy
└── input_descriptor.pkl
Une considération complète a été accordée à l'enregistrement de toutes les données d'appel dans une seule structure de données - peut-être même une base de données réelle ;) - mais le faire efficacement à grande échelle n'est pas facile et pourrait rendre ce package lourd. Les versions futures comprendront la possibilité de fusionner plusieurs petites petites fonctions dans un seul objet d'accumulateur pour éviter un grand nombre de petits fichiers.
Version 0.3.0 Infraction de nombreuses améliorations à callmonitor . Nous ne pouvions donc plus permettre une compatibilité vers l'arrière native. Un outil qui peut convertir une base de données de version 0.2.0 en version 0.3.0 (ou ultérieure) est actuellement en cours de développement. Les versions pré-date 0.2.0 ne sont plus prises en charge.