16/06/2010

Własne zdarzenia IntelliTrace 2

Home

Dzisiaj, zgodnie z wcześniejszą obietnicą, chciałem pokazać w jaki sposób zdefiniować nowe zdarzenie diagnostyczne dla naszej własnej metody. W tym celu użyję bardzo prostej klasy pokazanej poniżej:

namespace SmallTest
{
    public class Fun
    {
        public void Hello(int count, string msg)
        {
            for (int i = 0; i < count; ++i)
                Console.WriteLine(msg);
        }
    }
}

Przyjmijmy, że klasa ta znajduje się w projekcie o nazwie SmallTest po skompilowaniu, którego powstanie plik SmallTest.dll. Skoro mamy już z czym pracować przystąpmy do modyfikacji pliku ColllectionPlan.xml. Cały proces będzie bardzo podobny do tego co pokazałem w poprzednim poście. Zaczynamy od znalezienia węzła o nazwie ModuleList i umieszczamy pod nim węzeł wskazujący na naszą bibliotekę:

  <ModuleSpecification Id="SmallTest">SmallTest.dll</ModuleSpecification>

Co istotne biblioteka nie musi być podpisana, ani znajdować się w jakimś konkretnym katalogu. Następnie tworzymy nową kategorię dla zdarzeń. W tym celu pod węzłem Categories powinniśmy umieścić pokazany poniżej węzeł XML. Jeśli nie chcemy tworzyć nowej kategorii możemy pominąć ten krok.

  <Category Id="my" _locID="category.My">My</Category>

Na koniec najważniejsza rzecz, zdefiniowanie nowego zdarzenia. Postępujemy dokładnie tak samo jak przy definiowaniu zdarzenia dla metody platformy .NET. Potrzebny kod XML został pokazany poniżej. Umieszczamy go pod węzłem DiagnosticEventSpecifications. Jeśli w poprzednim kroku nie tworzyliśmy nowej kategorii wartość węzła CategoryId powinna odpowiadać nazwie innej, już istniejącej kategorii.

    <DiagnosticEventSpecifications>
      <DiagnosticEventSpecification>
        <CategoryId>my</CategoryId>
        <SettingsName _locID="settingsName.Fun.Hello">Fun.Hello</SettingsName>
        <SettingsDescription _locID="settingsDescription.Fun.Hello">Fun.Hello</SettingsDescription>
        <Bindings>
          <Binding>
            <ModuleSpecificationId>SmallTest</ModuleSpecificationId>
            <TypeName>SmallTest.Fun</TypeName>
            <MethodName>Hello</MethodName>
            <MethodId>SmallTest.Fun.Hello(System.Int32, System.String):System.Void</MethodId>
            <ShortDescription _locID="shortDescription.Fun.Hello">Fun.Hello({0},{1})</ShortDescription>
            <LongDescription _locID="longDescription.Fun.Hello">Fun.Hello(count={0},msg={1})</LongDescription>
            <DataQueries>
              <DataQuery index="1" maxSize="0" type="Int32" _locID="dataquery.Fun.Hello.count" _locAttrData="name" name="count" query="" />
              <DataQuery index="2" maxSize="256" type="String" _locID="dataquery.Fun.Hello.msg" _locAttrData="name" name="msg"  query=""></DataQuery>
            </DataQueries>
          </Binding>
        </Bindings>
      </DiagnosticEventSpecification>

Szczegółowy opis znaczenia poszczególnych węzłów XML użytych w tej definicji podałem w poprzednim poście, a więc nie będę go tutaj powielał. Uruchamiany Visual Studio i jeśli nie popełniliśmy żadnego błędu możemy już korzystać z nowego zdarzenia. W celu sprawdzenia czy wszystko jest w porządku najpierw zaglądamy do Tools->Options->IntteliTrace->IntteliTrace Events. Powinniśmy zobaczyć nową kategorię, a po jej rozwinięciu nowe zdarzenie:



Teraz możemy stworzyć nowy projekt konsolowy i dodać referencję to biblioteki SmallTest. Kiedy to zrobimy, nie pozostaje nic innego jak utworzyć obiekt klasy Fun i wywołać metodę Hello:

            Fun f = new Fun();
            f.Hello(2, "Hello world!");

Po skompilowaniu i uruchomieniu programu pod kontrolą debugger'a i z włączonym mechanizmem IntteliTrace w oknie z zarejestrowanymi zdarzeniami zobaczymy nasze zdarzenie:



W następnym poście pokażę w jaki sposób analizować wystąpienia zdarzeń w sposób programowy.

11/05/2010

Własne zdarzenia IntelliTrace!

Home

IntelliTrace, znane również pod nazwą historycznego debugger'a, to narzędzie jakie pojawiło się w Visual Studio 2010, a które stanowi rozwinięcie "tradycyjnych" debugger'ów o możliwość nagrywania historii wykonania programu w celu jej późniejszej analizy. Post ten rozpoczyna serię dotyczącą tej technologii, a w której chcę opisać zaawansowane techniki użycia IntelliTrace.

IntelliTrace posiada dwa tryby pracy: podstawowy oraz rozszerzony. W trybie podstawowym stan programu zapisywany jest w momencie wystąpienia tzw. zdarzeń diagnostycznych. W trybie rozszerzonym rejestrowane są dodatkowo wywołania metod, dostęp do właściwości itd. Zdarzenia diagnostyczne to ważne punkty w historii wykonania programu np.: wykonanie zapytania do bazy danych, dostęp do pliku, wystąpienie wyjątku, zatrzymanie programu na pułapce i wiele innych. Poniższy rysunek pokazuje penel z listą dostępnych zdarzeń. Możemy się do niego dostać wybierając Tools->Options->IntelliTrace->IntelliTrace Events.



Większość zdarzeń związana jest z wywołaniem określonych metod np.: ExecuteReader. Zdarzenia tego rodzaju zdefiniowane są w pliku CollectionPlan.xml. Istotne jest to, że plik ten można modyfikować poprzez zmianę istniejących zdarzeń albo dodawanie właśnych. Plik ten znajduje się w lokalizacji:

VS_2010_INSTALL_DIR\Team Tools\TraceDebugger Tools\LANGUAGE\CollectionPlan.xml

VS_2010_INSTALL_DIR to katalog instalacyjny Visual Studio 2010. Wartość LANGUAGE zależy od używanego przez nas języka. Domyślnie po zainstalowaniu środowiska zostanie utworzony katalog en. Jeśli jednak pracujemy z językiem polskim musimy samemu utworzyć katalog o nazwie pl i skopiować do niego plik CollectionPlan.xml. Jest to konieczne jeśli chcemy wprowadzić jakieś zmiany do podstawowego zestawu zdarzeń. Przed przystąpieniem do pracy zalecam utworzenie kopii zapasowej tego pliku.

Modyfikację ColllectionPlan.xml pokażę na przykładzie dodania nowego zdarzenia do kategorii Console. Kategoria ta zawiera zdarzenia skojarzone z wywołaniem metody Console.WriteLine. Z niewiadomych powodów nie przewidziano zdarzenia dla metody Console.Write. Naprawmy to! Poniżej zamieściłem XML stanowiący definicję nowego zdarzenia dla tej metody:

      <DiagnosticEventSpecification enabled="false">
        <CategoryId>console</CategoryId>
        <SettingsName _locID="settingsName.Console.Write">Write (1 args)</SettingsName>
        <SettingsDescription _locID="settingsDescription.Console.Write">Console Output with a String passed in.</SettingsDescription>
        <Bindings>
          <Binding>
            <ModuleSpecificationId>mscorlib</ModuleSpecificationId>
            <TypeName>System.Console</TypeName>
            <MethodName>Write</MethodName>
            <MethodId>System.Console.Write(System.String):System.Void</MethodId>
            <ShortDescription _locID="shortDescription.Console.Write.String">{0}</ShortDescription>
            <LongDescription _locID="longDescription.Console.Write">Console Output "{0}".</LongDescription>
            <DataQueries>
              <DataQuery index="0" maxSize="256" type="String" name="value" _locID="dataquery.Write.value" _locAttrData="name" query=""></DataQuery>
            </DataQueries>
          </Binding>
        </Bindings>
      </DiagnosticEventSpecification>

Węzeł Category określa kategorię do jakiej zostanie dodane nowe zdarzenie. SettingsName definiuje nazwę zdarzenia jaka będzie widoczna w ustawieniach VS 2010. Kolejny węzeł o nazwie SettingsDescription ma podobne znaczenie. Zawiera opis zdarzenia jak będzie wyświetlany dla użytkownika. Poniżej zamieścilem obrazek pokazujący w jaki sposób środowisko zinterpretuje te parametry.



Dalsza część definicji zdarzenia jest ważniejsza. Przede wszystkim zawiera ona dokładną specyfikację metody czyli nazwę modułu w jakim znajduje sie jej kod (węzeł ModuleSpecificationId), nazwę klasy w jakiej została zdefiniowana (węzeł TypeName), nazwę metody (węzeł MethodName) oraz pełną sygnaturę metody (węzeł MethodId). Jeśli podamy błędne dane nasze zdarzenie nie zadziała dlatego trzeba być uważnym.

Większą swobodę dają nam węzły ShortDescription oraz LongDescription definiujące opis podstawowy/skrócony i rozszerzony zdarzenia. O co dokładnie chodzi? Zostało to pokazane na poniższym obrazku przedstawiającym zdarzenia zarejestrowane przez IntelliTrace w czasie monitorowania programu.



Opis skrócony zdarzenia widoczny jest zawsze, podczas gdy opis rozszerzony dopiero po kliknięciu zdarzenia. Opis te mogą zawierać dowolne napisy, a także odwoływać się do wyniku zwróconego przez metodę, do wartości parametrów wywołania czy też do składowych klasy. O tym jakie informacje są dostępne dla zdarzenia decyduje zawartość węzła DataQueries. Może on zawierać dowolną liczbę podwęzłów DataQuery. Każdy podwęzeł o takiej nazwie definiuje coś w rodzaju zapytania, które zwróci np.: wartość parametru wywołania metody. Aby odwołać się do jakiegoś zapytania używamy składni {index} gdzie index to numer zapytania w numeracji od zero. Znaczenie poszczególnych atrybutów jest następujące.

  • index Przyjmuje odpowiednio wartości: -1 gdy odwołujemy się do wyniku zwracanego przez metodę, 0 gdy odwołujemy się do obiektu na rzecz, którego została wywołana metoda lub do pierwszego parametru jeśli to metoda statyczna, 0,1,2... gdy odwołujemy się do kolejnych parametrów wywołania metody.
  • maxSize Określa maksymalny rozmiar wyniku zwróconego przez zapytanie. Jeśli zostanie przekroczony to wynik zostanie obcięty.
  • type Typ wyniku. Na przykład jeśli pobieramy wartości parametru wywołania metody, który jest typu String to wartość tego atrybutu powinna być równa String.
  • name, _locID, _locAttrData Z mojego doświadczenia wynika, że można tu wpisać co kolwiek. Osobiście jednak wypełniając te atrybuty wzoruję się na innych definicjach zdarzeń.
  • query Atrybut używany kiedy chcemy odwołać się do składowej obiektu. W najprostszej postaci zawiera po prostu nazwę składowej.
Skoro już wiemy jak zbudowana jest definicja zdarzenia umieśćmy ją w pliku CollectionPlan.xml jako dziecko węzła DiagnosticEventSpecifications. Teraz konieczne jest ponowne uruchomienie VS 2010 i już możemy cieszyć się nowym zdarzeniem. Nic trudnego, prawda?

Na koniec ostrzeżenie. Przy dodawaniu nowych zdarzeń należy zachować umiar. Domyślny zestaw zdarzeń został dobrany tak aby nie wpływać na wydajność monitorowanego programu. Jeśli dodamy zdarzenie dla metody wywoływanej bardzo często wpłynie to negatywnie na jego wydajność podczas pracy z IntelliTrace.

W następnym poście na temat IntelliTrace pokażę jak dodać zdarzenie związane z naszą własną metodą, w jaki użyć węzłów DataQuery aby odwołać się do składowej obiektu oraz jak analizować wystąpienia zdarzeń w sposób programistyczny.

07/05/2010

ShowDialog i zwalnianie zasobów

Home

TestForm form = new TestForm();

if (form.ShowDialog() == DialogResult.OK)
{
  ...
}
Czy powyższy króciutki fragment kodu powodujący wyświetlenie okna dialogowego jest poprawny? Niestety, jeszcze do niedawna powiedziałbym bez mrugnięcia oka, że oczywiście tak. Niestety ponieważ ta odpowiedź jest niepoprawna. Do tego aby ten kod był poprawny brakuje niewielkiej ale za to bardzo istotnej rzeczy i zostało to pokazane poniżej:

using(TestForm form = new TestForm())
{
  if (form.ShowDialog() == DialogResult.OK)
  {
    ...
  }
}
Zamiast klauzuli using można oczywiście wywołać Dispose. Dlaczego jednak jawne zwolnienie zasobów jest w ogóle potrzebne? Czy to oznacza, że za każdym razem kiedy chcemy wyświetlić jakieś okno musimy pamiętać o klauzuli using?

Odpowiedź brzmi nie, jest to konieczne tylko jeśli wyświetlamy okno dialogowe. W takim wypadku po jego zamknięciu nie następuje wywołanie metody Close, która zwolni za nas zasoby. Dzięki temu możemy użyć tego samego okna ponownie. Nie jest to żadna wiedza tajemna i można o tym przeczytać w dokumentacji metody ShowDialog:

Unlike modeless forms, the Close method is not called by the .NET Framework when the user clicks the close form button of a dialog box or sets the value of the DialogResult property.

Kolejny raz przekonuję się, że nawet jeśli czegoś używamy bardzo często i jest to dla nas coś prostego i oczywistego to nie znaczy to, że robimy to na 100% dobrze :)

10/04/2010

.NET Reflector Pro

Home

.NET Reflector Pro to najnowsza odsłona dobrze znanego programistą .NET narzędzia, firmy redgate, ktora pojawiła się na rynku 15 lutego. Do tej pory nie miałem okazji się z nią zapoznać ale ostatnio nadrobiłem zaległości i jestem zachwycony nowymi możliwościami programu. Co mi się tak spodobało?

Otóż .NET Reflector Pro to plugin integrujący się z środowiskiem Visual Studio, który pozwala na dekompilację pakietów C#/VB w locie, a następnie ich debugowanie! Innymi słowy jeśli korzystamy z bibliotek jakiegoś zewnętrznego dostawcy ale nie posiadamy jej źródeł, a z jakiegoś powodu chcemy ją zdebugować postępujemy następująco:
  • Wybieramy polecenie Choose Assemblies To Debug z menu .NET Reflector w Visual Studio.
  • Zaznaczamy interesujące nas pakiety.
  • Klikamy OK.
  • Rozpoczynamy debugowanie, a niewidoczny do tej pory kody stoi dla nas otworem. Możemy stawiać pułapki, podglądać wartości zmiennych itd.
Proste, prawda? Bardzo lubię takie narzędzia, minimum wysiłku i maksimum korzyści. Niestety ale plugin jest płatny - można jednak testować go przez okres 14 dni (do pobrania tutaj). Szczerze polecam!

03/04/2010

Po konferencji

Home

Od mojej sesji na temat IntelliTrace podczas Warszawskich Dni Informatyki minęły już prawie trzy tygodnie. Pora odpowiedzieć na kilka pytań: Jak było? Jak spisali się organizatorzy? Czy jestem zadowolony ze swojej prezentacji? Co wypadło dobrze, a co źle?
Zacznę od tego, że organizatorzy czyli Student Partners z warszawskich uczelni stanęli na wysokości zadania i przygotowali w pełni profesjonalną konferencję. Wydarzenie było dobrze rozreklamowane i frekwencja dopisała. Zadbano o profesjonalne nagłośnienie. Prelegenci mogli spokojnie przygotować się do wystąpienia w zarezerwowanym dla nich pokoju. Nie chodzili też głodni i spragnieni, uczestnicy zresztą również nie ;) Każdy dostał materiały konferencyjne, w tym pendriv'a. Można było wygrać laptopa, a w czasie przerw pograć na konsoli XBOX. Zorganizowano też konkurs Guitar Hero oraz przygotowano ankiety.

Mam tylko kilka uwag ale niewielkiego kalibru. Wolałbym aby impreza odbyła się bliżej centrum albo jakiejś stacji metra ale zdaję sobie sprawę, że to nie taka prosta sprawa. Po drugie obsługa konferencji mogłaby rozpoznawać prelegentów i nie kazać się im rejestrować :) Dziewięć osób to nie jest znowu taka duża liczba. Największe zastrzeżenie mam do rzutnika, który przynajmniej podczas mojej sesji nie spisał sie dobrze i jakość obrazu była słaba.

Jak oceniam swoją sesję? W skrócie jestem zadowolony. Przed konferencją byłem trochę zdenerwowany ale zaprocentowało dobre przygotowanie i wcześniejsze przećwiczenie prezentacji. Dzięki temu kiedy zacząłem mówić nerwy odpuściły i poczułem się swobodniej. Plan prezentacji, układ i treść slajdów miałem w głowie i nie musiałem zastanawiać się kiedy co powiedzieć. Sądzę, że mówiłem głośno i wyraźnie. Udały się również wszystkie zaplanowane pokazy IntelliTrace, Visual Studio 2010 i przygotowanych programów. W czasie i po sesji uczestnicy zadawali pytania.

Oczywiście zdaję sobie sprawę z kilku niedociągnięć. Co mi nie wyszło? Po pierwsze po konferencji zwrócono mi uwagę na błąd ortograficzny na jednym ze slajdów. Na swoją obronę mogę powiedzieć, że Power Point też go nie zauważył. Po drugie nie ustrzegłem się błędu w wymowie słowa debugger oraz zapomniałem powiedzieć o jednej czy dwóch rzeczach. Nagrania swojej sesji jeszcze nie widziałem ale zdaję sobie sprawę, że pomimo wszystko byłem zdenerwowany - zdarzyło mi się zaciąć czy powiedzieć coś niezrozumiale. Powinienem również zachowywać się swobodniej.

Podsumowując konferencję oceniam na bardzo mocne 5, brawo dla organizatorów. Swojej sesji nie podejmują się oceniać ale chętnie wysłucham waszych opinii i uwag.

Chciałbym również podziękować żonie za pomoc w przygotowaniach do konferencji, cierpliwe wysłuchanie i cenne uwagi.