Az eseményszámlálók a .NET-ben

Ez a cikk a következőre vonatkozik: ✔️ .NET Core 3.0 SDK és újabb verziók

Megjegyzés

Az új .NET-projektek fejlesztéséhez a Microsoft az újabb System.Diagnostics.Metrics API-kat javasolja. A System.Diagnostics.Metrics API-k nagyobb funkcionalitást, szabványosítást és integrációt kínálnak az eszközök szélesebb körű ökoszisztémájával. További információkért tekintse meg a Metrics API összehasonlítását .

Az EventCounters .NET API-k egyszerű, platformfüggetlen és közel valós idejű teljesítménymetrikagyűjtéshez használatosak. Az EventCounters platformfüggetlen alternatívaként lett hozzáadva a Windows .NET-keretrendszer teljesítményszámlálóihoz. Ebben a cikkben megtudhatja, hogy mik az EventCountersek, hogyan implementálhatja őket, és hogyan használhatja fel őket.

A .NET-futtatókörnyezet és néhány .NET-kódtár a .NET Core 3.0-tól kezdődőEn az EventCounters használatával teszi közzé az alapvető diagnosztikai információkat. A .NET-futtatókörnyezet által biztosított EventCounters mellett saját EventCounters-eket is implementálhat. Az EventCounters különböző metrikák nyomon követésére használható. További információ ezekről .NET-jól ismert EventCounters szolgáltatásában.

Az EventCounters egy EventSource részeként működnek, és rendszeresen automatikusan továbbítják a figyelőeszközökre. A EventSource többi eseményéhez hasonlóan az EventListener in-proc és out-of-proc módokon is felhasználhatóak, az EventPipe segítségével. Ez a cikk az EventCounters platformfüggetlen képességeire összpontosít, és szándékosan kizárja a PerfView-t és az ETW-t (Windows-eseménykövetés) – bár mindkettő használható az EventCounters használatával.

EventCounters in-proc és out-of-proc diagram képe

Az EventCounter API áttekintése

Az EventCounters két elsődleges kategóriát tartalmaz. Egyes számlálók a "ráta" értékekhez tartoznak, például a kivételek teljes száma, a GCs-k teljes száma és a kérelmek teljes száma. Más számlálók a "pillanatkép" értékek, például a halomhasználat, a processzorhasználat és a munkakészlet mérete. A számlálók ezen kategóriáiban két számlálótípus létezik, amelyek az értékük lekérésétől függően változnak. A lehívó számlálók visszahívási funkcióval érik el az értéküket, míg a nem lehívó számlálók esetében az értékük közvetlenül a számlálópéldányon van beállítva.

A számlálókat a következő implementációk képviselik:

Az eseményfigyelő megadja, hogy mennyi a mérési időköz. Az egyes intervallumok végén egy érték lesz továbbítva a figyelőnek az egyes számlálókhoz. A számláló implementációi határozzák meg, hogy milyen API-kat és számításokat használnak az egyes intervallumok értékének előállításához.

  • A EventCounter rekordok egy értékkészletet rögzítenek. A EventCounter.WriteMetric metódus új értéket ad hozzá a készlethez. Az egyes intervallumok esetében a rendszer kiszámítja a készlet statisztikai összegzését, például a minimális, a maximális és a középértékeket. A dotnet-counters eszköz mindig megjeleníti a középértéket. Ez a EventCounter hasznos a különálló műveletek leírásához. A gyakori használat magában foglalhatja a legutóbbi IO-műveletek bájtban kifejezett átlagos méretét vagy egy pénzügyi tranzakció átlagos pénzügyi értékét.

  • Az IncrementingEventCounter egyes időintervallumok futási összegét rögzíti. A IncrementingEventCounter.Increment metódus hozzáad a teljes összeghez. Ha például Increment() egy intervallumban háromszor hívjuk meg az értékekkel 1, 2 és 5, akkor a 8 futó összegét az intervallum számlálóértékeiként jelentjük. A dotnet-counters eszköz megmutatja a ráta értékét, amely a rögzített összeg/idő hányadosaként van kifejezve. Ez IncrementingEventCounter hasznos annak méréséhez, hogy milyen gyakran történik egy művelet, például a másodpercenként feldolgozott kérelmek száma.

  • A PollingCounter jelentés értéke visszahívással határozható meg. Minden időintervallum esetén a rendszer meghívja a felhasználó által megadott visszahívási függvényt, és a visszatérési értéket használja számlálóértékként. A PollingCounter metrika külső forrásból való lekérdezésére használható, például az aktuális szabad bájtok lekérésére egy lemezen. Az alkalmazás igény szerint kiszámítható egyéni statisztikák jelentésére is használható. Ilyenek például a legutóbbi kérések késésének 95. percentilisének jelentése, vagy a gyorsítótár aktuális találati vagy kihagyási aránya.

  • A IncrementingPollingCounter visszahívással határozza meg a jelentett növekmény értékét. Minden időintervallum esetén a rendszer meghívja a visszahívást, majd az aktuális hívás és az utolsó hívás közötti különbség a jelentett érték. A dotnet-counters eszköz mindig megjeleníti a különbséget, mint egy ráta, a jelentett érték / idő. Ez a számláló akkor hasznos, ha nem lehet api-t meghívni minden előforduláshoz, de lekérdezhető az előfordulások teljes száma. Jelentheti például a fájlba írt bájtok másodpercenkénti számát, még értesítés nélkül is minden bájt megírásakor.

EventSource implementálása

Az alábbi kód egy nevesített EventSource szolgáltatóként közzétett mintát "Sample.EventCounter.Minimal" valósít meg. Ez a forrás egy EventCounter elemet tartalmaz, amely a kérés-feldolgozási időt képviseli. Az ilyen számlálónak van egy neve (azaz a forrás egyedi azonosítója) és egy megjelenítendő neve, melyeket a figyelőeszközök, mint például a dotnet-counters használnak.

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

A figyelhető .NET-folyamatok listájának megjelenítésére használható dotnet-counters ps :

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

Adja át a EventSource nevet a --counters opciónak a számláló figyelésének elindításához.

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

Az alábbi példa a monitor kimenetét mutatja be:

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

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

A monitorozási parancs leállításához nyomja le a q billentyűt.

Feltételes számlálók

Az EventSource implementálásakor a számlálók feltételesen példányosíthatók, ha a EventSource.OnEventCommand metódust a rendszer a Command értékkel hívja meg EventCommand.Enable. Annak érdekében, hogy biztonságosan példányosítson egy számlálópéldányt, csak akkor alkalmazza a null-egyesítés hozzárendelés operátort, ha null. Emellett az egyéni metódusok kiértékelhetik a IsEnabled metódust annak megállapításához, hogy engedélyezve van-e az aktuális eseményforrás.

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

Tipp.

A feltételes számlálók azok a számlálók, amelyeket feltételesen példányosítanak, ami egy mikrooptimalizáció. A futtatókörnyezet ezt a mintát alkalmazza olyan helyzetekben, ahol a számlálókat általában nem használják, az ezredmásodperc töredékének mentéséhez.

.NET Core-futtatókörnyezeti példaszámlálók

A .NET Core-futtatókörnyezetben számos nagyszerű példa implementáció található. Itt található a számláló futtatókörnyezeti implementációja, amely nyomon követi az alkalmazás munkakészletének méretét.

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

A PollingCounter jelentés az alkalmazás folyamatához (munkakészletéhez) hozzárendelt fizikai memória aktuális mennyiségét jelenti, mivel egy metrikát rögzít egy adott pillanatban. Az érték szavazásának visszahívása a megadott lambda kifejezés, ami valójában csak egy hívás a System.Environment.WorkingSet API-hoz. DisplayName és DisplayUnits választható tulajdonságok, amelyek beállíthatók úgy, hogy segítsenek a számláló fogyasztói oldalán az érték egyértelműbb megjelenítésében. A dotnet-counters például ezeket a tulajdonságokat használja a számlálónevek megjeleníthetőbb verziójának megjelenítéséhez.

Fontos

A DisplayName tulajdonságok nincsenek honosítva.

PollingCounter és IncrementingPollingCounter esetén semmi mást nem kell tenni. Mindketten maguk kérdezik le az értékeket a fogyasztó által kért időközönként.

Íme egy példa egy futásidejű számlálóra, amely a IncrementingPollingCounter segítségével van megvalósítva.

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

A IncrementingPollingCounter a Monitor.LockContentionCount API-t használja a teljes zárolási versengés számának növekményének jelentésére. A DisplayRateTimeScale tulajdonság nem kötelező, de használat esetén jelzi, hogy a számláló melyik időintervallumban jelenik meg a legjobban. A zárolási versengés száma például másodpercenkénti számként jelenik meg a legjobban, így DisplayRateTimeScale az értéke egy másodpercre van állítva. A megjelenítési sebesség különböző típusú sebességszámlálókhoz módosítható.

Megjegyzés

A DisplayRateTimeScaledotnet-counters nem használják, és az eseményfigyelőknek nem kell használniuk azt.

A .NET futtatókörnyezeti adattárban több számláló implementáció is használható referenciaként.

Egyidejűség

Tipp.

Az EventCounters API nem garantálja a szál biztonságát. Amikor a PollingCounter vagy IncrementingPollingCounter példányokhoz továbbított delegáltakat több szál hívja meg, az Ön felelőssége, hogy garantálja a delegáltak szálbiztonságát.

A kérések nyomon követéséhez például vegye figyelembe az alábbiakat EventSource .

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

A AddRequest() metódus meghívható egy kérelemkezelőtől, és a RequestRateCounter számláló fogyasztója által megadott időközönként kérdezi le az értéket. A AddRequest() metódust azonban egyszerre több szál is meghívhatja, ami versenyhelyzetet hoz létre._requestCount A szálbiztos alternatív módszer a _requestCount értékének növelésére a Interlocked.Increment használata.

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

A megszakadt olvasások elkerülése érdekében (32 bites architektúrákon) a long mező esetén használja a _requestCountInterlocked.Read.

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

EventCounters fogyasztása

Az EventCounter-eket két fő módon lehet használni: in-proc és out-of-proc. Az EventCounters használata a különböző fogyasztó technológiák három rétegében különböztethető meg.

  • Események átvitele nyers streamben ETW-en vagy EventPipe-en keresztül:

    Az ETW API-k a Windows operációs rendszert használják, az EventPipe pedig .NET API-ként vagy diagnosztikai IPC protokollként érhető el.

  • A bináris eseményfolyam dekódolása eseményekké:

    A TraceEvent kódtár az ETW és az EventPipe streamformátumokat is kezeli.

  • Parancssori és grafikus felhasználói felület eszközei:

    Olyan eszközök, mint a PerfView (ETW vagy EventPipe), a dotnet-counters (csak EventPipe) és a dotnet-monitor (csak EventPipe).

Használaton kívüli használat

Az EventCounters igényen kívüli felhasználása gyakori módszer. A dotnet-counters használatával platformfüggetlen módon használhatja fel őket egy EventPipe-en keresztül. Az dotnet-counters eszköz egy platformfüggetlen dotnet CLI globális eszköz, amely a számlálóértékek figyelésére használható. Ha szeretné megtudni, hogyan használhatja a számlálók monitorozására, lásd a dotnet-counters dokumentációt, vagy a teljesítmény mérése az EventCounters használatával végigjárható az oktatóanyagban.

Azure Application Insights

Az EventCounters-t az Azure Monitor, különösen az Azure Application Insights képes feldolgozni. A számlálók hozzáadhatók és eltávolíthatók, és szabadon megadhat egyéni számlálókat vagy jól ismert számlálókat. További információkért lásd: A gyűjtendő számlálók testreszabása.

dotnet-monitor

A dotnet-monitor eszköz megkönnyíti a diagnosztikához való hozzáférést egy .NET-folyamatból távoli és automatizált megközelítését alkalmazva. A nyomkövetések mellett figyelheti a metrikákat, valamint gyűjthet memóriaképeket és GC-dumpokat is. CLI-eszközként és docker-rendszerképként is el van osztva. EGY REST API-t tesz elérhetővé, és a diagnosztikai összetevők gyűjteménye REST-hívásokon keresztül történik.

További információ: dotnet-monitor.

Folyamaton belüli használat

A számlálóértékeket az EventListener API-val használhatja fel. Az EventListener az alkalmazás összes EventSource példánya által írt események belső feldolgozásának módja. Az API használatával kapcsolatos további információkért lásd EventListenerEventListener:

Először engedélyezni kell a EventSource-t, amely a számláló értékét állítja elő. Felülbírálhatja a EventListener.OnEventSourceCreated metódust, hogy értesítést kapjon, amikor egy EventSource példány jön létre; és ha az EventCounters-hez tartozó helyes EventSource-ről van szó, akkor meghívhatja rajta a EventListener.EnableEvents-t. Íme egy példa felülbírálásra:

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

Mintakód

Íme egy mintaosztály EventListener , amely a .NET-futtatókörnyezet összes számlálónevét és értékét kinyomtatja EventSourcea belső számlálók (System.Runtime) másodpercenkénti közzétételéhez.

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

Ahogy fentebb látható, meg kell győződnie arról, hogy a "EventCounterIntervalSec" argumentum be van állítva a filterPayload argumentumban, amikor a EnableEvents hívásra kerül. Ellenkező esetben a számlálók nem tudják kiüríteni az értékeket, mivel nem tudja, hogy milyen időközönként kell kiüríteni.

Lásd még