callmonitor : una herramienta simple para monitorear y registrar llamadas a la función pip install callmonitoro clonar este repositorio y:
python setup.py install Es simple de usar, simplemente decore cualquier función con el decorador @intercept . P.ej:
from callmonitor import intercept
@ intercept
def test_fn_2 ( x , y = 2 , z = 3 ):
pass Esto guardará las entradas ( args , kwargs y argspec ) junto con una base de datos de llamadas ( callmonitor.DB ) a: call-monitor/test_fn_2/<invocation count> .
callmonitor no sobrescribe la salida Si la carpeta call-monitor ya existe (por ejemplo, una ejecución anterior), se crea una nueva carpeta call-monitor-1 , o call-monitor-2 , y así sucesivamente, se crea. Consulte las secciones sobre Data Structure para obtener más detalles sobre cómo se guarda estos datos.
Para evitar diferentes procesos desde la escritura hasta la misma ubicación, callmonitor agrega -tid=<N> a la carpeta root ( call-monitor ). Actualmente, callmonitor admite mpi4py fuera de la caja: si mpi4py.MPI.COMM_WORLD.Get_rank() > 1 , callmonitor asume automáticamente que está ejecutando el modo multiproceso IM y appends -tid=<Get_rank()> a la salida. callmonitor.Settings su programa está multiprocesado con callmonitor marco (por ejemplo, concurrent.Futures .
from callmonitor import Settings
settings = Settings ()
settings . enable_multi_threading ( THREAD_ID ) Antes de la primera invocación de intercept (la base de datos se crea en el disco cuando se necesita por primera vez, es en ese punto cuando se lee callmonitor.Settings . Cualquier cambio realizado en callmonitor.Settings . Las sets después solo entrarán en vigencia si la base de datos se recrea, utilizando callmonitor.CONTEXT.new ).
Handler de argumentos A veces, pickle simplemente no lo cortará en términos de guardar entradas de funciones, por ejemplo. Cuando necesitamos guardar nuestros propios tipos de datos elegantes. callmonitor proporciona una forma de construir sus manejadores de argumentos hacia abajo y registrarse en el Global callmonitor.REGISTRY . El registro se consulta cada vez que las entradas de las funciones de tiempo se procesan, por lo que si construye su propio ArgHandler y los agrega usando callmonitor.REGISTRY.add , procesará cualquier argumento del tipo de datos asociado a partir de ese punto. Por ejemplo, numpy proporciona sus propias funciones save / load . Ya hemos construido (y registrado) un manejador de Argumento Numpy como así:
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 ) (Recuerde que callmonitor.REGISTRY.add debe llamarse antes de la primera invocación de @intercept que necesita este Handler en particular). Un controlador personalizado necesita heredar la clase callmonitor.Handler y definir save , load y accumulator_done (el último es un @classmethod ).
callmonitor.load(<path>) cargará una base de datos en <path> (consulte la sección sobre Data Structure ). P.ej:
from callmonitor import load
db = load ( "call-monitor" ) Ahora podemos obtener datos de llamadas de función individual de la base de datos utilizando DB.get :
args , kwargs = db . get ( "function_name" , invocation_count ) (que también cargará automáticamente archivos .npy y cualquier manejador personalizado; recuerde registrarlos en callmonitor.REGISTRY antes de ejecutar db.get )
Recuerde: invocation_count comienza en 1. Por lo tanto, para acceder a la primera llamada a test_np_1 , ejecutar:
In [ 4 ]: db . get ( "test_np_1" , 1 )
Out [ 4 ]: ([ 10 , array ([ 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ])], {})callmonitorIntentamos habilitar resúmenes de nivel superior de las siguientes clases de orientación de usuario:
REGISTRYDBDB.get_args , y Args a través de las funciones __str__ y __repr__ . Por ejemplo, callmonitor.REGISTRY muestra qué pares de datos de datos/parejas de controladores están configurados: In [ 2 ]: callmonitor . REGISTRY
Out [ 2 ]:
{
< class 'numpy.ndarray' > : < class 'callmonitor.handler.NPHandler' >
} Del mismo modo, el objeto DB muestra un resumen de las funciones llamadas y con qué frecuencia.
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 Eligiendo args , kwargs y argspec.defaults puede ser muy tedioso, especialmente si está tratando de averiguar el valor de un argumento específico. Por lo tanto, callmonitor.DB proporciona un getter adicional - get_args que devuelve un objeto Args . callmonitor.Args son clases de contenedores que almacenan cada argumento de entrada por nombre como atribuido. P.ej:
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 NOTA: El constructor callmonitor.Args completará cualquier argumento que no sea en args y kwargs de los valores predeterminados FullArgSpec . Si solo desea recrear la función original, llame a los args y kwargs devueltos por callmonitor.DB.get debería ser suficiente.
Si bien no es técnicamente una base de datos , llamemos a los directorios generados por callmonitor una base de datos por falta de un término mejor. Cada base de datos consiste en un archivo db.pkl (que contiene metadatos), así como carpetas para cada función (cada llamada de función se enumera). P.ej:
call-monitor
├── db.pkl
├── test_fn_1
│ ├── 1
│ │ └── input_descriptor.pkl
│ └── 2
│ └── input_descriptor.pkl
└── test_fn_2
└── 1
└── input_descriptor.pkl
Se presta especial atención a las entradas numpy : estas se llaman arg_<label>.npy , donde <label> es el índice del argumento de entrada o el kw para KWARGS. P.ej:
call-monitor
├── db.pkl
└── test_np_1
├── 1
│ ├── arg_1.npy
│ └── input_descriptor.pkl
└── 2
├── arg_n.npy
└── input_descriptor.pkl
Se consideró una consideración completa para guardar todos los datos de llamadas en una sola estructura de datos, tal vez incluso una base de datos real ;), pero hacer esto de manera eficiente a escala no es fácil y podría hacer que este paquete sea engorroso. Las versiones futuras incluirán la capacidad de fusionar múltiples llamadas de funciones pequeñas en un solo objeto de acumulador para evitar grandes cantidades de archivos pequeños.
La versión 0.3.0 Brigns muchas mejoras para callmonitor . Por lo tanto, ya no podíamos habilitar la compatibilidad nativa hacia atrás. Actualmente se está desarrollando una herramienta que puede convertir una base de datos de la versión 0.2.0 a una versión 0.3.0 (o posterior). Las versiones previas a la fecha 0.2.0 ya no son compatibles.