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 uproszczonej, międzyplatformowej i niemal w czasie rzeczywistym zbierania metryk wydajności. EventCounters zostały dodane jako międzyplatformowa alternatywa dla "liczników wydajności" .NET Framework na Windows. W tym artykule dowiesz się, czym są zdarzenia EventCounters, jak je zaimplementować i jak z nich korzystać.

Środowisko uruchomieniowe platformy .NET i kilka bibliotek .NET publikują podstawowe informacje diagnostyczne przy użyciu usługi EventCounters rozpoczynających się od platformy .NET Core 3.0. Poza elementami EventCounters udostępnianymi przez środowisko uruchomieniowe platformy .NET możesz wybrać wdrożenie własnych twoich danych eventCounters. EventCounters można użyć do śledzenia różnych metryk. Dowiedz się więcej o nich w dobrze znanych zdarzeniach 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 elemecie EventSource, mogą być używane zarówno w proc, jak i out-of-proc za pośrednictwem EventListener i EventPipe. Ten artykuł koncentruje się na możliwościach międzyplatformowych usługi EventCounters i celowo wyklucza program PerfView i ETW (śledzenie zdarzeń dla Windows) — chociaż oba te funkcje mogą być używane z usługą EventCounters.

EventCounters in-proc and out-of-proc diagram image

Omówienie interfejsu API eventCounter

Istnieją dwie główne kategorie usługi EventCounters. Niektóre liczniki są przeznaczone dla wartości "rate", takich jak całkowita liczba wyjątków, całkowita liczba kontrolerów domeny i całkowita 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 w każdym interwale.

  • Rejestruje EventCounter zestaw wartości. Metoda EventCounter.WriteMetric dodaje nową wartość do zestawu. W każdym interwale obliczane jest podsumowanie statystyczne dla zestawu, takie jak min, maksimum 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 do sumy. Jeśli na przykład Increment() jest wywoływana trzy razy w jednym interwale z wartościami 1, 2i 5, suma bieżąca 8 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żna go również użyć do raportowania niestandardowych statystyk, które mogą być obliczane 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łaszanej 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 jej unikatowy identyfikator w źródle) i nazwę wyświetlaną, zarówno używaną przez narzędzia odbiornika, takie jak dotnet-counter.

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 EventSourceelementu element , zawierające liczniki można warunkowo utworzyć wystąpienie, 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 o wartości null. Ponadto metody niestandardowe mogą ocenić metodę w IsEnabled 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);
    }
}

Porada

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

Przykładowe liczniki środowiska uruchomieniowego platformy .NET Core

Istnieje wiele doskonałych przykładowych implementacji w środowisku uruchomieniowym platformy .NET Core. Oto implementacja środowiska uruchomieniowego dla licznika, który śledzi 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 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.

PollingCounterW przypadku elementów , i IncrementingPollingCounter, nie trzeba robić nic innego. Obaj sondują 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)
};

Używa IncrementingPollingCounter interfejsu Monitor.LockContentionCount API do raportowania przyrostu łącznej liczby rywalizacji o blokadę. Właściwość jest opcjonalna DisplayRateTimeScale , ale gdy jest używana, może zapewnić wskazówkę, w jakim interwale czasu 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

Odbiorniki DisplayRateTimeScale zdarzeń nie są używane przez liczniki dotnet, a odbiorniki zdarzeń nie są wymagane do jego używania.

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

Współbieżność

Porada

Interfejs API EventCounters nie gwarantuje bezpieczeństwa wątków. Po przekazaniu delegatów do PollingCounter wystąpień lub IncrementingPollingCounter wywołaniu ich przez wiele wątków należy zagwarantować bezpieczeństwo 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 procedury obsługi żądań, a RequestRateCounter wartość sonduje w interwale określonym przez użytkownika 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 polecenia Interlocked.Increment.

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

Aby zapobiec rozdartym odczytom (w architekturach 32-bitowych) - longfield _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 usługi EventCounters

Istnieją dwa podstawowe sposoby korzystania z usługi EventCounters: in-proc i out-of-proc. Użycie usługi 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 interfejs EventPipe jest dostępny jako interfejs API platformy .NET lub protokół IPC diagnostyczny.

  • Dekodowanie strumienia zdarzeń binarnych 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 out-of-proc

Korzystanie z funkcji EventCounters poza proc jest typowym podejściem. Za pomocą liczników dotnet-counter można używać ich w sposób międzyplatformowy za pośrednictwem interfejsu EventPipe. Narzędzie dotnet-counters to wieloplatformowe narzędzie globalne 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 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

Liczba zdarzeń może być zużywana 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 to narzędzie eksperymentalne, które ułatwia uzyskiwanie dostępu do informacji diagnostycznych w procesie platformy .NET. Narzędzie służy jako nadzbiór wszystkich narzędzi diagnostycznych. 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 Wprowadzenie do dotnet-monitor , eksperymentalnego narzędzia.

Korzystanie z funkcji w proc

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

Najpierw należy włączyć wartość licznika, EventSource która generuje wartość licznika. Zastąpij metodę EventListener.OnEventSourceCreated , aby otrzymywać powiadomienie po utworzeniu EventSource elementu, a jeśli jest to poprawne EventSource z elementami EventCounters, 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 metody 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ż