Dela via


EventCounters i .NET

Den här artikeln gäller för: ✔️ .NET Core 3.0 SDK och senare versioner

EventCounters är .NET-API:er som används för enkel, plattformsoberoende och nästan realtidsbaserad insamling av prestandamått. EventCounters lades till som ett plattformsoberoende alternativ till "prestandaräknare" för .NET Framework i Windows. I den här artikeln får du lära dig vad EventCounters är, hur du implementerar dem och hur du använder dem.

.NET-körningen och några .NET-bibliotek publicerar grundläggande diagnostikinformation med Hjälp av EventCounters med start i .NET Core 3.0. Förutom EventCounters som tillhandahålls av .NET-körningen kan du välja att implementera dina egna EventCounters. EventCounters kan användas för att spåra olika mått. Läs mer om dem i välkända EventCounters i .NET

EventCounters lever som en del av en EventSource, och skickas automatiskt till lyssnarverktyg regelbundet. Precis som alla andra händelser i en EventSourcekan de användas både in-proc och out-of-proc via EventListener och EventPipe. Den här artikeln fokuserar på plattformsoberoende funktioner i EventCounters och utesluter avsiktligt PerfView och ETW (händelsespårning för Windows) – även om båda kan användas med EventCounters.

Bild av EventCounters-diagram för in-proc och out-of-proc

Översikt över EventCounter API

Det finns två primära kategorier av EventCounters. Vissa räknare är för "rate"-värden, till exempel totalt antal undantag, totalt antal GCs och totalt antal begäranden. Andra räknare är "ögonblicksbild"-värden, till exempel heapanvändning, CPU-användning och storlek på arbetsuppsättningar. Inom var och en av dessa räknare finns det två typer av räknare som varierar beroende på hur de får sitt värde. Avsökningsräknare hämtar sitt värde via ett återanrop, och icke-avsökningsräknare har sina värden direkt inställda på räknarinstansen.

Räknarna representeras av följande implementeringar:

En händelselyssnare anger hur långa måttintervallen är. I slutet av varje intervall överförs ett värde till lyssnaren för varje räknare. Implementeringarna av en räknare avgör vilka API:er och beräkningar som används för att generera värdet varje intervall.

  • Registrerar EventCounter en uppsättning värden. Metoden EventCounter.WriteMetric lägger till ett nytt värde i uppsättningen. Med varje intervall beräknas en statistisk sammanfattning för uppsättningen, till exempel min, max och medelvärde. Verktyget dotnet-counters visar alltid medelvärdet. EventCounter Är användbart för att beskriva en diskret uppsättning åtgärder. Vanlig användning kan vara att övervaka den genomsnittliga storleken i byte för de senaste I/O-åtgärderna eller det genomsnittliga monetära värdet för en uppsättning finansiella transaktioner.

  • Registrerar IncrementingEventCounter en löpande summa för varje tidsintervall. Metoden IncrementingEventCounter.Increment lägger till i totalsumman. Om Increment() till exempel anropas tre gånger under ett intervall med värdena 1, 2och 5, rapporteras den löpande summan av 8 som räknarvärdet för det här intervallet. Verktyget dotnet-counters visar hastigheten som den registrerade summan/tiden. IncrementingEventCounter Är användbart för att mäta hur ofta en åtgärd inträffar, till exempel antalet begäranden som bearbetas per sekund.

  • PollingCounter Använder ett återanrop för att fastställa det värde som rapporteras. För varje tidsintervall anropas den användaringivna återanropsfunktionen och returvärdet används som räknarvärde. En PollingCounter kan användas för att fråga ett mått från en extern källa, till exempel för att hämta aktuella kostnadsfria byte på en disk. Den kan också användas för att rapportera anpassad statistik som kan beräknas på begäran av ett program. Exempel är att rapportera den 95:e percentilen av de senaste svarstiderna för begäranden eller det aktuella träff- eller missförhållandet för en cache.

  • IncrementingPollingCounter Använder ett återanrop för att fastställa det rapporterade inkrementsvärdet. För varje tidsintervall anropas återanropet och sedan skillnaden mellan det aktuella anropet och det sista anropet är det rapporterade värdet. Verktyget dotnet-counters visar alltid skillnaden som en hastighet, det rapporterade värdet/tiden. Den här räknaren är användbar när det inte är möjligt att anropa ett API för varje förekomst, men det är möjligt att fråga det totala antalet förekomster. Du kan till exempel rapportera antalet byte som skrivs till en fil per sekund, även utan ett meddelande varje gång en byte skrivs.

Implementera en EventSource

Följande kod implementerar ett exempel EventSource som exponeras som den namngivna "Sample.EventCounter.Minimal" providern. Den här källan innehåller en EventCounter som representerar bearbetningstiden för begäranden. En sådan räknare har ett namn (dvs. dess unika ID i källan) och ett visningsnamn, som båda används av lyssnarverktyg som 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);
    }
}

Du använder dotnet-counters ps för att visa en lista över .NET-processer som kan övervakas:

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

Skicka namnet EventSource till alternativet för att börja övervaka räknaren --counters :

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

I följande exempel visas övervakningsutdata:

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

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

Tryck på q för att stoppa övervakningskommandot.

Villkorliga räknare

När du implementerar en EventSourcekan de innehållande räknarna instansieras villkorligt när EventSource.OnEventCommand metoden anropas med värdet CommandEventCommand.Enable. Om du bara vill instansiera en räknarinstans på ett säkert sätt om det är nullanvänder du tilldelningsoperatorn null-coalescing. Dessutom kan anpassade metoder utvärdera IsEnabled metoden för att avgöra om den aktuella händelsekällan är aktiverad eller inte.

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

Dricks

Villkorliga räknare är räknare som är villkorligt instansierade, en mikrooptimering. Körningen använder det här mönstret för scenarier där räknare normalt inte används för att spara en bråkdel av en millisekunder.

Exempelräknare för .NET Core-körning

Det finns många bra exempelimplementeringar i .NET Core-körningen. Här är körningsimplementeringen för räknaren som spårar programmets arbetsuppsättningsstorlek.

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

Rapporten PollingCounter rapporterar den aktuella mängden fysiskt minne som mappats till processen (arbetsuppsättningen) i appen, eftersom det samlar in ett mått vid en viss tidpunkt. Återanropet för avsökning av ett värde är det tillhandahållna lambda-uttrycket, som bara är ett anrop till API:et System.Environment.WorkingSet . DisplayName och DisplayUnits är valfria egenskaper som kan ställas in för att hjälpa konsumentsidan av räknaren att visa värdet tydligare. Till exempel använder dotnet-counters dessa egenskaper för att visa den mer visningsvänliga versionen av räknarnamnen.

Viktigt!

Egenskaperna DisplayName är inte lokaliserade.

PollingCounterFör , och IncrementingPollingCounter, behöver inget annat göras. Båda avsöker värdena själva med ett intervall som begärs av konsumenten.

Här är ett exempel på en körningsräknare som implementeras med hjälp av IncrementingPollingCounter.

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

IncrementingPollingCounter Använder API:et Monitor.LockContentionCount för att rapportera ökningen av det totala antalet låskonkurreringar. Egenskapen DisplayRateTimeScale är valfri, men när den används kan den ge en ledtråd för vilket tidsintervall räknaren bäst visas på. Till exempel visas antalet låskonkurens bäst som antal per sekund, så det DisplayRateTimeScale är inställt på en sekund. Visningshastigheten kan justeras för olika typer av frekvensräknare.

Kommentar

DisplayRateTimeScale Används inte av dotnet-räknare och händelselyssnare krävs inte för att använda den.

Det finns fler räknarimplementeringar att använda som referens i .NET-runtime-lagringsplatsen .

Samtidighet

Dricks

EventCounters API garanterar inte trådsäkerhet. När ombuden som skickas till PollingCounter eller IncrementingPollingCounter instanserna anropas av flera trådar är det ditt ansvar att garantera ombudens trådsäkerhet.

Tänk till exempel på följande EventSource för att hålla reda på begäranden.

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

Metoden AddRequest() kan anropas från en begärandehanterare och RequestRateCounter avsöker värdet med det intervall som anges av räknarens konsument. Metoden kan dock AddRequest() anropas av flera trådar samtidigt, vilket sätter ett konkurrensvillkor på _requestCount. Ett trådsäkert alternativt sätt att öka _requestCount är att använda Interlocked.Increment.

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

För att förhindra sönderrivna läsningar (på 32-bitars arkitekturer) för long-field _requestCount använder du Interlocked.Read.

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

Använda EventCounters

Det finns två huvudsakliga sätt att använda EventCounters: in-proc och out-of-proc. Förbrukningen av EventCounters kan särskiljas i tre lager av olika användningstekniker.

  • Transportera händelser i en råström via ETW eller EventPipe:

    ETW-API:er levereras med Windows-operativsystemet och EventPipe är tillgängligt som ett .NET-API eller diagnostik-IPC-protokollet.

  • Avkoda den binära händelseströmmen till händelser:

    TraceEvent-biblioteket hanterar både ETW- och EventPipe-strömformat.

  • Kommandorads- och GUI-verktyg:

    Verktyg som PerfView (ETW eller EventPipe), dotnet-counters (endast EventPipe) och dotnet-monitor (endast EventPipe).

Förbruka out-of-proc

Att använda EventCounters out-of-proc är en vanlig metod. Du kan använda dotnet-räknare för att använda dem på ett plattformsoberoende sätt via en EventPipe. Verktyget dotnet-counters är ett globalt cli-verktyg för plattformsoberoende dotnet som kan användas för att övervaka räknarvärdena. Om du vill ta reda på hur du använder dotnet-counters för att övervaka dina räknare kan du läsa dotnet-counters eller gå igenom självstudiekursen Mät prestanda med Hjälp av EventCounters .

dotnet-trace

Verktyget dotnet-trace kan användas för att använda räknardata via en EventPipe. Här är ett exempel som används dotnet-trace för att samla in räknardata.

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

Mer information om hur du samlar in räknarvärden över tid finns i dokumentationen för dotnet-trace .

Azure Application Insights

EventCounters kan användas av Azure Monitor, särskilt Azure Application Insights. Räknare kan läggas till och tas bort, och du kan ange anpassade räknare eller välkända räknare. Mer information finns i Anpassa räknare som ska samlas in.

dotnet-monitor

Verktyget dotnet-monitor gör det enklare att komma åt diagnostik från en .NET-process på ett fjärranslutet och automatiserat sätt. Förutom spårningar kan den övervaka mått, samla in minnesdumpar och samla in GC-dumpar. Det distribueras som både ett CLI-verktyg och en docker-avbildning. Det exponerar ett REST-API och insamlingen av diagnostiska artefakter sker via REST-anrop.

Mer information finns i dotnet-monitor.

Förbruka in-proc

Du kan använda räknarvärdena via API:et EventListener . En EventListener är ett in-proc-sätt att använda händelser som skrivits av alla instanser av en EventSource i ditt program. Mer information om hur du använder API:et finns i EventListenerEventListener.

EventSource Först måste det som genererar räknarvärdet aktiveras. EventListener.OnEventSourceCreated Åsidosätt metoden för att få ett meddelande när en EventSource skapas, och om detta är rätt EventSource med dina EventCounters kan du anropa EventListener.EnableEvents den. Här är ett exempel på åsidosättning:

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

Exempelkod

Här är en exempelklass EventListener som skriver ut alla räknarnamn och värden från .NET-körningens EventSource, för att publicera sina interna räknare (System.Runtime) varje 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);
    }
}

Som du ser ovan måste du se till att "EventCounterIntervalSec" argumentet anges i filterPayload argumentet när du anropar EnableEvents. Annars kommer räknarna inte att kunna rensa ut värden eftersom de inte vet vid vilket intervall det ska rensas ut.

Se även