Freigeben über


Sammeln von Metriken

Dieser Artikel gilt für: ✔️ .NET Core 3.1 und höher ✔️ .NET Framework 4.6.1 und höher

Der instrumentierte Code kann numerische Messungen aufzeichnen, aber die Messungen müssen in der Regel aggregiert, übertragen und gespeichert werden, um nützliche Metriken für die Überwachung zu erstellen. Der Vorgang zum Aggregieren, Übertragen und Speichern von Daten wird als Sammlung bezeichnet. Dieses Tutorial zeigt mehrere Beispiele für das Sammeln von Metriken:

Weitere Informationen zur benutzerdefinierten Metrikinstrumentierung und Optionen finden Sie unter Vergleichen von Metrik-APIs.

Voraussetzungen

Erstellen einer Beispiel-App

Bevor Metriken erfasst werden können, müssen Messungen erstellt werden. In diesem Tutorial wird eine App erstellt, die über eine grundlegende Metrikinstrumentation verfügt. Die .NET-Runtime verfügt auch über verschiedene integrierte Metriken. Weitere Informationen zum Erstellen neuer Metriken mithilfe der System.Diagnostics.Metrics.Meter-API finden Sie im Instrumentierungs-Tutorial.

dotnet new console -o metric-instr
cd metric-instr
dotnet add package System.Diagnostics.DiagnosticSource

Ersetzen Sie den Inhalt von Program.cs durch den folgenden Code.

using System.Diagnostics.Metrics;

class Program
{
    static Meter s_meter = new("HatCo.HatStore", "1.0.0");
    static Counter<int> s_hatsSold = s_meter.CreateCounter<int>("hats-sold");

    static void Main(string[] args)
    {
        var rand = Random.Shared;
        Console.WriteLine("Press any key to exit");
        while (!Console.KeyAvailable)
        {
            //// Simulate hat selling transactions.
            Thread.Sleep(rand.Next(100, 2500));
            s_hatsSold.Add(rand.Next(0, 1000));
        }
    }
}

Der Code oben simuliert den Verkauf von Hüten in zufälligen Intervallen und zu zufälligen Zeiten.

Anzeigen von Metriken mit Dotnet-Counters

Dotnet-Counters ist ein Befehlszeilentool, mit dem Live-Metriken für .NET-Core-Anwendungen bei Bedarf angezeigt werden können. Es ist keine Einrichtung erforderlich, was für Ad-hoc-Untersuchungen hilfreich sein kann oder wenn überprüft werden soll, ob die Metrikinstrumentierung funktioniert. Es funktioniert sowohl mit System.Diagnostics.Metrics basierten APIs als auch mit EventCounters.

Wenn das Dotnet-counters-Tool nicht installiert ist, führen Sie den folgenden Befehl aus:

dotnet tool update -g dotnet-counters

Starten Sie während der Ausführung der Beispiel-App dotnet-counters. Der folgende Befehl ist ein Beispiel für die dotnet-counters-Überwachung aller Metriken aus der HatCo.HatStore-Verbrauchseinheit. Bei dem Typnamen wird die Groß- und Kleinschreibung berücksichtigt. Unsere Beispiel-App war metric-instr.exe. Ersetzen Sie diese durch den Namen Ihrer Beispiel-App.

dotnet-counters monitor -n metric-instr HatCo.HatStore

Dadurch werden Informationen angezeigt, die mit denen der folgenden Ausgabe vergleichbar sind:

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

[HatCo.HatStore]
    hats-sold (Count / 1 sec)                          4

dotnet-counters kann auch mit einem anderen Satz von Metriken ausgeführt werden, um einen Teil der integrierten Instrumentierung aus der .NET-Runtime anzuzeigen:

dotnet-counters monitor -n metric-instr

Dadurch werden Informationen angezeigt, die mit denen der folgenden Ausgabe vergleichbar sind:

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

[System.Runtime]
    % Time in GC since last GC (%)                                 0
    Allocation Rate (B / 1 sec)                                8,168
    CPU Usage (%)                                                  0
    Exception Count (Count / 1 sec)                                0
    GC Heap Size (MB)                                              2
    Gen 0 GC Count (Count / 1 sec)                                 0
    Gen 0 Size (B)                                         2,216,256
    Gen 1 GC Count (Count / 1 sec)                                 0
    Gen 1 Size (B)                                           423,392
    Gen 2 GC Count (Count / 1 sec)                                 0
    Gen 2 Size (B)                                           203,248
    LOH Size (B)                                             933,216
    Monitor Lock Contention Count (Count / 1 sec)                  0
    Number of Active Timers                                        1
    Number of Assemblies Loaded                                   39
    ThreadPool Completed Work Item Count (Count / 1 sec)           0
    ThreadPool Queue Length                                        0
    ThreadPool Thread Count                                        3
    Working Set (MB)                                              30

Weitere Informationen finden Sie in unter dotnet-counters. Weitere Informationen zu Metriken in .NET finden Sie unter integrierte Metriken.

Anzeigen von Metriken in Grafana mit OpenTelemetry und Prometheus

Übersicht

OpenTelemetry:

  • Ein anbieterneutrales Open-Source-Projekt, das von der Cloud Native Computing Foundation unterstützt wird.
  • Standardisiert das Generieren und Sammeln von Telemetriedaten für cloudnative Software.
  • Funktioniert mit .NET mithilfe der .NET-Metrik-APIs.
  • Wird von Azure Monitor und vielen APM-Anbietern unterstützt.

Dieses Tutorial zeigt eine der Integrationen, die für OpenTelemetry-Metriken mithilfe der OSS-Projekte Prometheus und Grafana verfügbar sind. Der Metrikdatenfluss:

  1. Die .NET-Metrik-APIs zeichnen Messungen aus der Beispiel-App auf.

  2. Die OpenTelemetry-Bibliothek, die in der App ausgeführt wird, aggregiert die Messungen.

  3. Die Prometheus-Exporterbibliothek stellt die aggregierten Daten über einen HTTP-Metrikendpunkt zur Verfügung. "Exporter" bezeichnet OpenTelemetry die Bibliotheken, die Telemetriedaten an anbieterspezifische Back-Ends übertragen.

  4. Ein Prometheus-Server:

    • Fragt den Metrikendpunkt ab.
    • Liest die Daten.
    • Speichert die Daten für lange Haltbarkeit in einer Datenbank. Prometheus bezeichnet das Lesen und Speichern von Daten als Scraping eines Endpunkts.
    • Kann auf einem anderen Computer ausgeführt werden.
  5. Der Grafana-Server:

    • Fragt die in Prometheus gespeicherten Daten ab und zeigt sie auf einem webbasierten Überwachungsdashboard an.
    • Kann auf einem anderen Computer ausgeführt werden.

Konfigurieren der Beispielanwendung für die Verwendung des Prometheus-Exporters von OpenTelemetry

Fügen Sie der Beispielanwendung einen Verweis auf den OpenTelemetry Prometheus-Exporter hinzu:

dotnet add package OpenTelemetry.Exporter.Prometheus.HttpListener --prerelease

Hinweis

In diesem Tutorial wird ein Vorversionsbuild der Prometheus-Unterstützung von OpenTelemetry verwendet, der zum Zeitpunkt der Niederschrift verfügbar war.

Aktualisieren sie Program.cs mit der OpenTelemetry-Konfiguration:

using OpenTelemetry;
using OpenTelemetry.Metrics;
using System.Diagnostics.Metrics;

class Program
{
    static Meter s_meter = new("HatCo.HatStore", "1.0.0");
    static Counter<int> s_hatsSold = s_meter.CreateCounter<int>(
        name: "hats-sold",
        unit: "Hats",
        description: "The number of hats sold in our store");

    static void Main(string[] args)
    {
        using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder()
                .AddMeter("HatCo.HatStore")
                .AddPrometheusHttpListener(options => options.UriPrefixes = new string[] { "http://localhost:9184/" })
                .Build();

        var rand = Random.Shared;
        Console.WriteLine("Press any key to exit");
        while (!Console.KeyAvailable)
        {
            //// Simulate hat selling transactions.
            Thread.Sleep(rand.Next(100, 2500));
            s_hatsSold.Add(rand.Next(0,1000));
        }
    }
}

Für den Code oben gilt:

  • AddMeter("HatCo.HatStore") konfiguriert OpenTelemetry, um alle Metriken zu übertragen, die von der Verbrauchseinheit gesammelt wurden, die in der App definiert ist.
  • AddPrometheusHttpListener konfiguriert OpenTelemetry für Folgendes:
    • Verfügbarmachen des Prometheus-Metrikendpunkts am Port 9184
    • Verwenden Sie httpListener.

Weitere Informationen zu OpenTelemetry-Konfigurationsoptionen finden Sie in der OpenTelemetry-Dokumentation. Die OpenTelemetry-Dokumentation zeigt Hostingoptionen für ASP.NET Apps.

Führen Sie die App aus, und lassen Sie sie laufen, damit Messungen erfasst werden können:

dotnet run

Einrichten und Konfigurieren von Prometheus

Führen Sie die ersten Schritte von Prometheus aus, um einen Prometheus-Server einzurichten und zu bestätigen, dass er funktioniert.

Ändern Sie die Konfigurationsdatei prometheus.yml so ab, dass Prometheus auf den Metrik-Endpunkt, den die Beispiel-App verfügbar macht, zugreifen kann. Fügen Sie den folgenden Text im scrape_configs-Abschnitt hinzu:

# my global config
global:
  scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
    - static_configs:
        - targets:
          # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: "prometheus"

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
      - targets: ["localhost:9090"]

  - job_name: 'OpenTelemetryTest'
    scrape_interval: 1s # poll very quickly for a more responsive demo
    static_configs:
      - targets: ['localhost:9184']

Starten von Prometheus

  1. Laden Sie die Konfiguration neu, oder starten Sie den Prometheus-Server neu.

  2. Vergewissern Sie sich auf der Seite Status>Ziele des Prometheus-Webportals, dass OpenTelemetryTest den Status UP aufweist. Prometheus status

  3. Geben Sie auf der Seite „Graph“ des Prometheus-Webportals hats in das Ausdruckstextfeld ein, und wählen Sie hats_sold_Hatshat aus. Auf der Registerkarte "Graph" zeigt Prometheus den steigenden Wert des von der Beispiel-App ausgegebenen "Hats-sold"-Zählers an. Prometheus hats sold graph

In der vorherigen Abbildung ist die Graphzeit auf 5 m festgelegt, d. h. auf 5 Minuten.

Wenn der Prometheus-Server auf die Beispiel-App noch nicht lange zugreift, müssen Sie möglicherweise warten, bis genügend Daten gesammelt werden.

Anzeigen von Metriken auf einem Grafana-Dashboard

  1. Befolgen Sie die Standardanweisungen, um Grafana zu installieren und mit einer Prometheus-Datenquelle zu verbinden.

  2. Erstellen Sie ein Grafana-Dashboard, indem Sie auf das Symbol + im Grafana-Webportal auf der linken Symbolleiste klicken und dann Dashboardauswählen. Geben Sie im angezeigten Dashboard-Editor Hats Sold/Sec in das Eingabefeld Titel und rate(hats_sold[5m]) in das Feld „PromQL-Ausdruck“ ein:

    Hats sold Grafana dashboard editor

  3. Klicken Sie auf Übernehmen, um das neue Dashboard zu speichern und anzuzeigen.

    Hats sold Grafana dashboard]

Erstellen eines benutzerdefinierten Sammel-Tools mithilfe der .NET-MeterListener-API.

Die .NET-MeterListener-API ermöglicht Ihnen das Erstellen von benutzerdefinierter In-Prozess-Logik, um die von System.Diagnostics.Metrics.Meter aufgezeichneten Messungen zu beobachten. Anleitungen zum Erstellen benutzerdefinierter Logik, die mit einer älteren EventCounters-Instrumentierung kompatibel ist, finden Sie unter EventCounters.

Ändern Sie den Code von Program.cs, um MeterListener zu verwenden:

using System.Diagnostics.Metrics;

class Program
{
    static Meter s_meter = new("HatCo.HatStore", "1.0.0");
    static Counter<int> s_hatsSold = s_meter.CreateCounter<int>(
        name: "hats-sold",
        unit: "Hats",
        description: "The number of hats sold in our store");

    static void Main(string[] args)
    {
        using MeterListener meterListener = new();
        meterListener.InstrumentPublished = (instrument, listener) =>
        {
            if (instrument.Meter.Name is "HatCo.HatStore")
            {
                listener.EnableMeasurementEvents(instrument);
            }
        };

        meterListener.SetMeasurementEventCallback<int>(OnMeasurementRecorded);
        // Start the meterListener, enabling InstrumentPublished callbacks.
        meterListener.Start();

        var rand = Random.Shared;
        Console.WriteLine("Press any key to exit");
        while (!Console.KeyAvailable)
        {
            //// Simulate hat selling transactions.
            Thread.Sleep(rand.Next(100, 2500));
            s_hatsSold.Add(rand.Next(0, 1000));
        }
    }

    static void OnMeasurementRecorded<T>(
        Instrument instrument,
        T measurement,
        ReadOnlySpan<KeyValuePair<string, object?>> tags,
        object? state)
    {
        Console.WriteLine($"{instrument.Name} recorded measurement {measurement}");
    }
}

Die folgende Ausgabe zeigt die Ausgabe der App mit benutzerdefiniertem Rückruf für jede Messung:

> dotnet run
Press any key to exit
hats-sold recorded measurement 978
hats-sold recorded measurement 775
hats-sold recorded measurement 666
hats-sold recorded measurement 66
hats-sold recorded measurement 914
hats-sold recorded measurement 912
...

Erläutern des Beispielcodes

Die Codeausschnitte in diesem Abschnitt stammen aus dem vorherigen Beispiel.

Im folgenden hervorgehobenen Code wird eine Instanz von MeterListener erstellt, um Messungen zu empfangen. Das Schlüsselwort using bewirkt, dass Dispose aufgerufen wird, wenn der meterListener den Gültigkeitsbereich überschreitet.

using MeterListener meterListener = new();
meterListener.InstrumentPublished = (instrument, listener) =>
{
    if (instrument.Meter.Name is "HatCo.HatStore")
    {
        listener.EnableMeasurementEvents(instrument);
    }
};

Mit dem folgenden hervorgehobenen Code wird konfiguriert, von welchen Instrumenten der Listener Messungen empfängt. InstrumentPublished ist ein Delegat, der aufgerufen wird, wenn ein neues Instrument innerhalb der App erstellt wird.

using MeterListener meterListener = new();
meterListener.InstrumentPublished = (instrument, listener) =>
{
    if (instrument.Meter.Name is "HatCo.HatStore")
    {
        listener.EnableMeasurementEvents(instrument);
    }
};

Der Delegat kann das Instrument untersuchen, um zu entscheiden, ob er abonnieren möchte. Der Delegat kann beispielsweise den Namen, die Verbrauchseinheit oder eine andere öffentliche Eigenschaft überprüfen. EnableMeasurementEvents ermöglicht den Empfang von Messungen vom angegebenen Instrument. Code, der einen Verweis auf ein Instrument durch einen anderen Ansatz abruft:

  • Dies wird nicht unbedingt ausgeführt.
  • Kann EnableMeasurementEvents() jederzeit mit dem Verweis aufrufen.

Als Nächstes wird der Delegat konfiguriert, der aufgerufen wird, wenn Messungen von einem Instrument empfangen werden, indem SetMeasurementEventCallback aufgerufen wird:

    meterListener.SetMeasurementEventCallback<int>(OnMeasurementRecorded);
    // Start the meterListener, enabling InstrumentPublished callbacks.
    meterListener.Start();

    var rand = Random.Shared;
    Console.WriteLine("Press any key to exit");
    while (!Console.KeyAvailable)
    {
        //// Simulate hat selling transactions.
        Thread.Sleep(rand.Next(100, 2500));
        s_hatsSold.Add(rand.Next(0, 1000));
    }
}

static void OnMeasurementRecorded<T>(
    Instrument instrument,
    T measurement,
    ReadOnlySpan<KeyValuePair<string, object?>> tags,
    object? state)
{
    Console.WriteLine($"{instrument.Name} recorded measurement {measurement}");
}

Der generische Parameter steuert, welcher Datentyp der Messung vom Rückruf empfangen wird. Beispielsweise generiert ein Counter<int> die int Messungen, während Counter<double> die double Messungen generiert. Instruments können mit den Typen byte, short, int, long, float, double und decimal erstellt werden. Es wird empfohlen, für jeden Datentyp einen Rückruf zu registrieren, es sei denn, Sie verfügen über fallspezifische Kenntnisse, sodass nicht alle Datentypen benötigt werden. Wiederholte Aufrufe an SetMeasurementEventCallback mit unterschiedlichen generischen Argumenten erscheinen möglicherweise etwas ungewöhnlich. Die API wurde so konzipiert, dass MeterListener Messungen mit geringem Leistungsaufwand empfangen kann, in der Regel in nur wenigen Nanosekunden.

Wenn MeterListener.EnableMeasurementEvents aufgerufen wird, kann ein state-Objekt als einer der Parameter bereitgestellt werden. Das state-Objekt ist beliebig. Wenn Sie in diesem Aufruf ein Zustandsobjekt angeben, wird es mit diesem Instrument gespeichert und als Parameter state im Rückruf an Sie zurückgegeben. Dies dient sowohl der Einfachheit als auch der Leistungsoptimierung. Häufig müssen Listener:

  • ein Objekt für jedes Instrument erstellen, das Messungen im Arbeitsspeicher speichert.
  • über Code verfügen, um Berechnungen für diese Messungen durchzuführen.

Erstellen Sie alternativ ein Dictionary, das vom Instrument zum Speicherobjekt zugeordnet wird, und sehen Sie darin bei jeder Messung nach. Die Verwendung eines Dictionary ist viel langsamer als der Zugriff über state.

meterListener.Start();

Mit dem vorherigen Code wird der MeterListener gestartet, wodurch Rückrufe aktiviert werden. Der Delegat InstrumentPublished wird für jedes bereits vorhandene Instrument im Prozess aufgerufen. Neu erstellte Instrument-Objekte führen ebenfalls dazu, dass InstrumentPublished aufgerufen wird.

using MeterListener meterListener = new MeterListener();

Wenn die App mit dem Lauschen fertig ist, beendet das Verwerfen des Listeners den Ablauf von Rückrufen und gibt alle internen Verweise auf das Listenerobjekt frei. Das Schlüsselwort using, das beim Deklarieren von meterListener verwendet wurde, bewirkt, dass Dispose automatisch aufgerufen wird, wenn die Variable den Gültigkeitsbereich überschreitet. Beachten Sie, dass Dispose nur verspricht, dass keine neuen Rückrufe initiiert werden. Da Rückrufe in verschiedenen Threads auftreten, werden möglicherweise noch Rückrufe ausgeführt, nachdem der Aufruf an Dispose zurückgegeben wurde.

Um sicherzustellen, dass ein bestimmter Codebereich im Rückruf derzeit und in Zukunft nicht ausgeführt wird, muss die Threadsynchronisierung hinzugefügt werden. Dispose schließt die Synchronisierung standardmäßig nicht ein, denn:

  • Die Synchronisierung erhöht den Leistungsaufwand bei jedem Messrückruf.
  • MeterListener ist als hocheffiziente API konzipiert.