Bagikan melalui


EventCounters di .NET

Artikel ini berlaku untuk: ✔️ .NET Core 3.0 SDK dan versi yang lebih baru

Catatan

Untuk mengembangkan proyek .NET baru, Microsoft merekomendasikan penggunaan API System.Diagnostics.Metrics yang lebih baru. API System.Diagnostics.Metrics menawarkan peningkatan fungsionalitas, standardisasi, dan integrasi dengan ekosistem alat yang lebih luas. Lihat perbandingan API metrik untuk informasi selengkapnya.

EventCounters adalah API .NET yang digunakan untuk pengumpulan metrik performa yang ringan, lintas platform, dan hampir real-time. EventCounters ditambahkan sebagai alternatif lintas platform untuk "penghitung kinerja" .NET Framework di Windows. Dalam artikel ini, Anda akan mempelajari apa itu EventCounters, cara mengimplementasikannya, dan cara menggunakannya.

Runtime .NET dan beberapa pustaka .NET menerbitkan informasi diagnostik dasar menggunakan EventCounters mulai dari .NET Core 3.0. Selain EventCounters yang disediakan oleh runtime .NET, Anda dapat memilih untuk menerapkan EventCounters Anda sendiri. EventCounters dapat digunakan untuk melacak berbagai metrik. Pelajari selengkapnya tentang mereka di EventCounters terkenal di .NET

EventCounters hidup sebagai bagian EventSourcedari , dan secara otomatis didorong ke alat pendengar secara teratur. Seperti semua peristiwa lain pada EventSource, mereka dapat dikonsumsi baik dalam proc maupun di luar proc melalui EventListener dan EventPipe. Artikel ini berfokus pada kemampuan lintas platform EventCounters, dan dengan sengaja mengecualikan PerfView dan ETW (Pelacakan Peristiwa untuk Windows) - meskipun keduanya dapat digunakan dengan EventCounters.

Gambar diagram eventCounters in-proc dan out-of-proc

Gambaran umum API EventCounter

Ada dua kategori utama EventCounters. Beberapa penghitung adalah untuk nilai "tarif", seperti jumlah total pengecualian, jumlah total GC, dan jumlah total permintaan. Penghitung lain adalah nilai "rekam jepret", seperti penggunaan timbunan, penggunaan CPU, dan ukuran set kerja. Dalam masing-masing kategori penghitung ini, ada dua jenis penghitung yang bervariasi menurut cara mereka mendapatkan nilainya. Penghitung polling mengambil nilainya melalui panggilan balik, dan penghitung non-polling memiliki nilainya yang langsung diatur pada instans penghitung.

Penghitung diwakili oleh implementasi berikut:

Pendengar peristiwa menentukan berapa lama interval pengukuran. Di akhir setiap interval, nilai dikirimkan ke pendengar untuk setiap penghitung. Implementasi penghitung menentukan API dan perhitungan apa yang digunakan untuk menghasilkan nilai setiap interval.

  • Merekam EventCounter sekumpulan nilai. Metode menambahkan EventCounter.WriteMetric nilai baru ke set. Dengan setiap interval, ringkasan statistik untuk set dihitung, seperti min, maks, dan rata-rata. Alat penghitung dotnet akan selalu menampilkan nilai rata-rata. EventCounter berguna untuk menggambarkan serangkaian operasi diskrit. Penggunaan umum dapat mencakup pemantauan ukuran rata-rata dalam byte operasi IO terbaru, atau nilai moneter rata-rata dari serangkaian transaksi keuangan.

  • Merekam IncrementingEventCounter total berjalan untuk setiap interval waktu. Metode IncrementingEventCounter.Increment menambahkan ke total. Misalnya, jika Increment() dipanggil tiga kali selama satu interval dengan nilai 1, , 2dan 5, maka total 8 berjalan akan dilaporkan sebagai nilai penghitung untuk interval ini. Alat penghitung dotnet akan menampilkan laju sebagai total /waktu yang direkam. IncrementingEventCounter berguna untuk mengukur seberapa sering tindakan terjadi, seperti jumlah permintaan yang diproses per detik.

  • PollingCounter menggunakan panggilan balik untuk menentukan nilai yang dilaporkan. Dengan setiap interval waktu, fungsi panggilan balik yang disediakan pengguna dipanggil dan nilai pengembalian digunakan sebagai nilai penghitung. PollingCounter Dapat digunakan untuk mengkueri metrik dari sumber eksternal, misalnya mendapatkan byte gratis saat ini pada disk. Ini juga dapat digunakan untuk melaporkan statistik kustom yang dapat dihitung sesuai permintaan oleh aplikasi. Contohnya termasuk melaporkan persentil ke-95 dari latensi permintaan terbaru, atau rasio hit atau miss saat ini dari cache.

  • IncrementingPollingCounter menggunakan panggilan balik untuk menentukan nilai kenaikan yang dilaporkan. Dengan setiap interval waktu, panggilan balik dipanggil, dan kemudian perbedaan antara pemanggilan saat ini, dan pemanggilan terakhir adalah nilai yang dilaporkan. Alat penghitung dotnet akan selalu menampilkan perbedaan sebagai laju, nilai /waktu yang dilaporkan. Penghitung ini berguna ketika tidak layak untuk memanggil API pada setiap kemunculan, tetapi dimungkinkan untuk mengkueri jumlah total kemunculan. Misalnya, Anda dapat melaporkan jumlah byte yang ditulis ke file per detik, bahkan tanpa pemberitahuan setiap kali byte ditulis.

Menerapkan EventSource

Kode berikut mengimplementasikan sampel EventSource yang diekspos sebagai penyedia bernama "Sample.EventCounter.Minimal" . Sumber ini berisi waktu pemrosesan permintaan yang EventCounter mewakili. Penghitung seperti itu memiliki nama (yaitu, ID uniknya di sumbernya) dan nama tampilan, keduanya digunakan oleh alat pendengar seperti penghitung dotnet.

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

Anda menggunakan dotnet-counters ps untuk menampilkan daftar proses .NET yang dapat dipantau:

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

Teruskan EventSource nama ke --counters opsi untuk mulai memantau penghitung Anda:

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

Contoh berikut menunjukkan output monitor:

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

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

Tekan q untuk menghentikan perintah pemantauan.

Penghitung bersyarah

Saat menerapkan EventSource, penghitung yang berisi dapat diinstansiasi secara kondisional ketika EventSource.OnEventCommand metode dipanggil dengan Command nilai EventCommand.Enable. Untuk membuat instans penghitung dengan aman hanya jika instans penghitung , nullgunakan operator penugasan null-coalescing. Selain itu, metode kustom dapat mengevaluasi IsEnabled metode untuk menentukan apakah sumber peristiwa saat ini diaktifkan atau tidak.

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

Penghitung bersyarat adalah penghitung yang dibuat secara kondisional, pengoptimalan mikro. Runtime mengadopsi pola ini untuk skenario di mana penghitung biasanya tidak digunakan, untuk menyimpan sebagian kecil dari milidetik.

Penghitung contoh runtime .NET Core

Ada banyak contoh implementasi yang bagus dalam runtime .NET Core. Berikut adalah implementasi runtime untuk penghitung yang melacak ukuran set kerja aplikasi.

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

Melaporkan PollingCounter jumlah memori fisik saat ini yang dipetakan ke proses (set kerja) aplikasi, karena menangkap metrik pada saat itu juga. Panggilan balik untuk polling nilai adalah ekspresi lambda yang disediakan, yang hanya panggilan ke System.Environment.WorkingSet API. DisplayName dan DisplayUnits merupakan properti opsional yang dapat diatur untuk membantu sisi konsumen penghitung untuk menampilkan nilai dengan lebih jelas. Misalnya, penghitung dotnet menggunakan properti ini untuk menampilkan versi nama penghitung yang lebih ramah tampilan.

Penting

Properti DisplayName tidak dilokalkan.

PollingCounterUntuk , dan IncrementingPollingCounter, tidak ada lagi yang perlu dilakukan. Keduanya melakukan polling nilai itu sendiri pada interval yang diminta oleh konsumen.

Berikut adalah contoh penghitung runtime yang diterapkan menggunakan IncrementingPollingCounter.

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

IncrementingPollingCounter menggunakan API untuk melaporkan kenaikan jumlah total ketidakcocokan Monitor.LockContentionCount kunci. Properti DisplayRateTimeScale bersifat opsional, tetapi ketika digunakan, properti dapat memberikan petunjuk untuk interval waktu berapa penghitung paling baik ditampilkan. Misalnya, jumlah pertikaian kunci paling baik ditampilkan sebagai hitungan per detik, sehingga DisplayRateTimeScale diatur ke satu detik. Laju tampilan dapat disesuaikan untuk berbagai jenis penghitung tarif.

Catatan

DisplayRateTimeScale tidak digunakan oleh penghitung dotnet, dan pendengar peristiwa tidak diperlukan untuk menggunakannya.

Ada lebih banyak implementasi penghitung untuk digunakan sebagai referensi dalam repositori runtime .NET.

Konkurensi

Tip

API EventCounters tidak menjamin keamanan utas. Ketika delegasi yang diteruskan ke PollingCounter atau IncrementingPollingCounter instans dipanggil oleh beberapa utas, Anda bertanggung jawab untuk menjamin keamanan utas delegasi.

Misalnya, pertimbangkan hal berikut EventSource untuk melacak permintaan.

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

Metode AddRequest() ini dapat dipanggil dari penangan permintaan, dan RequestRateCounter polling nilai pada interval yang ditentukan oleh konsumen penghitung. Namun, metode ini AddRequest() dapat dipanggil oleh beberapa utas sekaligus, menempatkan kondisi balapan pada _requestCount. Cara alternatif yang aman untuk menaikkan _requestCount utas adalah dengan menggunakan Interlocked.Increment.

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

Untuk mencegah pembacaan robek (pada arsitektur 32-bit) dari long-field _requestCount gunakan Interlocked.Read.

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

Mengonsumsi EventCounters

Ada dua cara utama untuk menggunakan EventCounters: in-proc dan out-of-proc. Konsumsi EventCounters dapat dibedakan menjadi tiga lapisan dari berbagai teknologi yang mengonsumsi.

  • Mengangkut peristiwa dalam aliran mentah melalui ETW atau EventPipe:

    API ETW dilengkapi dengan OS Windows, dan EventPipe dapat diakses sebagai API .NET, atau protokol IPC diagnostik.

  • Mendekode aliran peristiwa biner ke dalam peristiwa:

    Pustaka TraceEvent menangani format aliran ETW dan EventPipe.

  • Alat baris perintah dan GUI:

    Alat seperti PerfView (ETW atau EventPipe), penghitung dotnet (hanya EventPipe), dan dotnet-monitor (hanya EventPipe).

Mengonsumsi out-of-proc

Mengonsumsi EventCounters di luar proc adalah pendekatan umum. Anda dapat menggunakan penghitung dotnet untuk menggunakannya secara lintas platform melalui EventPipe. Alat ini dotnet-counters adalah alat global CLI dotnet lintas platform yang dapat digunakan untuk memantau nilai penghitung. Untuk mengetahui cara menggunakan dotnet-counters untuk memantau penghitung Anda, lihat penghitung dotnet, atau bekerja melalui tutorial Mengukur performa menggunakan EventCounters .

Azure Application Insights

EventCounters dapat digunakan oleh Azure Monitor, khususnya Azure Application Insights. Penghitung dapat ditambahkan dan dihapus, dan Anda bebas menentukan penghitung kustom, atau penghitung terkenal. Untuk informasi selengkapnya, lihat Menyesuaikan penghitung yang akan dikumpulkan.

dotnet-monitor

Alat ini dotnet-monitor memudahkan untuk mengakses diagnostik dari proses .NET dengan cara jarak jauh dan otomatis. Selain jejak, ia dapat memantau metrik, mengumpulkan cadangan memori, dan mengumpulkan cadangan GC. Ini didistribusikan sebagai alat CLI dan gambar docker. Ini mengekspos REST API, dan pengumpulan artefak diagnostik terjadi melalui panggilan REST.

Untuk informasi selengkapnya, lihat dotnet-monitor.

Mengonsumsi dalam proc

Anda dapat menggunakan nilai penghitung melalui EventListener API. EventListener Adalah cara in-proc untuk mengonsumsi peristiwa apa pun yang ditulis oleh semua instans EventSource dalam aplikasi Anda. Untuk informasi selengkapnya tentang cara menggunakan EventListener API, lihat EventListener.

Pertama, EventSource yang menghasilkan nilai penghitung perlu diaktifkan. Ambil alih EventListener.OnEventSourceCreated metode untuk mendapatkan pemberitahuan saat EventSource dibuat, dan jika ini benar EventSource dengan EventCounters Anda, maka Anda dapat memanggilnya EventListener.EnableEvents . Berikut adalah contoh penimpaan:

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

Kode Sampel

Berikut adalah kelas sampel EventListener yang mencetak semua nama dan nilai penghitung dari runtime EventSource.NET , untuk menerbitkan penghitung internalnya (System.Runtime) setiap detik.

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

Seperti yang ditunjukkan di atas, Anda harus memastikan "EventCounterIntervalSec" argumen diatur dalam filterPayload argumen saat memanggil EnableEvents. Jika tidak, penghitung tidak akan dapat menghapus nilai karena tidak tahu pada interval mana penghitung harus dihapus.

Lihat juga