callmonitor - Ein einfaches Werkzeug zum Überwachen und Protokollierungsaufrufen pip install callmonitoroder klonen Sie dieses Repo und:
python setup.py install Es ist einfach zu bedienen. Dekorieren Sie einfach jede Funktion mit dem @intercept Decorator. Z.B:
from callmonitor import intercept
@ intercept
def test_fn_2 ( x , y = 2 , z = 3 ):
pass Dies speichert die Eingänge ( args , kwargs und argspec ) zusammen mit einer Anrufdatenbank ( callmonitor.DB ) zu: call-monitor/test_fn_2/<invocation count> .
callmonitor überschreibt die Ausgabe nicht Wenn der call-monitor Ordner bereits vorhanden ist (z. B. ein früherer Lauf), wird ein neuer Ordner call-monitor-1 oder call-monitor-2 usw. erstellt. Weitere Informationen zum Speichern dieser Daten finden Sie in den Abschnitten zur Data Structure .
Um zu vermeiden, dass unterschiedliche Prozesse an demselben Speicherort geschrieben werden, findet callmonitor -tid=<N> zum Stammordner ( call-monitor ). Derzeit unterstützt callmonitor mpi4py aus der Box: Wenn mpi4py.MPI.COMM_WORLD.Get_rank() > 1 automatisch callmonitor , wird automatisch davon ausgegangen -tid=<Get_rank()> dass es im Multi -Thread -Modus im Multi -Thread -Modus ausgeführt wird. Wenn Ihr Programm mit einem anderen Framwork (z. B. concurrent.Futures ) mit einem anderen Thread mithilfe von callmonitor Ihre Thread-ID mit callmonitor.Settings : Settings:
from callmonitor import Settings
settings = Settings ()
settings . enable_multi_threading ( THREAD_ID ) Vor dem ersten Aufruf von intercept (die Datenbank wird auf der Festplatte erstellt, wenn sie zum ersten Mal benötigt wird, wird an diesem Punkt an diesem Punkt stattgefunden. callmonitor.Settings wird gelesen. Alle Änderungen an callmonitor.Settings danach werden nur dann wirksam, wenn die Datenbank nachgebildet wird - mit callmonitor.CONTEXT.new ).
Handler Manchmal wird pickle es einfach nicht in Bezug auf die Eingänge der Speicherung von Funktionen reduzieren - z. B.. Wenn wir unsere eigenen ausgefallenen Datentypen speichern müssen. callmonitor bietet eine Möglichkeit, Ihre Down -Argument -Handler aufzubauen und sich beim globalen callmonitor.REGISTRY zu registrieren. Die Registrierung wird jedes Mal abgefragt, wenn die Funktionseingaben verarbeitet werden. Wenn Sie also Ihren eigenen ArgHandler erstellen und mit callmonitor.REGISTRY.add hinzufügen, werden alle Argumente des zugehörigen Datentyps ab diesem Zeitpunkt vorgeschrieben. ZB numpy bietet seine eigenen save / load . Wir haben bereits einen Numpy Arggument -Handler erstellt (und registriert) wie SO:
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 ) (Denken Sie daran, dass callmonitor.REGISTRY.add vor dem ersten Aufruf von @intercept aufgerufen werden muss, der diesen bestimmten Handler benötigt.) Ein benutzerdefinierter Handler muss die callmonitor.Handler -Klasse erben und save , load und accumulator_done definieren (der letzte ist ein @classmethod ).
callmonitor.load(<path>) lädt eine Datenbank unter <path> (siehe Abschnitt zur Data Structure ). Z.B:
from callmonitor import load
db = load ( "call-monitor" ) Wir können jetzt individuelle Funktionsaufrufe aus der Datenbank mit DB.get erhalten:
args , kwargs = db . get ( "function_name" , invocation_count ) (Dies wird auch automatisch .npy -Dateien und alle benutzerdefinierten Handler geladen - denken Sie daran, diese in callmonitor.REGISTRY zu registrieren, bevor Sie db.get ausführen)
Denken Sie daran: invocation_count beginnt bei 1. Daher reiten Sie auf den ersten Anruf bei test_np_1 aus:
In [ 4 ]: db . get ( "test_np_1" , 1 )
Out [ 4 ]: ([ 10 , array ([ 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ])], {})callmonitor interagierenWir versuchen, die Zusammenfassungen der folgenden nutzergerichteten Klassen auf höchstem Niveau zu aktivieren:
REGISTRYDBDB.get_args und Args über die __str__ und __repr__ -Funktionen. Z. B. callmonitor.REGISTRY zeigt, welche Datentyp-/Handlerpaare konfiguriert sind: In [ 2 ]: callmonitor . REGISTRY
Out [ 2 ]:
{
< class 'numpy.ndarray' > : < class 'callmonitor.handler.NPHandler' >
} Ebenso zeigt das DB -Objekt eine Zusammenfassung von Funktionen und wie oft.
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 Container Das Aussuchen von args , kwargs und argspec.defaults kann sehr langweilig sein - insbesondere wenn Sie versuchen, den Wert eines bestimmten Arguments herauszufinden. Daher bietet callmonitor.DB einen Additionl Getter - get_args , der ein Args -Objekt zurückgibt. callmonitor.Args sind Containerklassen, die jedes Eingabargument mit Namen als zugeschrieben speichern. Z.B:
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 Hinweis: Der Konstruktor callmonitor.Args füllt alle Argumente aus, die nicht in args und kwargs aus FullArgSpec -Standardeinstellungen sind. Wenn Sie nur die ursprüngliche Funktion neu erstellen möchten, rufen Sie die args und kwargs an, die von callmonitor.DB.get zurückgegeben werden.
Obwohl technisch gesehen keine Datenbank , rufen wir die von callmonitor eine Datenbank generierten Verzeichnisse zum Fehlen eines besseren Begriffs auf. Jede Datenbank besteht aus einer db.pkl -Datei (mit Metadaten) sowie Ordner für jede Funktion (jeder Funktionsaufruf wird aufgezählt). Z.B:
call-monitor
├── db.pkl
├── test_fn_1
│ ├── 1
│ │ └── input_descriptor.pkl
│ └── 2
│ └── input_descriptor.pkl
└── test_fn_2
└── 1
└── input_descriptor.pkl
Besondere Aufmerksamkeit werden numpy -Eingängen gelegt - diese werden als arg_<label>.npy bezeichnet, wobei <label> entweder der Index des Eingabebedarfs oder der kw für KWARGs ist. Z.B:
call-monitor
├── db.pkl
└── test_np_1
├── 1
│ ├── arg_1.npy
│ └── input_descriptor.pkl
└── 2
├── arg_n.npy
└── input_descriptor.pkl
Das Speichern aller Anrufdaten in einer einzelnen Datenstruktur - möglicherweise sogar in einer realen Datenbank;) -, wurde jedoch nicht einfach. Zukünftige Versionen umfassen die Möglichkeit, mehrere kleine Funktionsaufrufe in ein einzelnes Akkumulatorobjekt zu verschmelzen, um eine große Anzahl kleiner Dateien zu vermeiden.
Version 0.3.0 Brichtet viele Verbesserungen an callmonitor . Wir konnten daher keine native Rückwärtskompatibilität mehr ermöglichen. Derzeit wird ein Tool entwickelt, das eine Version 0.2.0 Datenbank in eine Version 0.3.0 (oder später) umwandeln kann. Versionen vor dem Dating 0.2.0 werden nicht mehr unterstützt.