EventCounters na platformie .NET
Ten artykuł dotyczy: ✔️ zestaw .NET Core 3.0 SDK i nowsze wersje
Uwaga
W przypadku tworzenia nowych projektów .NET firma Microsoft zaleca zamiast tego korzystanie z nowszych interfejsów API System.Diagnostics.Metrics . Interfejsy API System.Diagnostics.Metrics oferują zwiększoną funkcjonalność, standaryzację i integrację z szerszym ekosystemem narzędzi. Aby uzyskać więcej informacji, zobacz porównanie interfejsu API metryk.
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.
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ściami1
,2
i5
, suma8
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 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ą Command EventCommand.Enable
. Aby bezpiecznie utworzyć wystąpienie wystąpienia licznika tylko wtedy, gdy jest null
to , 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) long
pola _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 ).
Azure Application Insights
Konta zdarzeń można używać przez usługę Azure Monitor, w szczególności aplikacja systemu Azure Insights. 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.