callmonitor - Uma ferramenta simples para monitorar e registrar chamadas de função pip install callmonitorou clonar este repo e:
python setup.py install É simples de usar, basta decorar qualquer função com o decorador @intercept . Por exemplo:
from callmonitor import intercept
@ intercept
def test_fn_2 ( x , y = 2 , z = 3 ):
pass Isso salvará as entradas ( args , kwargs e argspec ), juntamente com um banco de dados de chamada ( callmonitor.DB ) para: call-monitor/test_fn_2/<invocation count> .
callmonitor não substitui a saída Se a pasta de call-monitor já existir (por exemplo, uma execução anterior), será criada uma nova pasta call-monitor-1 ou call-monitor-2 e assim por diante. Consulte as seções sobre Data Structure para obter mais detalhes sobre como esses dados são salvos.
Para evitar processos diferentes, desde a escrita até o mesmo local, callmonitor apêmea -tid=<N> à pasta Root ( call-monitor ). Atualmente, callmonitor suporta mpi4py para fora da caixa: se mpi4py.MPI.COMM_WORLD.Get_rank() > 1 , callmonitor assume automaticamente que está executando o modo IM Multi -Thread e anexa -tid=<Get_rank()> para a saída. Se o seu programa for multi-thread com outro trabalho de Fram (por exemplo, concurrent.Futures ), você precisará informar callmonitor seu ID do thread usando callmonitor.Settings :
from callmonitor import Settings
settings = Settings ()
settings . enable_multi_threading ( THREAD_ID ) Antes da primeira invocação de intercept (o banco de dados é criado no disco quando é necessário, é nesse ponto em que callmonitor.Settings é lido. Quaisquer alterações feitas para callmonitor.Settings posteriormente entrarão em vigor se o banco de dados for recriado - usando callmonitor.CONTEXT.new ).
Handler de argumentos Às vezes, pickle simplesmente não o corte em termos de salvamento de entradas da função - por exemplo. Quando precisamos salvar nossos próprios tipos de dados sofisticados. callmonitor fornece uma maneira de criar seus manipuladores de argumento de baixo e se registrar no Global callmonitor.REGISTRY . O registro é consultado sempre que as entradas de função são processadas; portanto, se você construir seu próprio ArgHandler e adicioná -las usando callmonitor.REGISTRY.add , processará quaisquer argumentos do tipo de dados associados a partir desse ponto. Por exemplo, numpy fornece suas próprias funções de save / load . Já construímos (e registramos) um manipulador Numpy Arggument como assim:
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 ) (Lembre -se de que callmonitor.REGISTRY.add precisa ser chamado antes da primeira invocação de @intercept que precisa desse Handler em particular). Um manipulador personalizado precisa herdar a classe callmonitor.Handler e definir save , load e accumulator_done (o último sendo um @classmethod ).
callmonitor.load(<path>) carregará um banco de dados em <path> (consulte a seção na Data Structure ). Por exemplo:
from callmonitor import load
db = load ( "call-monitor" ) Agora podemos obter dados de chamadas de funções individuais do banco de dados usando DB.get :
args , kwargs = db . get ( "function_name" , invocation_count ) (que também carregará arquivos .npy automaticamente e quaisquer manipuladores personalizados - lembre -se de registrá -los em callmonitor.REGISTRY antes de executar db.get )
Lembre -se: invocation_count começa em 1. Portanto, para acessar a primeira chamada para test_np_1 , execute:
In [ 4 ]: db . get ( "test_np_1" , 1 )
Out [ 4 ]: ([ 10 , array ([ 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ])], {})callmonitorTentamos ativar os resumos de nível superior das seguintes classes voltadas para o usuário:
REGISTRYDBDB.get_args e Args através das funções __str__ e __repr__ . Por exemplo, callmonitor.REGISTRY mostra quais pares de dados/manipuladores estão configurados: In [ 2 ]: callmonitor . REGISTRY
Out [ 2 ]:
{
< class 'numpy.ndarray' > : < class 'callmonitor.handler.NPHandler' >
} Da mesma forma, o objeto DB exibe um resumo das funções chamadas e com que frequência.
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 Escolhendo args , kwargs e argspec.defaults pode ser muito tedioso - especialmente se você estiver tentando descobrir o valor de um argumento específico. Portanto, callmonitor.DB fornece um additt getter - get_args que retorna um objeto Args . callmonitor.Args são classes de contêineres que armazenam cada argumento de entrada pelo nome como um atribuído. Por exemplo:
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: O construtor callmonitor.Args preencherá quaisquer argumentos que não estejam em args e kwargs dos padrões da FullArgSpec . Se você deseja apenas recriar a função original, ligue para os args e kwargs devolvidos por callmonitor.DB.get deve ser suficiente.
Embora não seja tecnicamente um banco de dados , vamos chamar os diretórios gerados pelo callmonitor de banco de dados para a falta de um termo melhor. Cada banco de dados consiste em um arquivo db.pkl (contendo metadados), bem como pastas para cada função (cada chamada de função é enumerada). Por exemplo:
call-monitor
├── db.pkl
├── test_fn_1
│ ├── 1
│ │ └── input_descriptor.pkl
│ └── 2
│ └── input_descriptor.pkl
└── test_fn_2
└── 1
└── input_descriptor.pkl
Atenção especial é dada às entradas numpy - elas são chamadas de arg_<label>.npy , onde <label> é o índice do argumento de entrada ou o kw para Kwargs. Por exemplo:
call-monitor
├── db.pkl
└── test_np_1
├── 1
│ ├── arg_1.npy
│ └── input_descriptor.pkl
└── 2
├── arg_n.npy
└── input_descriptor.pkl
A consideração completa foi dada para salvar todos os dados de chamada em uma única estrutura de dados - talvez até um banco de dados real ;) - mas fazer isso com eficiência em escala não é fácil e pode tornar esse pacote complicado. As versões futuras incluirão a capacidade de fundir várias chamadas de pequenas funções em um único objeto de acumulador para evitar um grande número de arquivos pequenos.
Versão 0.3.0 Brinha muitos aprimoramentos ao callmonitor . Portanto, não podíamos mais permitir a compatibilidade com versões anteriores nativas. Uma ferramenta que pode converter um banco de dados da versão 0.2.0 em uma versão 0.3.0 (ou posterior) está sendo desenvolvido atualmente. As versões anteriores a 0.2.0 não são mais suportadas.