05/06/2012

CLR Profiling API - dobrego złe początki

Home

Jakiś czas temu odpowiadając na komentarz pod postem przebąknąłem, że przymierzam się do wzięcia byka za rogi i chcę napisać własny profiler korzystając z CLR Profiling API. Zagadnienie nie jest trywialne dlatego postanowiłem zacząć od zapoznania się z "literaturą". Na pierwszy ogień poszedł artykuł Write Profilers With Ease Using High-Level Wrapper Classes. Swoje lata ma, chociaż na liście mam jeszcze starsze artykuly, ale akurat w przypadku CLR  Profiling API lata biegną wolniej niż w przypadku innych technologii.

Do artykułu, jakby inaczej, dołączony jest kod, który postanowiłem jak najszybciej uruchomić aby zobaczyć czy to rzeczywiście działa. W tym momencie natknąłem się na pierwszy problem i o tym będzie ten post. Problem wynikał z tego, że chciałem "sprofilować" aplikację używającą .NET 4.0 kodem napisanym z myślą o .NET 2.0. Z biegu nie jest to możliwe i objawia się takim błędem w logu systemowym:

.NET Runtime version 4.0.30319.225 - Loading profiler failed. The profiler that was configured to load was designed for an older version of the CLR. You can use the COMPLUS_ProfAPI_ProfilerCompatibilitySetting environment variable to allow older profilers to be loaded by the current version of the CLR. Please consult the documentation for information on how to use this environment variable, and the risks associated with it. Profiler CLSID: '{B98BC3F3-D630-4001-B214-8CEF909E7BB2}'. Process ID (decimal): 5128. Message ID: [0x2517].


Łatwo jednak sobie z tym poradzić, wystarczy ustawić zmienną środowiskową COMPLUS_ProfAPI_ProfilerCompatibilitySetting. Uruchomienie programu pod kontrolą profiler'a będzie więc wyglądało w następujący sposób:

rem Rejestrujemy dll'kę z profiler'em
regsvr32 /s MyProfiler.dll

rem Włączenie profilowania
set COR_ENABLE_PROFILING=1

rem Ustawiamy profiler, który chcemy użyć (zarejestrowaliśmy go 2 linijki wcześniej)
set COR_PROFILER={32E2F4DA-1BEA-47ea-88F9-C5DAF691C94A}

rem Włączamy kombatybilność wstecz
COMPLUS_ProfAPI_ProfilerCompatibilitySetting=EnableV2Profiler

rem Uruchamiamy program
MyProgram.exe

rem Profiler nie jest już potrzebny, więc go wyrejestrowujemy
regsvr32 /s /u MyProfiler.dll

Ale to nie wszystko. Drugi rodzaj błędu z jakim się spotkałem objawia się następującym wpisem w logu systemowym:

.NET Runtime version 4.0.30319.225 - Loading profiler failed during CoCreateInstance. Profiler CLSID: '{B98BC3F3-D630-4001-B214-8CEF909E7BB2}'. HRESULT: 0x80070002. Process ID (decimal): 2620. Message ID: [0x2504].

I może wynikać z dwóch rzeczy. Po pierwsze jeśli nasz program jest programem 64 bitowym, do jego profilowania musimy użyć 64 bitowej wersji profilera. Sprawa ma się podobnie, jeśli program jest 32 bitowy. W przeciwnym wypadku środowisko nię będzie potrafiło załadować odpowiedniej biblioteki. Błąd pojawi się również jeśli spróbujemy uruchomić profiler nie posiadając praw administratora (Windows Vista z włączonym UAC). A więc uruchamiając skrypt należy skorzystać z opcji Uruchom jako administrator.

24/05/2012

Krótka lekcja na temat char i varchar

Home

Na początek prosty kawałek kodu w T-SQL, w którym sprawdzane jest, czy zadany ciąg znaków pasuje do podanego wzorca tj. czy zaczyna się dwoma cyframi:

declare @input char(10)
declare @pattern char(100)

SET @input = '12aaabbb'
SET @pattern = '[0-9][0-9]%'

if @input like @pattern
 print 'OK'
else
 print 'Fail'

Pomimo, że ciąg znaków pasuje do wzorca to warunek dopasowania nie jest spełniony i na ekran zostanie wypisany napis Fail. Dzieje się tak ponieważ kiedy przypisujemy wzorzec do zmiennej @pattern, która jest typu char(100) to zostanie on dopełniony spacjami. A więc przy testowaniu warunku tak naprawdę sprawdzamy czy zadany ciąg znaków zaczyna się dwoma cyframi i kończy przynajmniej 89 spacjami (100 - długość wzorca).

Można to naprawić przechowując wzorzec w zmiennej typu varchar albo stosując funkcję rtrim. Wszystko zależy od konkretnej sytuacji ale przy wykonywaniu różnych operacji na ciągach znaków należy zawsze pamiętać o różnicy pomiędzy char i varchar. Sprawa wydaje się prosta ale kiedy pracujemy z dużą ilością kodu bazodanowego łatwo można coś przeoczyć, a znalezienie takiego błędu może nie być wbrew pozorom proste.

13/05/2012

Podglądanie tabel tymczasowych

Home

Czasami zdarza się, że pracujemy z aplikacją, która tworzy tabelę tymczasową, a następnie woła serię procedur składowanych, które: wypełniają tę tabelę, modyfikują ją itd. W takim wypadku dość często np.: przy debugowaniu błędu, potrzeba podejrzeć zawartość takiej tabeli. Pół biedy kiedy jest to globalna tabela tymczasowa np.: ##SomeTempTable. Wtedy wystarczy zatrzymać aplikację na pułapce i wykonać zapytanie w SQL Server Management Studio np.: select * from ##SomeTempTable.

Niestety, jeśli globalna tabela tymczasowa została utworzona w transakcji, to już nie zadziała. Jeszcze gorzej jest w przypadku lokalnych tabel tymczasowych np.: #SomeTempTable, których nie podejrzymy nawet jeśli zostały utworzone poza transakcją. Co w takim wypadku?

Ja w takiej sytuacji korzystam z możliwości Visual Studio, a dokładniej z potęgi okna Immediate (Debug -> Windows -> Immediate Ctrl + D, I). Zakładam, że wykonując poniższe kroki aplikacja zatrzymana jest na jakiejś pułapce np.: przed uruchomieniem kolejnej procedury robiącej coś z tabelą tymczasową.
  • W oknie Immediate wpisuje DataTable dt = new DataTable();
  • Również w oknie Immediate, wpisuję dt = DataProvider.Instance.ExecuteDataTable("select * from #SomeTempTable");.
  • Przechodzę np.: do okna Watch, podaję nazwę utworzonej zmiennej czyli dt i cieszę się wbudowanym w VS wizualizatorem dla klasy DataTable.


Kilka słów wyjaśnienia w sprawie DataProvider.Instance.ExecuteDataTable. Zakładam tutaj, że jeśli ktoś pisze aplikację bazodanową i nie korzysta z ORM, to ma napisaną jakąś pomocnicza klaskę, która: zarządza połączeniem do bazy danych, pozwala łatwo wykonać zapytania bez potrzeby każdorazowego ręcznego tworzenia DbCommand itd. W tym przypadku jest to klasa DataProvider, która jest singleton'em. Jest to o tyle ważne, że kiedy odwołuję się do niej w oknie Immediate to korzystam z tego samego połączenia co aplikacja, a więc mogę podejrzeć tabele tymczasowe. Pod spodem wykonywane jest zwykłe zapytanie w stylu ADO.NET.

Ostatnio odkryłem również fajny projekt sp_select składający się z dwóch procedur składowanych, które w "magiczny" sposób pozwalają podejrzeć lokalną tabelę tymczasową korzystając z innego połączenia niż to, w którym tabela została utworzona.

09/05/2012

Dziwne zachowanie konstruktora statycznego - ciąg dalszy

Home

Na początku marca pisałem o "dziwnym" zachowaniu konstruktora statycznego. W skrócie chodziło o to, że jeśli chcieliśmy wywołać metodę w domenie aplikacyjnej innej niż domyślna, konstruktor statyczny klasy, do której należała ta metoda, wołany był o jeden raz więcej w środowisku x64 niż w środowisku x86. Ponieważ sprawa mnie nurtowała postanowiłem zadać pytanie poprzez portal Microsoft Connect. Zajęło to trochę ale w końcu uzyskałem wyjaśnienie. Odpowiedź jest dość ciekawa, a więc zapraszam do lektury.

08/05/2012

RavenDB (cz. 7) - HttpListenerException

Home

Ten post będzie krótki i zwięzły. Jakiś czas temu kiedy włączyłem UAC (User Account Control) ze zdziwieniem zauważyłem, że moja aplikacja używająca Raven DB nie działa. Przy wywołaniu metody DocumentStore.Initialize rzucany był wyjątek HttpListenerException. Po wyłączeniu UAC błąd nie występował.

Z problem łatwo sobie poradzić nadając użytkownikowi, jaki uruchamia aplikację, uprawnienia do nasłuchiwania na porcie używanym przez bazę danych. Można to zrobić przy pomocy narzędzia httpcfg lub netsh tak jak to zostało opisane w tym dokumencie. Co jednak najlepsze Raven DB dostarcza gotowej klasy NonAdminHttp, w bibliotece Raven.Database.dll, która rozwiązuje ten problem. Poniżej przykład użycia.

...
NonAdminHttp.EnsureCanListenToWhenInNonAdminContext(Store.Configuration.Port);

Store.Initialize();
...

A tak w ogóle to warto zajrzeć, przy pomocy jakiegoś deasemblera, do wnętrza metody EnsureCanListenToWhenInNonAdminContext i zobaczyć jak została zaimplementowana.

Podsumujmy co już umiemy:
  • Osadzić Raven DB w aplikacji hostującej.
  • Zainicjować Raven DB.
  • Skonfigurować dostęp do Raven Studio i API REST'owego.
  • Tworzyć obiekty POCO jakie mogą zostać umieszczone w Raven DB.
  • Dodawać/usuwać/modyfikować dokumenty.
  • Zadawać proste i te trochę bardziej skomplikowane zapytania.
  • Utworzyć indeks.
  • Skorzystać z algorytmu Map/Reduce.
  • Skorzystać z zapytań Lucene.
  • Wymusić zwrócenie przez zapytanie aktualnych danych.
  • Sterować tym, które właściwości zostaną zapisane do bazy danych.
  • Rozwiązać kłopoty związane z IntelliTrace i Raven DB.
  • Rozwiązać problem z HttpListenerException.