Share via


EventCounters in .NET

Dit artikel is van toepassing op: ✔️ .NET Core 3.0 SDK en latere versies

EventCounters zijn .NET-API's die worden gebruikt voor lichtgewicht, platformoverschrijdende en bijna realtime prestatiegegevensverzameling. EventCounters zijn toegevoegd als platformoverschrijdend alternatief voor de 'prestatiemeteritems' van .NET Framework in Windows. In dit artikel leert u wat EventCounters zijn, hoe u ze implementeert en hoe u ze kunt gebruiken.

De .NET-runtime en enkele .NET-bibliotheken publiceren basisgegevens over diagnostische gegevens met behulp van EventCounters vanaf .NET Core 3.0. Naast de EventCounters die worden geleverd door de .NET-runtime, kunt u ervoor kiezen om uw eigen EventCounters te implementeren. EventCounters kunnen worden gebruikt om verschillende metrische gegevens bij te houden. Meer informatie hierover vindt u in bekende EventCounters in .NET

EventCounters live als onderdeel van een EventSource, en worden automatisch regelmatig naar listenerhulpprogramma's gepusht. Net als alle andere gebeurtenissen op een EventSource, kunnen ze zowel in-proc als out-of-proc worden gebruikt via EventListener en EventPipe. Dit artikel is gericht op de platformoverschrijdende mogelijkheden van EventCounters en sluit PerfView en ETW (Event Tracing for Windows) opzettelijk uit, hoewel beide kunnen worden gebruikt met EventCounters.

Afbeelding van EventCounters in-proc- en out-of-proc-diagram

Overzicht van EventCounter-API

Er zijn twee primaire categorieën EventCounters. Sommige tellers zijn voor 'rate'-waarden, zoals het totale aantal uitzonderingen, het totale aantal pc's en het totale aantal aanvragen. Andere tellers zijn 'momentopname'-waarden, zoals heapgebruik, CPU-gebruik en werksetgrootte. Binnen elk van deze categorieën tellers zijn er twee typen tellers die variëren op basis van hoe ze hun waarde krijgen. Pollingtellers halen hun waarde op via een callback en niet-pollingtellers hebben hun waarden rechtstreeks ingesteld op het tellerexemplaren.

De tellers worden vertegenwoordigd door de volgende implementaties:

Een gebeurtenislistener geeft aan hoe lang meetintervallen zijn. Aan het einde van elk interval wordt een waarde naar de listener voor elke teller verzonden. De implementaties van een prestatiemeteritem bepalen welke API's en berekeningen worden gebruikt om elke interval de waarde te produceren.

  • De EventCounter records bevatten een set waarden. Met EventCounter.WriteMetric de methode wordt een nieuwe waarde aan de set toegevoegd. Met elk interval wordt een statistische samenvatting voor de set berekend, zoals het minimum, het maximum en het gemiddelde. Het hulpmiddel dotnet-counters geeft altijd de gemiddelde waarde weer. Het EventCounter is handig om een discrete set bewerkingen te beschrijven. Veelvoorkomend gebruik kan bestaan uit het controleren van de gemiddelde grootte in bytes van recente IO-bewerkingen of de gemiddelde monetaire waarde van een set financiële transacties.

  • De IncrementingEventCounter records een voorlopig totaal voor elk tijdsinterval. De IncrementingEventCounter.Increment methode wordt toegevoegd aan het totaal. Als bijvoorbeeld Increment() drie keer wordt aangeroepen tijdens één interval met waarden12, en5, wordt het lopende totaal 8 gerapporteerd als de tellerwaarde voor dit interval. Het hulpprogramma dotnet-counters geeft de snelheid weer als de opgenomen totale /tijd. Het IncrementingEventCounter is handig om te meten hoe vaak een actie plaatsvindt, zoals het aantal verwerkte aanvragen per seconde.

  • Er PollingCounter wordt een callback gebruikt om de gerapporteerde waarde te bepalen. Met elk tijdsinterval wordt de door de gebruiker opgegeven callback-functie aangeroepen en wordt de retourwaarde gebruikt als de tellerwaarde. Een PollingCounter kan worden gebruikt om een query uit te voeren op een metrische waarde van een externe bron, bijvoorbeeld om de huidige gratis bytes op een schijf op te halen. Het kan ook worden gebruikt om aangepaste statistieken te rapporteren die op aanvraag kunnen worden berekend door een toepassing. Voorbeelden hiervan zijn het rapporteren van het 95e percentiel van recente latenties van aanvragen, of de huidige treffer- of missverhouding van een cache.

  • Er IncrementingPollingCounter wordt een callback gebruikt om de gerapporteerde incrementele waarde te bepalen. Met elk tijdsinterval wordt de callback aangeroepen en vervolgens het verschil tussen de huidige aanroep en de laatste aanroep is de gerapporteerde waarde. Het hulpprogramma dotnet-tellers geeft altijd het verschil weer als een snelheid, de gerapporteerde waarde /tijd. Deze teller is handig wanneer het niet haalbaar is om een API aan te roepen voor elk exemplaar, maar het is mogelijk om een query uit te voeren op het totale aantal exemplaren. U kunt bijvoorbeeld het aantal bytes rapporteren dat per seconde naar een bestand is geschreven, zelfs zonder een melding telkens wanneer een byte wordt geschreven.

Een EventSource implementeren

Met de volgende code wordt een voorbeeld EventSource geïmplementeerd dat wordt weergegeven als de benoemde "Sample.EventCounter.Minimal" provider. Deze bron bevat een EventCounter representatie van de aanvraagverwerkingstijd. Een dergelijke teller heeft een naam (dat wil gezegd, de unieke id in de bron) en een weergavenaam, die beide worden gebruikt door listenerhulpprogramma's zoals 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);
    }
}

U gebruikt dotnet-counters ps om een lijst weer te geven met .NET-processen die kunnen worden bewaakt:

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

Geef de EventSource naam door aan de --counters optie om te beginnen met het bewaken van uw teller:

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

In het volgende voorbeeld ziet u de uitvoer van de monitor:

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

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

Druk op q om de bewakingsopdracht te stoppen.

Voorwaardelijke meteritems

Bij het implementeren van een EventSource, kunnen de tellers voorwaardelijk worden geïnstantieerd wanneer de EventSource.OnEventCommand methode wordt aangeroepen met een Command waarde van EventCommand.Enable. Als u een exemplaar van een teller alleen veilig wilt instantiëren als dit het geval is null, gebruikt u de toewijzingsoperator null-coalescing. Daarnaast kunnen aangepaste methoden de IsEnabled methode evalueren om te bepalen of de huidige gebeurtenisbron al dan niet is ingeschakeld.

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

Tip

Voorwaardelijke tellers zijn tellers die voorwaardelijk worden geïnstantieerd, een microoptimalisatie. De runtime gebruikt dit patroon voor scenario's waarbij tellers normaal gesproken niet worden gebruikt, om een fractie van een milliseconde op te slaan.

.NET Core Runtime-voorbeeldtellers

Er zijn veel geweldige voorbeeld-implementaties in de .NET Core-runtime. Hier volgt de runtime-implementatie voor de teller waarmee de grootte van de werkset van de toepassing wordt bijgehouden.

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

De PollingCounter huidige hoeveelheid fysiek geheugen die is toegewezen aan het proces (werkset) van de app wordt gerapporteerd, omdat hiermee een metrische waarde op een bepaald moment wordt vastgelegd. De callback voor het peilen van een waarde is de opgegeven lambda-expressie. Dit is slechts een aanroep naar de System.Environment.WorkingSet API. DisplayName en DisplayUnits zijn optionele eigenschappen die kunnen worden ingesteld om de consumentenzijde van het teller te helpen de waarde duidelijker weer te geven. Dotnet-tellers gebruiken deze eigenschappen bijvoorbeeld om de meer weergavevriendelijke versie van de tellernamen weer te geven.

Belangrijk

De DisplayName eigenschappen zijn niet gelokaliseerd.

Voor de PollingCounter, en de IncrementingPollingCounter, hoeft niets anders te worden gedaan. Ze peilen beide de waarden zelf met een interval dat door de consument wordt aangevraagd.

Hier volgt een voorbeeld van een runtimemeteritem dat is geïmplementeerd met behulp van IncrementingPollingCounter.

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

De IncrementingPollingCounter API gebruikt de Monitor.LockContentionCount API om de toename van het totale aantal vergrendelingsconflicten te rapporteren. De DisplayRateTimeScale eigenschap is optioneel, maar wanneer deze wordt gebruikt, kan dit een hint geven voor het tijdsinterval waarop de teller het beste wordt weergegeven. Het aantal vergrendelingsconflicten wordt bijvoorbeeld het beste weergegeven als aantal per seconde, dus DisplayRateTimeScale het is ingesteld op één seconde. De weergavesnelheid kan worden aangepast voor verschillende typen tarieftellers.

Notitie

Het DisplayRateTimeScale wordt niet gebruikt door dotnet-tellers en gebeurtenislisteners zijn niet vereist om deze te gebruiken.

Er zijn meer tegenmplementaties die moeten worden gebruikt als referentie in de .NET Runtime-opslagplaats .

Gelijktijdigheid

Tip

De EventCounters-API garandeert geen threadveiligheid. Wanneer de gedelegeerden worden doorgegeven aan PollingCounter of IncrementingPollingCounter instanties worden aangeroepen door meerdere threads, is het uw verantwoordelijkheid om de thread-veiligheid van de gemachtigden te garanderen.

Denk bijvoorbeeld aan het volgende EventSource om aanvragen bij te houden.

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

De AddRequest() methode kan worden aangeroepen vanuit een aanvraaghandler en de RequestRateCounter waarde wordt gepeild op het interval dat is opgegeven door de consument van de teller. De AddRequest() methode kan echter door meerdere threads tegelijk worden aangeroepen, waardoor een racevoorwaarde wordt aangeroepen _requestCount. Een thread-veilige alternatieve manier om het _requestCount te verhogen is te gebruiken Interlocked.Increment.

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

Om gescheurde leesbewerkingen (op 32-bits architecturen) van het longveldgebruik _requestCountInterlocked.Readte voorkomen.

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

EventCounters gebruiken

Er zijn twee primaire manieren om EventCounters te gebruiken: in-proc en out-of-proc. Het verbruik van EventCounters kan worden onderscheiden in drie lagen van verschillende verbruikstechnologieën.

  • Gebeurtenissen in een onbewerkte stroom transporteren via ETW of EventPipe:

    ETW-API's worden geleverd met het Windows-besturingssysteem en EventPipe is toegankelijk als een .NET-API of het diagnostische IPC-protocol.

  • De binaire gebeurtenisstroom decoderen in gebeurtenissen:

    De TraceEvent-bibliotheek verwerkt zowel ETW- als EventPipe-stroomindelingen.

  • Opdrachtregel- en GUI-hulpprogramma's:

    Hulpprogramma's zoals PerfView (ETW of EventPipe), dotnet-counters (alleen EventPipe) en dotnet-monitor (alleen EventPipe).

Out-of-proc gebruiken

Het gebruik van EventCounters out-of-proc is een algemene benadering. U kunt dotnet-tellers gebruiken om ze op een platformoverschrijdende manier te gebruiken via een EventPipe. Het dotnet-counters hulpprogramma is een platformoverschrijdend dotnet CLI-hulpprogramma dat kan worden gebruikt om de prestatiemeteritems te bewaken. Als u wilt leren hoe dotnet-counters u uw tellers kunt bewaken, raadpleegt u dotnet-tellers of doorloopt u de metingprestaties met behulp van de zelfstudie EventCounters .

dotnet-trace

Het dotnet-trace hulpprogramma kan worden gebruikt om de tellergegevens via een EventPipe te gebruiken. Hier volgt een voorbeeld van het verzamelen van dotnet-trace tellergegevens.

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

Zie de documentatie over dotnet-trace voor meer informatie over het verzamelen van prestatiemeteritems in de loop van de tijd.

Azure Application Insights

EventCounters kunnen worden gebruikt door Azure Monitor, met name Azure-toepassing Insights. Tellers kunnen worden toegevoegd en verwijderd en u kunt aangepaste tellers of bekende tellers opgeven. Zie Aanpassen van tellers die moeten worden verzameld voor meer informatie.

dotnet-monitor

Het dotnet-monitor hulpprogramma maakt het eenvoudiger om diagnostische gegevens te openen vanuit een .NET-proces op een externe en geautomatiseerde manier. Naast traceringen kan het metrische gegevens bewaken, geheugendumps verzamelen en GC-dumps verzamelen. Het wordt gedistribueerd als een CLI-hulpprogramma en een docker-installatiekopieën. Er wordt een REST API beschikbaar gemaakt en de verzameling diagnostische artefacten vindt plaats via REST-aanroepen.

Zie dotnet-monitor voor meer informatie.

In-proc gebruiken

U kunt de prestatiemeteritems gebruiken via de EventListener API. Een EventListener is een in-proc manier om gebeurtenissen te gebruiken die zijn geschreven door alle exemplaren van een EventSource in uw toepassing. Zie EventListenervoor meer informatie over het gebruik van de EventListener API.

Eerst moet de EventSource tellerwaarde worden ingeschakeld. Overschrijf de EventListener.OnEventSourceCreated methode om een melding te ontvangen wanneer er een EventSource wordt gemaakt en als dit het juiste EventSource is met uw EventCounters, kunt u deze aanroepen EventListener.EnableEvents . Hier volgt een voorbeeld van onderdrukking:

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

Voorbeeldcode

Hier volgt een voorbeeldklasse EventListener waarmee alle namen en waarden van de tellers van de .NET-runtime EventSourceworden afgedrukt voor het publiceren van de interne tellers (System.Runtime) elke seconde.

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

Zoals hierboven wordt weergegeven, moet u ervoor zorgen dat het "EventCounterIntervalSec" argument is ingesteld in het argument bij het filterPayload aanroepenEnableEvents. Anders kunnen de tellers geen waarden leegmaken, omdat ze niet weten met welk interval het moet worden leeggemaakt.

Zie ook