Korzystanie z liczników wydajności sygnałów w roli sieci Web platformy Azure

Autor Luke Latham

Warning

Ta dokumentacja nie jest najnowsza dla najnowszej wersji usługi sygnalizującej. Zapoznaj się z tematem ASP.NET Core sygnalizujący.

Liczniki wydajności sygnałów są używane do monitorowania wydajności aplikacji w roli sieci Web platformy Azure. Liczniki są przechwytywane przez Diagnostyka Microsoft Azure. Liczniki wydajności dla sygnałów są instalowane na platformie Azure przy użyciu signalr.exe, tego samego narzędzia, które jest używane w przypadku aplikacji autonomicznych lub lokalnych. Ponieważ Role platformy Azure są przejściowe, należy skonfigurować aplikację do instalowania i rejestrowania liczników wydajności sygnałów przy uruchamianiu.

Wymagania wstępne

Tworzenie aplikacji roli sieci Web platformy Azure, która uwidacznia liczniki wydajności sygnałów

  1. Otwórz program Visual Studio.

  2. W programie Visual Studio wybierz pozycję plik > Nowy > projekt.

  3. W oknie dialogowym Nowy projekt wybierz kategorię Visual C# > Cloud z lewej strony, a następnie wybierz szablon usługi w chmurze platformy Azure . Nadaj aplikacji nazwę SignalRPerfCounters i wybierz przycisk OK.

    Nowa aplikacja w chmurze

    Note

    Jeśli nie widzisz kategorii szablonów w chmurze lub szablonu usługi w chmurze Azure , musisz zainstalować obciążenie Programowanie na platformie azure dla programu Visual Studio 2017. Wybierz łącze otwórz Instalator programu Visual Studio w lewej dolnej części okna dialogowego Nowy projekt , aby otworzyć Instalator programu Visual Studio. Wybierz obciążenie Programowanie na platformie Azure , a następnie wybierz pozycję Modyfikuj , aby rozpocząć instalowanie obciążenia.

    Obciążenie Programowanie na platformie Azure w Instalator programu Visual Studio

  4. W oknie dialogowym nowa Microsoft Azure usługa w chmurze wybierz pozycję rola sieci Web ASP.NET i wybierz przycisk >, aby dodać rolę do projektu. Wybierz przycisk OK.

    Dodaj rolę sieci Web ASP.NET

  5. W oknie dialogowym Nowa aplikacja sieci Web ASP.NET — WebRole1 wybierz szablon MVC , a następnie wybierz przycisk OK.

    Dodawanie MVC i interfejsu API sieci Web

  6. W Eksplorator rozwiązańOtwórz plik Diagnostics. Wadcfgx w obszarze WebRole1.

    Eksplorator rozwiązań Diagnostics. wadcfgx

  7. Zastąp zawartość pliku następującą konfiguracją i Zapisz plik:

    <?xml version="1.0" encoding="utf-8"?>
    <DiagnosticsConfiguration xmlns="http://schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration">
      <PublicConfig>
        <WadCfg>
          <DiagnosticMonitorConfiguration overallQuotaInMB="4096">
            <DiagnosticInfrastructureLogs scheduledTransferLogLevelFilter="Error" />
            <Logs scheduledTransferPeriod="PT1M" scheduledTransferLogLevelFilter="Error" />
            <Directories scheduledTransferPeriod="PT1M">
              <IISLogs containerName ="wad-iis-logfiles" />
              <FailedRequestLogs containerName ="wad-failedrequestlogs" />
            </Directories>
            <WindowsEventLog scheduledTransferPeriod="PT1M">
              <DataSource name="Application!*[System[(Level=1 or Level=2 or Level=3)]]" />
              <DataSource name="Windows Azure!*[System[(Level=1 or Level=2 or Level=3 or Level=4)]]" />
            </WindowsEventLog>
            <CrashDumps containerName="wad-crashdumps" dumpType="Mini">
              <CrashDumpConfiguration processName="WaIISHost.exe" />
              <CrashDumpConfiguration processName="WaWorkerHost.exe" />
              <CrashDumpConfiguration processName="w3wp.exe" />
            </CrashDumps>
            <PerformanceCounters scheduledTransferPeriod="PT1M">
              <PerformanceCounterConfiguration counterSpecifier="\Memory\Available MBytes" sampleRate="PT3M" />
              <PerformanceCounterConfiguration counterSpecifier="\Web Service(_Total)\ISAPI Extension Requests/sec" sampleRate="PT3M" />
              <PerformanceCounterConfiguration counterSpecifier="\Web Service(_Total)\Bytes Total/Sec" sampleRate="PT3M" />
              <PerformanceCounterConfiguration counterSpecifier="\ASP.NET Applications(__Total__)\Requests/Sec" sampleRate="PT3M" />
              <PerformanceCounterConfiguration counterSpecifier="\ASP.NET Applications(__Total__)\Errors Total/Sec" sampleRate="PT3M" />
              <PerformanceCounterConfiguration counterSpecifier="\ASP.NET\Requests Queued" sampleRate="PT3M" />
              <PerformanceCounterConfiguration counterSpecifier="\ASP.NET\Requests Rejected" sampleRate="PT3M" />
              <PerformanceCounterConfiguration counterSpecifier="\Processor(_Total)\% Processor Time" sampleRate="PT3M" />
              <PerformanceCounterConfiguration counterSpecifier="\.NET CLR Memory(w3wp)\% Time in GC" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\.NET CLR Exceptions(w3wp)\# of Exceps Thrown / sec" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\.NET CLR LocksAndThreads(w3wp)\# of current logical Threads" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\.NET CLR LocksAndThreads(w3wp)\# of current physical Threads" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\.NET CLR LocksAndThreads(w3wp)\Current Queue Length" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\.NET CLR LocksAndThreads(w3wp)\Contention Rate / sec" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\.NET CLR Memory(w3wp)\# Bytes in all Heaps" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\.NET CLR Memory(w3wp)\# GC Handles" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\.NET CLR Memory(w3wp)\# of Pinned Objects" sampleRate="PT10S" />
    
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Connections Connected" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Connections Reconnected" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Connections Disconnected" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Connections Current" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Connection Messages Received Total" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Connection Messages Sent Total" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Connection Messages Received/Sec" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Connection Messages Sent/Sec" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Message Bus Messages Received Total" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Message Bus Messages Received/Sec" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Scaleout Message Bus Messages Received/Sec" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Message Bus Messages Published Total" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Message Bus Messages Published/Sec" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Message Bus Subscribers Current" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Message Bus Subscribers Total" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Message Bus Subscribers/Sec" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Message Bus Allocated Workers" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Message Bus Busy Workers" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Message Bus Topics Current" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Errors: All Total" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Errors: All/Sec" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Errors: Hub Resolution Total" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Errors: Hub Resolution/Sec" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Errors: Hub Invocation Total" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Errors: Hub Invocation/Sec" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Errors: Tranport Total" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Errors: Transport/Sec" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Scaleout Streams Total" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Scaleout Streams Open" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Scaleout Streams Buffering" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Scaleout Errors Total" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Scaleout Errors/Sec" sampleRate="PT10S" />
              <PerformanceCounterConfiguration counterSpecifier="\SignalR(*)\Scaleout Send Queue Length" sampleRate="PT10S" />
            </PerformanceCounters>
          </DiagnosticMonitorConfiguration>
        </WadCfg>
        <StorageAccount></StorageAccount>
      </PublicConfig>
      <PrivateConfig>
        <StorageAccount name="" key="" endpoint="" />
      </PrivateConfig>
      <IsEnabled>true</IsEnabled>
    </DiagnosticsConfiguration>
    
  8. Otwórz konsolę Menedżera pakietów narzędzia z Tools > Menedżera pakietów NuGet. Wprowadź następujące polecenia, aby zainstalować najnowszą wersję programu sygnalizującego i pakiet narzędzi sygnalizujących:

    install-package microsoft.aspnet.signalr
    install-package microsoft.aspnet.signalr.utils
    
  9. Skonfiguruj aplikację, aby zainstalować liczniki wydajności sygnalizujące w wystąpieniu roli podczas jego uruchamiania lub odtwarzania. W Eksplorator rozwiązańkliknij prawym przyciskiem myszy projekt WebRole1 i wybierz polecenie Dodaj > Nowy folder. Nazwij nowy folder startowy.

    Dodaj folder startowy

  10. Skopiuj plik signalr.exe (dodany z pakietem Microsoft. ASPNET. signaler. <project folder> /SignalRPerfCounters/Packages/Microsoft.ASPNET.SignalR.utils. <version> /Tools do folderu startowego utworzonego w poprzednim kroku.

  11. W Eksplorator rozwiązańkliknij prawym przyciskiem myszy folder startowy i wybierz polecenie Dodaj > istniejący element. W wyświetlonym oknie dialogowym wybierz pozycję signalr.exe i wybierz pozycję Dodaj.

    Dodawanie signalr.exe do projektu

  12. Kliknij prawym przyciskiem myszy utworzony folder startowy . Wybierz pozycję Dodaj > nowy element. Wybierz węzeł Ogólne , wybierz pozycję plik tekstowyi Nadaj nowemu elementowi SignalRPerfCounterInstall. cmd. Ten plik polecenia spowoduje zainstalowanie liczników wydajności sygnalizujących w roli sieci Web.

    Utwórz plik wsadowy instalacji licznika wydajności sygnalizujący

  13. Gdy program Visual Studio tworzy plik SignalRPerfCounterInstall. cmd , zostanie automatycznie otwarty w oknie głównym. Zastąp zawartość pliku następującym skryptem, a następnie Zapisz i zamknij plik. Ten skrypt wykonuje signalr.exe, który dodaje liczniki wydajności sygnalizujące do wystąpienia roli.

    SET SignalR_LogDir=%~dp0Log\
    MKDIR "%SignalR_LogDir%"
    cd %~dp0
    signalr.exe ipc >> "%SignalR_LogDir%SignalR_Log.txt" 2>&1
    net localgroup "Performance Monitor Users" "Network Service" /ADD >> "%SignalR_LogDir%NetworkAdd.txt" 2>&1
    
  14. Wybierz plik signalr.exe w Eksplorator rozwiązań. We właściwościachpliku ustaw opcję Kopiuj do katalogu wyjściowego na zawsze Kopiuj.

    Ustaw wartość Kopiuj do katalogu wyjściowego, aby zawsze była kopiowana

  15. Powtórz poprzedni krok dla pliku SignalRPerfCounterInstall. cmd .

  16. Kliknij prawym przyciskiem myszy plik SignalRPerfCounterInstall. cmd i wybierz polecenie Otwórz za pomocą. W wyświetlonym oknie dialogowym wybierz pozycję Edytor binarny , a następnie wybierz przycisk OK.

    Otwórz za pomocą edytora binarnego

  17. W edytorze binarnym zaznacz wszystkie wiodące bajty w pliku i usuń je. Zapisz i zamknij plik.

    Usuń wiodące bajty

  18. Otwórz plik ServiceDefinition. csdef i Dodaj zadanie uruchamiania, które powoduje wykonanie pliku SignalrPerfCounterInstall. cmd podczas uruchamiania usługi:

    <?xml version="1.0" encoding="utf-8"?>
    <ServiceDefinition name="SignalRPerfCounters" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition" schemaVersion="2015-04.2.6">
      <WebRole name="WebRole1" vmsize="Small">
        <Startup>
          <Task commandLine="Startup\SignalRPerfCounterInstall.cmd" executionContext="elevated" taskType="background" />
        </Startup>
        <Sites>
          <Site name="Web">
            <Bindings>
              <Binding name="Endpoint1" endpointName="Endpoint1" />
            </Bindings>
          </Site>
        </Sites>
        <Endpoints>
          <InputEndpoint name="Endpoint1" protocol="http" port="80" />
        </Endpoints>
      </WebRole>
    </ServiceDefinition>
    
  19. Otwórz Views/Shared/_Layout.cshtml i Usuń skrypt pakietu jQuery z końca pliku.

    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>
        </footer>
    </div>
    
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @RenderSection("scripts", required: false)
    </body>
    </html>
    
  20. Dodaj klienta języka JavaScript, który stale wywołuje increment metodę na serwerze. Otwórz Views/Home/Index.cshtml i Zastąp zawartość następującym kodem:

    @{
        ViewBag.Title = "Home Page";
    }
    
    <script src="~/Scripts/jquery-1.10.2.min.js"></script>
    <script src="~/Scripts/jquery.signalR-2.2.1.min.js"></script>
    <script src="~/signalr/hubs" type="text/javascript"></script>
    
    <div id="body">
        <section class="featured">
            <div class="content-wrapper">
                <p>
                    Hello World!
                </p>
                <div style="font-size:large;">
                    My Counter: <span id="counter"></span>
                </div>
            </div>
        </section>
        <section class="content-wrapper main-content clear-fix"></section>
    </div>
    
    <script type="text/javascript">
      $(document).ready(function () {
        var hub = $.connection.myHub;
    
        hub.client.sendResult = function (x) {
          console.log('sendResult(' + x + ')');
          $("#counter").text(x);
          window.setTimeout(function () {
            hub.server.increment(x);
          }, 1000);
        };
    
        $.connection.hub.connected = function () {};
        $.connection.hub.disconnected = function () {};
    
        $.connection.hub.stateChanged(function (change) {
          console.log('new State' + change.newState);
          if (change.newState === $.signalR.connectionState.disconnected) {
            $.connection.hub.start();
          }
          if (change.newState === $.signalR.connectionState.reconnecting) {
            console.log('Re-connecting');
          } else if (change.newState === $.signalR.connectionState.connected) {
            console.log('The server is online');
          }
        });
    
        $.connection.hub.error(function (error) {
          console.log('error ' + error);
        });
        
        $.connection.hub.logging = true;
        
        $.connection.hub.reconnected(function () {
          console.log('Reconnected');
          hub.server.increment(0);
        });
    
        $.connection.hub.start().done(function () {
          console.log('hub started');
          hub.server.increment(0);
        });
      });
    </script>
    
  21. Utwórz nowy folder w projekcie WebRole1 o nazwie Hubs. Kliknij prawym przyciskiem myszy folder Hubs w Eksplorator rozwiązań a następnie wybierz pozycję Dodaj > nowy element. W oknie dialogowym Dodaj nowy element wybierz kategorię sygnalizacja sieci Web > SignalR , a następnie wybierz szablon elementu centrum sygnału (v2) . Nazwij nowe centrum MyHub.cs i wybierz pozycję Dodaj.

    Dodawanie klasy centrum sygnałów do folderu Hubs w oknie dialogowym Dodaj nowy element

  22. MyHub.cs zostanie automatycznie otwarta w oknie głównym. Zastąp zawartość następującym kodem, a następnie Zapisz i zamknij plik:

    using System.Threading.Tasks;
    using Microsoft.AspNet.SignalR;
    
    namespace WebRole1.Hubs
    {
        public class MyHub : Hub
        {
            public async Task Increment(int x)
            {
                await this.Clients.Caller.sendResult(x + 1);
            }
        }
    }
    
  23. Crank.exe to narzędzie do testowania gęstości połączenia dostarczone z bazą kodu sygnalizującego. Ponieważ podpory wymaga połączenia trwałego, należy dodać je do lokacji do użycia podczas testowania. Dodaj nowy folder do projektu WebRole1 o nazwie PersistentConnections. Kliknij prawym przyciskiem myszy ten folder i wybierz polecenie Dodaj > klasę. Nazwij nowy plik klasy MyPersistentConnections.cs a następnie wybierz pozycję Dodaj.

  24. Program Visual Studio otworzy plik MyPersistentConnections.cs w oknie głównym. Zastąp zawartość następującym kodem, a następnie Zapisz i zamknij plik:

    using System.Threading.Tasks;
    using Microsoft.AspNet.SignalR;
    using Microsoft.AspNet.SignalR.Infrastructure;
    
    namespace WebRole1.PersistentConnections
    {
        public class MyPersistentConnection : PersistentConnection
        {
            protected override Task OnReceived(IRequest request, string connectionId, string data)
            {
                //Return data to calling user
                return Connection.Send(connectionId, data);        
            }
        }
    }
    
  25. Korzystając z Startup klasy, obiekty sygnalizujące są uruchamiane po rozpoczęciu Owin. Otwórz lub Utwórz Startup.cs i Zastąp zawartość następującym kodem:

    using Microsoft.Owin;
    using Owin;
    using WebRole1.PersistentConnections;
    
    // Marks this class for automatic OWIN startup
    [assembly: OwinStartup(typeof(WebRole1.Startup))]
    namespace WebRole1
    {
        public partial class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                ConfigureAuth(app);
                // Only needed if "No Authentication" was not selected for the project
                app.MapSignalR();
                app.MapSignalR<MyPersistentConnection>("/echo");
            }
        }
    }
    

    W powyższym kodzie OwinStartup atrybut oznacza tę klasę do uruchomienia Owin. ConfigurationMetoda uruchamia sygnał.

  26. Przetestuj aplikację w Emulator Microsoft Azure, naciskając klawisz F5.

    Note

    Jeśli napotkasz FileLoadException o MapSignalR, Zmień przekierowania powiązań w web.config na następujące:

    <dependentAssembly>
      <assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
      <bindingRedirect oldVersion="0.0.0.0-2.0.2.0" newVersion="2.0.0.0" />
    </dependentAssembly>
    <dependentAssembly>
      <assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
      <bindingRedirect oldVersion="0.0.0.0-2.0.2.0" newVersion="2.0.0.0" />
    </dependentAssembly>
    
  27. Odczekaj około jednej minuty. Otwórz okno narzędzia Cloud Explorer w programie Visual Studio (Wyświetl > Eksplorator chmury) i rozwiń ścieżkę (Local)/Storage Accounts/(Development)/Tables . Kliknij dwukrotnie pozycję WADPerformanceCountersTable. W danych tabeli powinny być widoczne liczniki sygnalizujące. Jeśli nie widzisz tabeli, może być konieczne ponowne wprowadzenie poświadczeń usługi Azure Storage. Może być konieczne wybranie przycisku Odśwież w celu wyświetlenia tabeli w programie Cloud Explorer lub wybranie przycisku Odśwież w oknie Otwórz tabelę, aby wyświetlić dane w tabeli.

    Wybieranie tabeli liczników wydajności funkcji wad w programie Visual Studio Cloud Explorer

    Wyświetlanie liczników zebranych w tabeli liczników wydajności funkcji wad

  28. Aby przetestować aplikację w chmurze, zaktualizuj plik ServiceConfiguration. Cloud. cscfg i ustaw Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString dla niego prawidłowe parametry połączenia z kontem usługi Azure Storage.

    <?xml version="1.0" encoding="utf-8"?>
    <ServiceConfiguration serviceName="SignalRPerfCounters" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="4" osVersion="*" schemaVersion="2015-04.2.6">
      <Role name="WebRole1">
        <Instances count="1" />
        <ConfigurationSettings>
          <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="DefaultEndpointsProtocol=https;AccountName=&lt;account-name&gt;;AccountKey=&lt;account-key&gt;" />
        </ConfigurationSettings>
      </Role>
    </ServiceConfiguration>
    
  29. Wdróż aplikację w subskrypcji platformy Azure. Aby uzyskać szczegółowe informacje na temat sposobu wdrażania aplikacji na platformie Azure, zobacz jak utworzyć i wdrożyć usługę w chmurze.

  30. Zaczekaj kilka minut. W Eksploratorze chmuryZnajdź konto magazynu skonfigurowane powyżej i Znajdź WADPerformanceCountersTable w nim tabelę. W danych tabeli powinny być widoczne liczniki sygnalizujące. Jeśli nie widzisz tabeli, może być konieczne ponowne wprowadzenie poświadczeń usługi Azure Storage. Może być konieczne wybranie przycisku Odśwież w celu wyświetlenia tabeli w programie Cloud Explorer lub wybranie przycisku Odśwież w oknie Otwórz tabelę, aby wyświetlić dane w tabeli.

Specjalne poświęcenie na Martin Richard dla oryginalnej zawartości używanej w tym samouczku.