niedziela, styczeń 30, 2011

Debugging na produkcji–narzędzia–MDbg

W poprzednim wpisie pokazaliśmy sobie jak możemy zmusić Visual Studio do zdalnego debuggowania naszej aplikacji na produkcji. W tym wpisie przyjrzymy się MDbg. MDbg - to prosty, konsolowy debugger przeznaczony do śledzenia kodu aplikacji zarządzanych (tylko i wyłącznie – nie ma wsparcia dla aplikacji natywnych).

MDbg jest częścią instalacji Visual Studio lub Windows SDK. Działa także bez instalacji, tak więc możemy go nagrać na przenośną pamięć i nosić ze sobą w razie potrzeby. Poniżej Mdbg w działaniu – lista komend.

mdbg

Jak zacząć z nim pracę? Po uruchomieniu należy podpiąć się pod nasz proces. Wykorzystujemy do tego polecenie           (a[ttach]). Jeśli uruchomimy ją bez podania PID procesu MDbg wypisze nam wszystkie procesy zarządzane, aktualnie uruchomione. Następnie pozostaje już tylko dotrzeć do interesujących nas fragmentów aplikacji. Możemy do tego użyć takich poleceń jak: w[here] – drukuje stos wywołań, b[reak] – wyświetla/ustawia pułapki, u[p]/d[own] x – ustawia aktywną ramkę stosu (można podać o ile ma się przenieść), sh[ow] – wyświetla kod źródłowy w obrębie aktywnej instrukcji.

Zobaczymy jak z jego pomocą możemy zdiagnozować problem w aplikacji (choć w tym przypadku chyba łatwiej po prostu zerknąć na nią w Reflector’ze – tu jednak w celach pokazowych użyjemy MDbg).

Dostaliśmy informację, że nasza super inteligentna aplikacja licząca silnię działa czasami niepoprawnie. Widzimy wyniki: 5! – 120, 8! – 40320, 10! – 20922789888000. Trochę za dużo. Tak więc działamy: a 1416 i podpinamy się do naszego procesu. Za pomocą where zobaczymy stack trace i widzimy, że aplikacja czeka na wprowadzenie danych. Następnie up 5 – przechodzimy do kodu naszej aplikacji. Wtedy już tylko sh 10 - wyświetlamy 10 linijek kodu i widzimy interesujący fragment.

int num = int.Parse(input, System.Globalization.NumberStyles.HexNumber);

Console.WriteLine(Fac(num));

Widzimy więc, że nasz programista parsuje sobie liczby jako hex, dlatego wprowadzając 10 tak na prawdę liczmy 16!. Sprawdźmy… Uruchamiamy aplikację dalej (g) i wprowadzamy wartość ‘a’. Ukazuje nam się wynik 3628800 czyli poprawny.

Na koniec mała uwaga. Mdbg został napisany w kodzie zarządzanym i jeśli kogoś to interesuje to może sobie podejrzeć za pomocą Reflector’a jak wygląda kod tej aplikacji. Zachęcam.

W następnym wpisie (mam nadzieję, że tyle nie trzeba będzie na niego czekać) WinDbg.

Miłego debuggowania!

piątek, styczeń 14, 2011

Debugging na produkcji – Narzędzia


Czasami zdarzają się sytuacje, że pomimo usilnych prób nie możemy zreprodukować błędu u siebie na maszynie lokalnej. Musimy sprawdzić dlaczego nasza aplikacji źle działa na maszynie produkcyjnej. W tym wpisie postaram się przedstawić jakie narzędzia mamy do dyspozycji jeśli jeśli taka potrzeba zajdzie. Zobaczmy co jest zatem dostępne.

Visual Studio Remote Debugging

remote debuggingPierwszą naszą opcją jest Remote debugging dostępny w VisualStudio. Wraz z zainstalowanym VS instalują nam się komponenty, które możemy wykorzystać do zdalnego podłączenia się z serwerem i uruchomienia aplikacji właśnie tam; podczas, gdy pułapki i zmienne możemy sobie oglądać w naszym lokalnym VisualStudio. Jak to zrobić? Przede wszystkim na maszynie zdalnej musimy uruchomić Visual Studio Remote Debugging Monitor. Jest to małe narzędzie, które będzie monitorować przychodzące zdalne sesje debuggowania i nimi zarządzać.

vsrdm
Następnie możemy, albo uruchomić aplikację z naszego VS zdalnie albo też podpiąć się do już działającego procesu na maszynie. Zobaczmy jak skonfigurować to pierwsze. Uruchamiamy projekt naszej aplikacji i przechodzimy do właściwości. Na karcie debug wypełniamy odpowiednio. Zaznaczamy opcję Start external program i wprowadzamy ścieżkę do naszej aplikacji – taką, jaka jest na zdalnej maszynie! Następnie zaznaczamy opcję Use remote machine i wprowadzamy nazwę serwera, którą podał nam Remote Debugging Monitor. Zapisujemy ustawienia i uruchamiamy naszą aplikację. W czasie uruchamiania możemy jeszcze otrzymać komunikat o cache’owaniu symboli na zdalnej maszynie i być może będziemy musili zmodyfikować ustawienia w Option->Debugging->Symbols. Po zapoznaniu się z tą informacją nasza aplikacja jest uruchamiana na zdalnej maszynie a breakpointy, podgląd zmiennych i wszystko inne co związane z debuggowaniem widzimy w naszym lokalnym VS. Dla przykładu porównanie tego co zwraca Environment.MachineName z nazwą komputera w systemie.

p01

Druga opcja, tak jak już wspomniałem wcześniej, to podłączenie się do działającego już procesu na maszynie zdalnej. W tym celu otwieramy okno ‘Debug – Attach’ a następnie w polu Qualifier wpisujemy nazwę serwera sesji zdalnego debuggowania czyli w moim przypadku pawlos@PAWLOS-LAPTOP i po odświeżeniu listy procesów możemy podpiąć się do naszego procesu uruchomionego na zdalnej maszynie. Dalej już podobnie jak w przypadku poprzednim, działamy tak jakbyś debuggowali naszą aplikację lokalnie.

Plus? Minusy?

Jakie korzyści płyną z takiego rozwiązania? Przede wszystkim pozostajemy w VS, które większość z nas zna dość dobrze. Dzięki temu nie trzeba poznawać nowych narzędzi (choć to czasem minus), aby rozpoznać dlaczego nasza aplikacja nie działa. Plusem jest też, iż pomimo tego, że Visual Studio Remote Debugging Monitor jest instalowany razem z Visual Studio to możemy go nagrać na pendrive’a i mieć go ze sobą zawsze (ja mam na swoim zestawie niezbędnych aplikacji). Wady? Problemy z firewallem w Windows, który blokuje połączenia pomiędzy maszynami co czasem może skutecznie uniemożliwić skorzystanie z tego rozwiązania. Czy wtedy pozostajemy bez wyjścia? Nie. W następnej części zobaczymy co oferuje nam mDbg.

Miłego debuggowania.

poniedziałek, styczeń 03, 2011

SqlStatistics w LINQu

Kilka dni temu w poście Statystyka… zapowiedziałem, iż postaram się zaproponować kawałki kodu, które umożliwią włączenie statystyk dla zapytań LINQ to SQL. Zadanie okazało się trochę trudniejsze niż myślałem, ale udało się coś osiągnąć. Zobaczmy jak.

Rozwiązanie Naïve

Pierwsze co przychodzi na myśl to proste rozszerzenie obiektu DataContext o nasze metody. Mniej więcej tak:

public static class DataContextExt

{

    public static T WithStatistics<T>(this T context) where T: DataContext

    {

        EnableStatistics(context);

        return context;

    }

 

    [Conditional("DEBUG")]

    private static void EnableStatistics<T>(T context) where T: DataContext

    {

        if (context != null && context.Connection as SqlConnection != null)

            (context.Connection as SqlConnection).StatisticsEnabled = true;

    }

 

    [Conditional("DEBUG")]

    public static void PrintStatistics<T>(this T context, TextWriter writer, bool reset) where T: DataContext

    {

        if (context != null && context.Connection as SqlConnection != null)

        {

            var connection = context.Connection as SqlConnection;

            var stats = connection.RetrieveStatistics();

 

            Array.ForEach(stats.Cast<DictionaryEntry>().ToArray(),

                    entry => writer.WriteLine(string.Format("Klucz: {0}, Wartość: {1}",

                                                              entry.Key, entry.Value)));

            writer.WriteLine();

 

            if (reset)

                connection.ResetStatistics();

        }

    }

}

Co robimy powyżej? Rozszerzamy obiekt, który jest typu DataContext o dwie metody jak poprzednio. Pierwsza wyciąga SqlConnection i ustawia generowanie statystyk, druga drukuje raport. Nic tu nadzwyczajnego. A uruchomienie?

var dc = new DataClasses1DataContext().WithStatistics();

var query = (from t in dc.tests

            where t.t2.Contains("12")

            select t);

foreach (var test in query)

{

    Console.WriteLine(test.t1);

}

dc.PrintStatistics(Console.Out, false); 

Statystyki włączamy wywołując metodę .WithStatistics. Aby wydrukować statystyki posługujemy się tym samym DataContext. Jakie minusy? Brakuje nam informacji o tym jakie zapytanie wywołaliśmy – ono znajduje się w obiekcie query. Czemu zatem nie dodamy metody PrintStatistics? Niestety nie możemy tędy dostać się do obiektu SqlConnection. Przynajmniej nie bezpośrednio.

Rozwiązanie zaawansowane

Tutaj na początku wielkie podziękowania dla Marcina Najdera za podanie rozwiązania. W zasadzie 99,9% kodu poniżej to dzieło Marcina. Chcielibyśmy, aby nasz kod można było wywołać w następujący sposób:

var query = (from t in dc.tests.WithStatistics()

            where t.t2.Contains("12")

            select t);

foreach (var test in query)

{

    Console.WriteLine(test.t1);

}

query.PrintStatistics(Console.Out, true);

var q2 = query.Where(t => t.t2.Contains("123"));

foreach (var test in q2)

{

    Console.WriteLine(test.t1);

}

q2.PrintStatistics(Console.Out, true);

Czyli pominięcie operowania na DataContex. Niestety, aby to osiągnąć musimy napisać własny provider LINQu. Fajnie :)

public static IQueryable<T> WithStatistics<T>(this IQueryable<T> query, SqlConnection connection)

{

    return new StatsQueryable<T>(query, connection.WithStatistics());

}

 

public static IQueryable<T> WithStatistics<T>(this Table<T> table)

    where T : class

{

    return table.WithStatistics((SqlConnection)table.Context.Connection);

}

To nasze główne metody. Reszta to niestety “plumbing code”, który musimy wykonać. A zatem co robimy? Rozszerzamy obiekt Table<T>, i zwracamy nasz nowy obiekt StatsQueryable. Zapamiętujemy sobie w nim także nasze połączenie, tak abyśmy mogli później wydrukować informacje dotyczące zapytania. Reszta kodu czyli nasz provider LINQ, który tak na prawdę robi niewiele (nic) a całą pracę deleguje do elementów, które otrzymał w konstruktorze czyli Query oraz SqlConnection.

[Conditional("DEBUG")]

public static void PrintStatistics<T>(this IQueryable<T> query, TextWriter writer, bool reset)

{

    var myQueryable = query as StatsQueryable<T>;

    if (myQueryable != null)

        myQueryable.SqlConnection.PrintStatistics(writer, reset, query.ToString());

}

 

private class StatsQueryable : IQueryable

{

    protected readonly IQueryable Query;

    internal readonly SqlConnection SqlConnection;

 

    public StatsQueryable(IQueryable query, SqlConnection sqlConnection)

    {

        Query = query;

        SqlConnection = sqlConnection;

    }

 

    IEnumerator IEnumerable.GetEnumerator()

    {

        return Query.GetEnumerator();

    }

    public Expression Expression

    {

        get { return Query.Expression; }

    }

    public Type ElementType

    {

        get { return Query.ElementType; }

    }

 

    public IQueryProvider Provider

    {

        get { return new StatsProvider(Query.Provider, SqlConnection); }

    }

 

    public override string ToString()

    {

        return Query.ToString();

    }

}

 

private class StatsQueryable<T> : StatsQueryable, IQueryable<T>

{

    public StatsQueryable(IQueryable<T> query, SqlConnection connection)

        : base(query, connection)

    {

    }

 

    public IEnumerator<T> GetEnumerator()

    {

        return ((IQueryable<T>)Query).GetEnumerator();

    }

}

 

private class StatsProvider : IQueryProvider

{

    private readonly IQueryProvider Provider;

    private readonly SqlConnection SqlConnection;

 

    public StatsProvider(IQueryProvider provider, SqlConnection sqlConnection)

    {

        Provider = provider;

        SqlConnection = sqlConnection;

    }

 

    public IQueryable CreateQuery(Expression expression)

    {

        return new StatsQueryable(Provider.CreateQuery(expression), SqlConnection);

    }

 

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)

    {

        return new StatsQueryable<TElement>(Provider.CreateQuery<TElement>(expression), SqlConnection);

    }

 

    public object Execute(Expression expression)

    {

        return Provider.Execute(expression);

    }

 

    public TResult Execute<TResult>(Expression expression)

    {

        return Provider.Execute<TResult>(expression);

    }

}

I wszystko działa pięknie. Wynik działania:

resultLinq

Jakby ktoś potrzebował – pełny plik do pobrania: SqlStatisticsExtensions.cs

Gdyby ktoś szukał fajnych pomocy do LINQu to Marcin napisał własny provider, który umożliwia wyświetlanie naszego zapytania w rozbiciu na poszczególne części, które pozwalają nam prześledzić co dzieje się w środku - Debugging Reactive Framework (RxDebugger) and Linq to objects (LinqDebugger).

Miłego kodowania!

sobota, styczeń 01, 2011

Podsumowanie bloga - 2010

Tym razem będzie o statystykach bloga :)

visits

Nie będę pisał co mi się udało a co nie, bo w tym roku za dużo jest tych rzeczy na nie :). Zrobię tylko małe podsumowanie bloga w liczbach.

Top 3 najbardziej poczytnych postów w ciągu całego roku:
1) Visual Studio 2010 & .NET 4 – Pierwsze wrażenie. Post z ….2008 roku :) - 5,368 wyświetleń
2) WPF – Podsumowanie - 2,491 wyświetleń
3) 70-502 (WPF) Przygotowania…nr 1 - 1,069 wyświetleń

PS. Tak na prawdę na drugim miejscu jest strona główna z liczbą 5,100 wyświetleń.

30 postów (strasznie mało…), 27466 wizyt oraz 49430 wyświetleń co daje 1.8 wyświetlań na wizytę.

Dziękuję wszystkim za kolejny rok wspierania bloga. Wszystkiego najlepszego w 2011 i oby był dla was jeszcze lepszy niż miniony 2010…