Samouczek: mierzenie wydajności przy użyciu funkcji EventCounters na platformie .NET Core

Ten artykuł dotyczy: ✔️ .NET Core 3.0 SDK i nowszych wersji

Z tego samouczka dowiesz się, jak EventCounter można użyć do mierzenia wydajności z wysoką częstotliwością zdarzeń. Możesz użyć dostępnych liczników opublikowanych przez różne oficjalne pakiety platformy .NET Core, dostawców innych firm lub utworzyć własne metryki do monitorowania.

W tym samouczku wykonasz następujące czynności:

Wymagania wstępne

W tym samouczku są używane następujące elementy:

Pobieranie źródła

Przykładowa aplikacja będzie używana jako podstawa do monitorowania. Przykładowe repozytorium ASP.NET Core jest dostępne w przeglądarce przykładów. Pobierz plik zip, wyodrębnij go po pobraniu i otwórz go w ulubionym środowisku IDE. Skompiluj i uruchom aplikację, aby upewnić się, że działa prawidłowo, a następnie zatrzymaj aplikację.

Implementowanie źródła zdarzeń

W przypadku zdarzeń, które mają miejsce co kilka milisekund, obciążenie na zdarzenie ma być niskie (mniej niż milisekund). W przeciwnym razie wpływ na wydajność będzie znaczący. Rejestrowanie zdarzenia oznacza, że będziesz zapisywać coś na dysku. Jeśli dysk nie jest wystarczająco szybki, utracisz zdarzenia. Potrzebujesz rozwiązania innego niż rejestrowanie samego zdarzenia.

Podczas pracy z dużą liczbą zdarzeń znajomość miary na zdarzenie również nie jest przydatna. Większość czasu, czego potrzebujesz, to tylko niektóre statystyki z niego. Dzięki temu można uzyskać statystyki w ramach samego procesu, a następnie napisać zdarzenie raz na chwilę, aby zgłosić statystyki, to właśnie EventCounter zrobi.

Poniżej przedstawiono przykład implementacji elementu System.Diagnostics.Tracing.EventSource. Utwórz nowy plik o nazwie MinimalEventCounterSource.cs i użyj fragmentu kodu jako jego źródła:

using System.Diagnostics.Tracing;

[EventSource(Name = "Sample.EventCounter.Minimal")]
public sealed class MinimalEventCounterSource : EventSource
{
    public static readonly MinimalEventCounterSource Log = new MinimalEventCounterSource();

    private EventCounter _requestCounter;

    private MinimalEventCounterSource() =>
        _requestCounter = new EventCounter("request-time", this)
        {
            DisplayName = "Request Processing Time",
            DisplayUnits = "ms"
        };

    public void Request(string url, long elapsedMilliseconds)
    {
        WriteEvent(1, url, elapsedMilliseconds);
        _requestCounter?.WriteMetric(elapsedMilliseconds);
    }

    protected override void Dispose(bool disposing)
    {
        _requestCounter?.Dispose();
        _requestCounter = null;

        base.Dispose(disposing);
    }
}

Wiersz EventSource.WriteEvent jest EventSource częścią i nie jest częścią EventCounterelementu , został zapisany w celu pokazania, że można rejestrować komunikat wraz z licznikiem zdarzeń.

Dodawanie filtru akcji

Przykładowy kod źródłowy jest projektem ASP.NET Core. Możesz dodać filtr akcji globalnie, który będzie rejestrować łączny czas żądania. Utwórz nowy plik o nazwie LogRequestTimeFilterAttribute.cs i użyj następującego kodu:

using System.Diagnostics;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc.Filters;

namespace DiagnosticScenarios
{
    public class LogRequestTimeFilterAttribute : ActionFilterAttribute
    {
        readonly Stopwatch _stopwatch = new Stopwatch();

        public override void OnActionExecuting(ActionExecutingContext context) => _stopwatch.Start();

        public override void OnActionExecuted(ActionExecutedContext context)
        {
            _stopwatch.Stop();

            MinimalEventCounterSource.Log.Request(
                context.HttpContext.Request.GetDisplayUrl(), _stopwatch.ElapsedMilliseconds);
        }
    }
}

Filtr akcji rozpoczyna działanie Stopwatch w momencie rozpoczęcia żądania i zatrzymuje się po jego zakończeniu, przechwytując czas, który upłynął. Łączna liczba milisekund jest rejestrowana w wystąpieniu pojedynczego MinimalEventCounterSource wystąpienia. Aby ten filtr był stosowany, należy dodać go do kolekcji filtrów. W pliku Startup.cs zaktualizuj metodę ConfigureServices w pliku uwzględnij ten filtr.

public void ConfigureServices(IServiceCollection services) =>
    services.AddControllers(options => options.Filters.Add<LogRequestTimeFilterAttribute>())
            .AddNewtonsoftJson();

Monitorowanie licznika zdarzeń

Dzięki implementacji w filtrze EventSource i niestandardowej akcji skompiluj i uruchom aplikację. Zarejestrowano metryki w EventCounterobiekcie , ale jeśli nie uzyskujesz dostępu do statystyk z niej, nie jest to przydatne. Aby uzyskać statystyki, należy włączyć funkcję EventCounter , tworząc czasomierz, który jest uruchamiany tak często, jak chcesz, zdarzenia, a także odbiornik do przechwytywania zdarzeń. W tym celu można użyć polecenia dotnet-counters.

Użyj polecenia dotnet-counters ps , aby wyświetlić listę procesów platformy .NET, które można monitorować.

dotnet-counters ps

Przy użyciu identyfikatora procesu z danych wyjściowych dotnet-counters ps polecenia można rozpocząć monitorowanie licznika zdarzeń za pomocą następującego dotnet-counters monitor polecenia:

dotnet-counters monitor --process-id 2196 --counters Sample.EventCounter.Minimal,Microsoft.AspNetCore.Hosting[total-requests,requests-per-second],System.Runtime[cpu-usage]

dotnet-counters monitor Gdy polecenie jest uruchomione, przytrzymaj klawisz F5 w przeglądarce, aby rozpocząć wydawanie ciągłych żądań do punktu końcowegohttps://localhost:5001/api/values. Po kilku sekundach zatrzymaj się, naciskając klawisz q

Press p to pause, r to resume, q to quit.
    Status: Running

[Microsoft.AspNetCore.Hosting]
    Request Rate / 1 sec                               9
    Total Requests                                   134
[System.Runtime]
    CPU Usage (%)                                     13
[Sample.EventCounter.Minimal]
    Request Processing Time (ms)                      34.5

Polecenie dotnet-counters monitor doskonale nadaje się do aktywnego monitorowania. Można jednak zebrać te metryki diagnostyczne na potrzeby przetwarzania i analizy. W tym celu użyj dotnet-counters collect polecenia . Polecenie collect switch jest podobne do monitor polecenia, ale akceptuje kilka dodatkowych parametrów. Możesz określić żądaną nazwę i format pliku wyjściowego. W przypadku pliku JSON o nazwie diagnostics.json użyj następującego polecenia:

dotnet-counters collect --process-id 2196 --format json -o diagnostics.json --counters Sample.EventCounter.Minimal,Microsoft.AspNetCore.Hosting[total-requests,requests-per-second],System.Runtime[cpu-usage]

Ponownie, gdy polecenie jest uruchomione, przytrzymaj klawisz F5 w przeglądarce, aby rozpocząć wydawanie ciągłych żądań do punktu końcowego https://localhost:5001/api/values . Po kilku sekundach zatrzymaj się, naciskając klawisz q. Plik diagnostics.json jest zapisywany. Zapisany plik JSON nie jest jednak wcięciem; w celu zapewnienia czytelności jest ona wcięta w tym miejscu.

{
  "TargetProcess": "DiagnosticScenarios",
  "StartTime": "8/5/2020 3:02:45 PM",
  "Events": [
    {
      "timestamp": "2020-08-05 15:02:47Z",
      "provider": "System.Runtime",
      "name": "CPU Usage (%)",
      "counterType": "Metric",
      "value": 0
    },
    {
      "timestamp": "2020-08-05 15:02:47Z",
      "provider": "Microsoft.AspNetCore.Hosting",
      "name": "Request Rate / 1 sec",
      "counterType": "Rate",
      "value": 0
    },
    {
      "timestamp": "2020-08-05 15:02:47Z",
      "provider": "Microsoft.AspNetCore.Hosting",
      "name": "Total Requests",
      "counterType": "Metric",
      "value": 134
    },
    {
      "timestamp": "2020-08-05 15:02:47Z",
      "provider": "Sample.EventCounter.Minimal",
      "name": "Request Processing Time (ms)",
      "counterType": "Metric",
      "value": 0
    },
    {
      "timestamp": "2020-08-05 15:02:47Z",
      "provider": "System.Runtime",
      "name": "CPU Usage (%)",
      "counterType": "Metric",
      "value": 0
    },
    {
      "timestamp": "2020-08-05 15:02:48Z",
      "provider": "Microsoft.AspNetCore.Hosting",
      "name": "Request Rate / 1 sec",
      "counterType": "Rate",
      "value": 0
    },
    {
      "timestamp": "2020-08-05 15:02:48Z",
      "provider": "Microsoft.AspNetCore.Hosting",
      "name": "Total Requests",
      "counterType": "Metric",
      "value": 134
    },
    {
      "timestamp": "2020-08-05 15:02:48Z",
      "provider": "Sample.EventCounter.Minimal",
      "name": "Request Processing Time (ms)",
      "counterType": "Metric",
      "value": 0
    },
    {
      "timestamp": "2020-08-05 15:02:48Z",
      "provider": "System.Runtime",
      "name": "CPU Usage (%)",
      "counterType": "Metric",
      "value": 0
    },
    {
      "timestamp": "2020-08-05 15:02:50Z",
      "provider": "Microsoft.AspNetCore.Hosting",
      "name": "Request Rate / 1 sec",
      "counterType": "Rate",
      "value": 0
    },
    {
      "timestamp": "2020-08-05 15:02:50Z",
      "provider": "Microsoft.AspNetCore.Hosting",
      "name": "Total Requests",
      "counterType": "Metric",
      "value": 134
    },
    {
      "timestamp": "2020-08-05 15:02:50Z",
      "provider": "Sample.EventCounter.Minimal",
      "name": "Request Processing Time (ms)",
      "counterType": "Metric",
      "value": 0
    }
  ]
}

Następne kroki