Udostępnij za pośrednictwem


Używanie liczników wydajności usługi SignalR w roli internetowej platformy Azure

Autor: Luke Latham

Ostrzeżenie

Ta dokumentacja nie dotyczy najnowszej wersji usługi SignalR. Przyjrzyj się ASP.NET Core SignalR.

Liczniki wydajności usługi SignalR służą do monitorowania wydajności aplikacji w roli internetowej platformy Azure. Liczniki są przechwytywane przez firmę Microsoft Diagnostyka Azure. Liczniki wydajności usługi SignalR są instalowane na platformie Azure przy użyciu signalr.exe, tego samego narzędzia używanego w aplikacjach autonomicznych lub lokalnych. Ponieważ role platformy Azure są przejściowe, należy skonfigurować aplikację do instalowania i rejestrowania liczników wydajności usługi SignalR podczas uruchamiania.

Wymagania wstępne

Tworzenie aplikacji roli internetowej platformy Azure, która uwidacznia liczniki wydajności usługi SignalR

  1. Otwórz program Visual Studio.

  2. W programie Visual Studio wybierz pozycje Plik>Nowy>Projekt.

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

    Nowa aplikacja w chmurze

    Uwaga

    Jeśli nie widzisz kategorii szablonu chmury lub szablonu usługi w chmurze platformy Azure, musisz zainstalować pakiet roboczy Programowanie na platformie Azure dla programu Visual Studio 2017. Wybierz link Otwórz Instalator programu Visual Studio w lewym dolnym rogu okna dialogowego Nowy projekt, aby otworzyć Instalator programu Visual Studio. Wybierz pakiet roboczy 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 usługa w chmurze platformy Microsoft Azure wybierz pozycję ASP.NET rola sieci Web i wybierz > przycisk, aby dodać rolę do projektu. Wybierz przycisk OK.

    Dodawanie roli sieci Web ASP.NET

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

    Dodawanie wzorca MVC i internetowego interfejsu API

  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żer pakietów z narzędzia NuGet>Menedżer pakietów. Wprowadź następujące polecenia, aby zainstalować najnowszą wersję usługi SignalR i pakiet narzędzi SignalR:

    install-package microsoft.aspnet.signalr
    install-package microsoft.aspnet.signalr.utils
    
  9. Skonfiguruj aplikację, aby zainstalować liczniki wydajności usługi SignalR w wystąpieniu roli podczas uruchamiania lub recyklingu. W Eksplorator rozwiązań kliknij prawym przyciskiem myszy projekt WebRole1 i wybierz polecenie Dodaj>nowy folder. Nazwij nowy folder Startup.

    Dodaj folder startowy

  10. Skopiuj plik signalr.exe (dodany z pakietem Microsoft.AspNet.SignalR.Utils) z <folderu> projektu/SignalRPerfCounters/packages/Microsoft.AspNet.SignalR.Utils.<version>/tools do folderu Startup utworzonego w poprzednim kroku.

  11. W Eksplorator rozwiązań kliknij prawym przyciskiem myszy folder Startup 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 tekstowy i nadaj nowej nazwę SignalRPerfCounterInstall.cmd elementu. Ten plik poleceń zainstaluje liczniki wydajności usługi SignalR do roli sieci Web.

    Tworzenie pliku wsadowego licznika wydajności usługi SignalR

  13. Gdy program Visual Studio utworzy plik SignalRPerfCounterInstall.cmd , zostanie on 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, co dodaje liczniki wydajności usługi SignalR 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ń. W obszarze Właściwości pliku ustaw opcję Kopiuj na Katalog wyjściowy, aby skopiować zawsze.

    Ustaw opcję Kopiuj do katalogu wyjściowego, aby zawsze kopiować

  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 i wybierz przycisk OK.

    Otwórz za pomocą edytora binarnego

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

    Usuwanie bajtów wiodących

  18. Otwórz plik ServiceDefinition.csdef i dodaj zadanie uruchamiania, które wykonuje plik SignalrPerfCounterInstall.cmd po uruchomieniu 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 metodę increment 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ń i wybierz polecenie Dodaj>nowy element. W oknie dialogowym Dodawanie nowego elementu wybierz kategorię Web>SignalR, a następnie wybierz szablon elementu Klasa centrum SignalR (wersja 2). Nadaj nowej MyHub.cs centrum i wybierz pozycję Dodaj.

    Dodawanie klasy usługi SignalR Hub do folderu Hubs w oknie dialogowym Dodawanie nowego elementu

  22. MyHub.cs zostanie automatycznie otwarty 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 usługi SignalR. Ponieważ Crank wymaga trwałego połączenia, należy dodać go do witryny 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 i 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. Przy użyciu klasy obiekty SignalR są uruchamiane po uruchomieniu Startup 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 OwinStartup kodzie atrybut oznacza tę klasę, aby uruchomić OWIN. Metoda Configuration uruchamia signalr.

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

    Uwaga

    Jeśli napotkasz wyjątek FileLoadException w elemecie MapSignalR, zmień przekierowania powiązań w pliku 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. Poczekaj 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 signalR. 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ż, aby wyświetlić tabelę w Eksploratorze chmury lub wybrać przycisk Odśwież w oknie otwartej tabeli, aby wyświetlić dane w tabeli.

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

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

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

    <?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 wdrażania aplikacji na platformie Azure, zobacz How to Create and Deploy a Cloud Service (Jak utworzyć i wdrożyć usługę w chmurze).

  30. Zaczekaj kilka minut. W Eksploratorze chmury znajdź konto magazynu skonfigurowane powyżej i znajdź w niej tabelę WADPerformanceCountersTable . W danych tabeli powinny być widoczne liczniki signalR. 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ż, aby wyświetlić tabelę w Eksploratorze chmury lub wybrać przycisk Odśwież w oknie otwartej tabeli, aby wyświetlić dane w tabeli.

Specjalne podziękowania dla Martina Richarda za oryginalną zawartość używaną w tym samouczku.