Tematykę IntelliTrace poruszałem już kilkukrotnie. Dzisiaj chciałbym powrócić do zagadnienia opisanego w poście
Logi IntelliTrace bez tajemnic czyli analizy logu przy pomocy
IntelliTrace API. Tym razem napiszę w jaki sposób dostać się do informacji o tym kiedy została wywołana jakaś metoda, jaki wynik zwróciła i jakie były parametry wywołania. Informacje te są prezentowane w oknie
Calls View w
Visual Studio 2010 ale można je analizować w ograniczonym stopniu. Poniższe informacje przydadzą się każdemu kto będzie chciał na przykład załadować te dane do bazy danych w celu później analizy, na przykład przy pomocy algorytmów odkrywania wiedzy (
data mining).
Zacznijmy od wzorca kodu, który posłuży nam do odczytania pliku z logiem:
using (IntelliTraceFile file = new IntelliTraceFile(pathToIntelliTraceLog))
{
//Kolekcja procesów będzie miała tylko jedną pozycję
foreach (IntelliTraceProcess process in traceFile.Processes)
{
//Przetworzenie kolejnych wątków
foreach (IntelliTraceThread thread in process.Threads)
{
//Tworzymy strumień ze zdarzeniami. Każde zdarzenie odpowiada np.: wywołaniu metody
Chain chain = thread.CreateThreadChain<ThreadChain>();
EventToken eventToken = chain.FirstValidToken;
//Przetwarzamy zdarzenie po zdarzeniu
while (eventToken != chain.AfterLastToken)
{
//Pobranie zdarzenia
IntelliTraceEvent ev = chain.GetEvent(eventToken);
//To zdarzenie reprezentujące wywołanie metody
if(ev is MethodEnterEvent)
ProcessMethodEnterEvent(ev);
//To zdarzenie reprezentujące zakończenie wywołania metody
else if(ev is MethodExitEvent)
ProcessMethodExitEvent(ev);
eventToken = chain.GetNextToken(eventToken);
}
}
}
}
Powyższy kod zawiera dużo komentarzy dlatego nie powinno być trudności z jego zrozumieniem. Chciałbym zwrócić uwagę tylko na jedną rzecz. Lista zdarzeń jakie możemy obsłużyć zawiera znacznie więcej pozycji niż tylko dwie pokazane powyżej (
MethodEnterEvent,
MethodExitEvent). Niestety ale nie ma dobrej dokumentacji z pełną listą jaki "łańcuch zdarzeń" jakie zdarzenia udostępnia. Pozostaje droga eksperymentalna oraz przyjrzenie się projektowi
iTraceReader, o którym już pisałem w poście
Logi IntelliTrace bez tajemnic.
Zobaczmy, więc jak wyglądają metody
ProcessMethodEnterEvent oraz
ProcessMethodExitEvent. Na samym początku należy zamienić zdarzenia
MethodEnterEvent oraz
MethodExitEvent, które zawierają tylko suchy ciąg bajtów na bardziej przyjazne
ResolvedMethodEnterEvent oraz
ResolvedMethodExitEvent, z których łatwo wyciągniemy interesujące nas informacje.
private void ProcessMethodEnterEvent(IntelliTraceEvent ev)
{
ResolvedMethodEnterEvent methodEnterEvent = new ResolvedMethodEnterEvent(CurrentProcess, ev as MethodEnterEvent);
//...
}
Klasy
ResolvedMethodEnterEvent oraz
ResolvedMethodExitEvent zawierają właściwość
Method, która zwraca obiekt klasy
ResolvedMethod. Klasa ta reprezentuje wywołaną metodę i udostępnia następujące bardzo intuicyjne właściwości:
- ContainingTypeName - Pełna nazwa typu (razem z przestrzenią nazw) w ramach, którego metoda została zdefiniowana.
- MethodName - Nazwa metody.
- ParameterNames - Nazwy (ale nie wartości) parametrów.
- ParameterTypeNames - Pełne nazwy typów parametrów.
- ReturnTypeName - Pełna nazwa typu (ale nie wartość) zwracanego przez metodę.
Teraz przejdźmy do odczytania parametrów przekazanych do metody przy jej wywołaniu. Do tego celu posłuży nam metoda:
IList<IDataElement> ResolvedMethodEnterEvent.GetParameters()
W celu reprezentowania różnych bytów
IntelliTrace API udostępnia interfejs o nazwie
IDataElement.
IDataElement może reprezentować typy proste i referencyjne. W przypadku typu referencyjnego
IDataElement może zawierać elementy podrzędne reprezentujące składowe obiektu, które również będą reprezentowane przez
IDataElement. Napisałem może, a nie musi ponieważ IntelliTrace analizuje tylko obiekty pierwszego poziomu. Czyli jeśli parametr jakiejś metody to obiekt klasy A, który zawiera składowe typu
string,
int oraz wskazanie na obiekt klasy B to IntelliTrace zapisze w logu wartości wszystkich składowych typów prostych obiektu klasy A ale już nie obiektu klasy B. Na temat obiektu klasy B dowiemy się tylko tyle, że jest. Poniżej zamieszczam opis interfejsu
IDataElement. Zaznaczam jednak, że tworząc ten opis bazowałem na doświadczeniu i przeprowadzonych eksperymentach dlatego może on zawierać nieścisłości lub błędy:
- HasChildren - true jeśli mamy do czynienia z typem referencyjnym i jest to obiekt pierwszego poziomu, false w przypadku typów prostych i obiektów zagnieżdżonych.
- Name - Nazwa parametru, nazwa składowej obiektu, '[Return value]' dla wartości zwracanej przez metodę, this lub base.
- TypeName - Pełna nazwa typu reprezentowanego bytu.
- Value - W przypadku typów prostych właściwa wartość, a w przypadku typów referencyjnych nazwę typu. Jeśli wartość nie została zapisana przez IntelliTrace otrzymamy wynik 'unknown'.
- GetChildren - Metoda, którą możemy wywołać jeśli właściwość HasChildren jest równa true. Metoda ta zwraca elementy podrzędne w stosunku do bieżącego.
Warto również wiedzieć o poniższej metodzie, która służy do pobrania wyniku zwróconego przez metodę:
IDataElement ResolvedMethodExitEvent.GetReturnValue()
Znacznie rzadziej będą nas interesowały parametry typu
out, do których uzyskamy dostęp przy pomocy poniżej metody. Niestety ale wygląda na to, że metoda ta zawiera jakiś błąd. Próbowałem wywołać ją kilkukrotnie dla rożnych metod z parametrem
out typu
string. Za każdym razem kończyło się to błędem. Raz był to wyjątek
ArgumentOutOfRangeException innym razem
ApplicationException.
IList<IDataElement> ResolvedMethodExitEvent.GetOutputs()
Podsumowując pomimo ograniczeń
IntelliTrace API zachęcam do zabawy z logami.