EventCounters na platformie .NET

Ten artykuł dotyczy: ✔️ zestaw .NET Core 3.0 SDK i nowsze wersje

EventCounters to interfejsy API platformy .NET używane do lekkiej, międzyplatformowej i niemal w czasie rzeczywistym zbierania metryk wydajności. EventCounters zostały dodane jako międzyplatformowa alternatywa dla "liczników wydajności" programu .NET Framework w systemie Windows. W tym artykule dowiesz się, czym są zdarzenia, jak je zaimplementować i jak je używać.

Środowisko uruchomieniowe platformy .NET i kilka bibliotek platformy .NET publikują podstawowe informacje diagnostyczne przy użyciu usługi EventCounters, począwszy od platformy .NET Core 3.0. Poza elementami EventCounters udostępnianymi przez środowisko uruchomieniowe platformy .NET możesz wdrożyć własne konta zdarzeń. Funkcja EventCounters może służyć do śledzenia różnych metryk. Dowiedz się więcej o nich w dobrze znanych elementach EventCounters na platformie .NET

EventCounters na żywo w ramach elementu EventSourcei są automatycznie wypychane do narzędzi odbiornika w regularnych odstępach czasu. Podobnie jak wszystkie inne zdarzenia w obiekcie EventSource, mogą być używane zarówno w proc, jak i out-of-proc za pośrednictwem EventListener i EventPipe. W tym artykule skupiono się na możliwościach obejmujących wiele platform usługi EventCounters i celowo wyklucza element PerfView i ETW (śledzenie zdarzeń dla systemu Windows) — chociaż oba te elementy mogą być używane z elementami EventCounters.

Obraz diagramu ZdarzeniaCounters in-proc i out-of-proc

Omówienie interfejsu API eventcounter

Istnieją dwie główne kategorie eventcounters. Niektóre liczniki są przeznaczone dla wartości "rate", takich jak łączna liczba wyjątków, łączna liczba kontrolerów domeny i łączna liczba żądań. Inne liczniki to wartości "migawki", takie jak użycie sterty, użycie procesora CPU i rozmiar zestawu roboczego. W każdej z tych kategorii liczników istnieją dwa typy liczników, które różnią się w zależności od ich wartości. Liczniki sondowania pobierają swoją wartość za pośrednictwem wywołania zwrotnego, a liczniki niezwiązane z sondowaniem mają swoje wartości bezpośrednio ustawione w wystąpieniu licznika.

Liczniki są reprezentowane przez następujące implementacje:

Odbiornik zdarzeń określa, jak długie są interwały pomiaru. Na końcu każdego interwału wartość jest przesyłana do odbiornika dla każdego licznika. Implementacje licznika określają, jakie interfejsy API i obliczenia są używane do generowania wartości każdego interwału.

  • Rekordy EventCounter zestawu wartości. Metoda EventCounter.WriteMetric dodaje nową wartość do zestawu. W każdym interwale obliczane jest statystyczne podsumowanie zestawu, takie jak minimalna, maksymalna i średnia. Narzędzie dotnet-counters zawsze wyświetla średnią wartość. Jest EventCounter to przydatne do opisania dyskretnego zestawu operacji. Typowe użycie może obejmować monitorowanie średniego rozmiaru w bajtach ostatnich operacji we/wy lub średnią wartość pieniężną zestawu transakcji finansowych.

  • Rejestruje IncrementingEventCounter sumę bieżącą dla każdego interwału czasu. Metoda IncrementingEventCounter.Increment dodaje sumę. Jeśli na przykład Increment() jest wywoływany trzy razy w jednym interwale z wartościami 1, 2i 5, suma 8 bieżąca zostanie zgłoszona jako wartość licznika dla tego interwału. Narzędzie dotnet-counters wyświetli szybkość jako zarejestrowaną sumę/czas. Jest IncrementingEventCounter to przydatne do mierzenia, jak często występuje akcja, na przykład liczba przetworzonych żądań na sekundę.

  • Funkcja PollingCounter używa wywołania zwrotnego w celu określenia wartości, która jest zgłaszana. Przy każdym interwale czasu wywoływana jest funkcja wywołania zwrotnego podana przez użytkownika, a wartość zwracana jest używana jako wartość licznika. Element PollingCounter może służyć do wykonywania zapytań dotyczących metryki ze źródła zewnętrznego, na przykład pobierania bieżących wolnych bajtów na dysku. Może również służyć do raportowania niestandardowych statystyk, które można obliczyć na żądanie przez aplikację. Przykłady obejmują raportowanie 95. percentyla ostatnich opóźnień żądań lub bieżącego trafienia lub współczynnika miss pamięci podręcznej.

  • Funkcja IncrementingPollingCounter używa wywołania zwrotnego w celu określenia zgłoszonej wartości przyrostowej. Przy każdym interwale czasu wywołanie zwrotne jest wywoływane, a następnie różnica między bieżącym wywołaniem, a ostatnią wywołaniem jest zgłoszona wartość. Narzędzie dotnet-counters zawsze wyświetla różnicę jako szybkość, zgłoszoną wartość /godzinę. Ten licznik jest przydatny, gdy nie jest możliwe wywołanie interfejsu API dla każdego wystąpienia, ale można wykonać zapytanie o łączną liczbę wystąpień. Można na przykład zgłosić liczbę bajtów zapisanych w pliku na sekundę, nawet bez powiadomienia za każdym razem, gdy bajt jest zapisywany.

Implementowanie źródła zdarzeń

Poniższy kod implementuje przykład EventSource uwidoczniony jako nazwany "Sample.EventCounter.Minimal" dostawca. To źródło zawiera EventCounter reprezentujący czas przetwarzania żądań. Taki licznik ma nazwę (czyli unikatowy identyfikator w źródle) i nazwę wyświetlaną, zarówno używaną przez narzędzia odbiornika, jak dotnet-counters.

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);
    }
}

dotnet-counters ps Służy do wyświetlania listy procesów platformy .NET, które można monitorować:

dotnet-counters ps
   1398652 dotnet     C:\Program Files\dotnet\dotnet.exe
   1399072 dotnet     C:\Program Files\dotnet\dotnet.exe
   1399112 dotnet     C:\Program Files\dotnet\dotnet.exe
   1401880 dotnet     C:\Program Files\dotnet\dotnet.exe
   1400180 sample-counters C:\sample-counters\bin\Debug\netcoreapp3.1\sample-counters.exe

EventSource Przekaż nazwę do --counters opcji , aby rozpocząć monitorowanie licznika:

dotnet-counters monitor --process-id 1400180 --counters Sample.EventCounter.Minimal

W poniższym przykładzie przedstawiono dane wyjściowe monitora:

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

[Samples-EventCounterDemos-Minimal]
    Request Processing Time (ms)                            0.445

Naciśnij klawisz q , aby zatrzymać polecenie monitorowania.

Liczniki warunkowe

Podczas implementowania klasy EventSource, liczniki zawierające mogą być warunkowo tworzone, gdy EventSource.OnEventCommand metoda jest wywoływana z wartością CommandEventCommand.Enable. Aby bezpiecznie utworzyć wystąpienie wystąpienia licznika tylko wtedy, gdy jest nullto , użyj operatora przypisania łączenia wartości null. Ponadto metody niestandardowe mogą oceniać metodę IsEnabled w celu określenia, czy jest włączone bieżące źródło zdarzeń.

using System.Diagnostics.Tracing;

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

    private EventCounter _requestCounter;

    private ConditionalEventCounterSource() { }

    protected override void OnEventCommand(EventCommandEventArgs args)
    {
        if (args.Command == EventCommand.Enable)
        {
            _requestCounter ??= new EventCounter("request-time", this)
            {
                DisplayName = "Request Processing Time",
                DisplayUnits = "ms"
            };
        }
    }

    public void Request(string url, float elapsedMilliseconds)
    {
        if (IsEnabled())
        {
            _requestCounter?.WriteMetric(elapsedMilliseconds);
        }
    }

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

        base.Dispose(disposing);
    }
}

Napiwek

Liczniki warunkowe to liczniki, które są warunkowo tworzone, mikrozoptymalizować. Środowisko uruchomieniowe stosuje ten wzorzec dla scenariuszy, w których liczniki są zwykle nieużytowane, aby zapisać ułamek milisekund.

Przykładowe liczniki środowiska uruchomieniowego platformy .NET Core

W środowisku uruchomieniowym platformy .NET Core istnieje wiele doskonałych przykładowych implementacji. Oto implementacja środowiska uruchomieniowego licznika śledzącego rozmiar zestawu roboczego aplikacji.

var workingSetCounter = new PollingCounter(
    "working-set",
    this,
    () => (double)(Environment.WorkingSet / 1_000_000))
{
    DisplayName = "Working Set",
    DisplayUnits = "MB"
};

Raportuje PollingCounter bieżącą ilość pamięci fizycznej zamapowanej na proces (zestaw roboczy) aplikacji, ponieważ przechwytuje metrykę w czasie. Wywołanie zwrotne do sondowania wartości to podane wyrażenie lambda, które jest tylko wywołaniem interfejsu System.Environment.WorkingSet API. DisplayName i DisplayUnits są opcjonalnymi właściwościami, które można ustawić, aby ułatwić użytkownikowi wyświetlanie wartości po stronie licznika. Na przykład liczniki dotnet-counters używają tych właściwości do wyświetlania bardziej przyjaznej dla wyświetlania wersji nazw liczników.

Ważne

Właściwości DisplayName nie są zlokalizowane.

W przypadku elementów PollingCounter, i IncrementingPollingCounter, nie należy wykonywać żadnych innych czynności. Obaj sondują same wartości w interwale żądanym przez konsumenta.

Oto przykład licznika środowiska uruchomieniowego zaimplementowanego przy użyciu polecenia IncrementingPollingCounter.

var monitorContentionCounter = new IncrementingPollingCounter(
    "monitor-lock-contention-count",
    this,
    () => Monitor.LockContentionCount
)
{
    DisplayName = "Monitor Lock Contention Count",
    DisplayRateTimeScale = TimeSpan.FromSeconds(1)
};

Funkcja IncrementingPollingCounter używa interfejsu Monitor.LockContentionCount API do raportowania przyrostu całkowitej liczby rywalizacji o blokadę. Właściwość jest opcjonalna DisplayRateTimeScale , ale jeśli jest używana, może zapewnić wskazówkę dotyczącą interwału czasu, w jakim licznik jest najlepiej wyświetlany. Na przykład liczba rywalizacji o blokadę jest najlepiej wyświetlana jako liczba na sekundę, więc jest DisplayRateTimeScale ustawiona na jedną sekundę. Częstotliwość wyświetlania można dostosować dla różnych typów liczników szybkości.

Uwaga

Parametr DisplayRateTimeScale nie jest używany przez liczniki dotnet, a odbiorniki zdarzeń nie są wymagane do jej używania.

Istnieje więcej implementacji liczników do użycia jako odwołanie w repozytorium środowiska uruchomieniowego platformy .NET.

Współbieżność

Napiwek

Interfejs API EventCounters nie gwarantuje bezpieczeństwa wątków. Gdy delegaty przekazywane do PollingCounter lub IncrementingPollingCounter wystąpienia są wywoływane przez wiele wątków, twoim zadaniem jest zagwarantowanie bezpieczeństwa wątków delegatów.

Rozważmy na przykład następujące EventSource kwestie, aby śledzić żądania.

using System;
using System.Diagnostics.Tracing;

public class RequestEventSource : EventSource
{
    public static readonly RequestEventSource Log = new RequestEventSource();

    private IncrementingPollingCounter _requestRateCounter;
    private long _requestCount = 0;

    private RequestEventSource() =>
        _requestRateCounter = new IncrementingPollingCounter("request-rate", this, () => _requestCount)
        {
            DisplayName = "Request Rate",
            DisplayRateTimeScale = TimeSpan.FromSeconds(1)
        };

    public void AddRequest() => ++ _requestCount;

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

        base.Dispose(disposing);
    }
}

Metodę AddRequest() można wywołać z programu obsługi żądań, a RequestRateCounter wartość sonduje w interwale określonym przez konsumenta licznika. AddRequest() Jednak metoda może być wywoływana przez wiele wątków jednocześnie, umieszczając warunek wyścigu na _requestCount. Bezpieczny wątkowo alternatywny sposób inkrementacji _requestCount metody polega na użyciu metody Interlocked.Increment.

public void AddRequest() => Interlocked.Increment(ref _requestCount);

Aby zapobiec rozdartym odczytom (w architekturach 32-bitowych) longpola _requestCount , użyj polecenia Interlocked.Read.

_requestRateCounter = new IncrementingPollingCounter("request-rate", this, () => Interlocked.Read(ref _requestCount))
{
    DisplayName = "Request Rate",
    DisplayRateTimeScale = TimeSpan.FromSeconds(1)
};

Korzystanie z eventcounters

Istnieją dwa podstawowe sposoby korzystania z usługi EventCounters: in-proc i out-of-proc. Użycie elementów EventCounters można odróżnić od trzech warstw różnych technologii zużywających.

  • Transportowanie zdarzeń w strumieniu nieprzetworzonym za pośrednictwem funkcji ETW lub EventPipe:

    Interfejsy API ETW są dostarczane z systemem operacyjnym Windows, a rozwiązanie EventPipe jest dostępne jako interfejs API platformy .NET lub protokół IPC diagnostyki.

  • Dekodowanie binarnego strumienia zdarzeń do zdarzeń:

    Biblioteka TraceEvent obsługuje formaty strumieni ETW i EventPipe.

  • Narzędzia wiersza polecenia i graficznego interfejsu użytkownika:

    Narzędzia takie jak PerfView (ETW lub EventPipe), dotnet-counters (tylko EventPipe) i dotnet-monitor (tylko EventPipe).

Korzystanie z funkcji out-of-proc

Korzystanie z funkcji EventCounters out-of-proc jest typowym podejściem. Za pomocą liczników dotnet-counter można je używać w sposób międzyplatformowy za pośrednictwem interfejsu EventPipe. Narzędzie dotnet-counters jest wieloplatformowym narzędziem globalnym interfejsu wiersza polecenia dotnet, które może służyć do monitorowania wartości liczników. Aby dowiedzieć się, jak monitorować dotnet-counters liczniki, zobacz dotnet-counters (Liczniki dotnet-counters) lub zapoznaj się z samouczkiem Measure performance using EventCounters (Mierzenie wydajności przy użyciu funkcji EventCounters ).

dotnet-trace

Narzędzie dotnet-trace może służyć do korzystania z danych licznika za pośrednictwem elementu EventPipe. Oto przykład użycia do dotnet-trace zbierania danych licznika.

dotnet-trace collect --process-id <pid> Sample.EventCounter.Minimal:0:0:EventCounterIntervalSec=1

Aby uzyskać więcej informacji na temat zbierania wartości liczników w czasie, zobacz dokumentację dotnet-trace .

Azure Application Insights

Konta zdarzeń mogą być używane przez usługę Azure Monitor, w szczególności aplikacja systemu Azure Szczegółowe informacje. Liczniki można dodawać i usuwać, a ty możesz określić niestandardowe liczniki lub dobrze znane liczniki. Aby uzyskać więcej informacji, zobacz Dostosowywanie liczników do zbierania.

dotnet-monitor

Narzędzie dotnet-monitor ułatwia dostęp do diagnostyki z procesu platformy .NET w sposób zdalny i zautomatyzowany. Oprócz śladów może monitorować metryki, zbierać zrzuty pamięci i zbierać zrzuty GC. Jest on dystrybuowany zarówno jako narzędzie interfejsu wiersza polecenia, jak i obraz platformy Docker. Uwidacznia interfejs API REST, a kolekcja artefaktów diagnostycznych odbywa się za pośrednictwem wywołań REST.

Aby uzyskać więcej informacji, zobacz dotnet-monitor.

Korzystanie z narzędzia in-proc

Wartości liczników można używać za pośrednictwem interfejsu EventListener API. Element EventListener to sposób korzystania ze wszystkich zdarzeń zapisywanych przez wszystkie wystąpienia EventSource obiektu w aplikacji. Aby uzyskać więcej informacji na temat korzystania z interfejsu EventListener API, zobacz EventListener.

Najpierw należy EventSource włączyć wartość licznika, która generuje wartość licznika. Zastąpij EventListener.OnEventSourceCreated metodę , aby uzyskać powiadomienie po utworzeniu elementu EventSource , a jeśli jest to poprawne EventSource dla Twoich zdarzeń, możesz wywołać EventListener.EnableEvents je. Oto przykład zastąpienia:

protected override void OnEventSourceCreated(EventSource source)
{
    if (!source.Name.Equals("System.Runtime"))
    {
        return;
    }

    EnableEvents(source, EventLevel.Verbose, EventKeywords.All, new Dictionary<string, string>()
    {
        ["EventCounterIntervalSec"] = "1"
    });
}

Przykładowy kod

Oto przykładowa EventListener klasa, która drukuje wszystkie nazwy liczników i wartości z środowiska uruchomieniowego platformy EventSource.NET do publikowania liczników wewnętrznych (System.Runtime) co sekundę.

using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;

public class SimpleEventListener : EventListener
{
    public SimpleEventListener()
    {
    }

    protected override void OnEventSourceCreated(EventSource source)
    {
        if (!source.Name.Equals("System.Runtime"))
        {
            return;
        }

        EnableEvents(source, EventLevel.Verbose, EventKeywords.All, new Dictionary<string, string>()
        {
            ["EventCounterIntervalSec"] = "1"
        });
    }

    protected override void OnEventWritten(EventWrittenEventArgs eventData)
    {
        if (!eventData.EventName.Equals("EventCounters"))
        {
            return;
        }

        for (int i = 0; i < eventData.Payload.Count; ++ i)
        {
            if (eventData.Payload[i] is IDictionary<string, object> eventPayload)
            {
                var (counterName, counterValue) = GetRelevantMetric(eventPayload);
                Console.WriteLine($"{counterName} : {counterValue}");
            }
        }
    }

    private static (string counterName, string counterValue) GetRelevantMetric(
        IDictionary<string, object> eventPayload)
    {
        var counterName = "";
        var counterValue = "";

        if (eventPayload.TryGetValue("DisplayName", out object displayValue))
        {
            counterName = displayValue.ToString();
        }
        if (eventPayload.TryGetValue("Mean", out object value) ||
            eventPayload.TryGetValue("Increment", out value))
        {
            counterValue = value.ToString();
        }

        return (counterName, counterValue);
    }
}

Jak pokazano powyżej, należy upewnić się, że "EventCounterIntervalSec" argument jest ustawiony w argumencie filterPayload podczas wywoływania elementu EnableEvents. W przeciwnym razie liczniki nie będą mogły opróżniać wartości, ponieważ nie wie, w jakim interwale powinny zostać opróżnione.

Zobacz też