Delen via


Metrische gegevens verzamelen

Dit artikel is van toepassing op: ✔️ .NET 6.0 en hoger ✔️ .NET Framework 4.6.1 en hoger

Met instrumenteerde code kunnen numerieke metingen worden vastgelegd, maar de metingen moeten meestal worden geaggregeerd, verzonden en opgeslagen om nuttige metrische gegevens te maken voor bewaking. Het proces voor het samenvoegen, verzenden en opslaan van gegevens wordt verzameling genoemd. In deze zelfstudie ziet u verschillende voorbeelden van het verzamelen van metrische gegevens:

Zie Metrische API's vergelijken voor meer informatie over aangepaste metrische instrumentatie en opties.

Vereiste voorwaarden

Een voorbeeld-app maken

Voordat metrische gegevens kunnen worden verzameld, moeten metingen worden geproduceerd. In deze zelfstudie maakt u een app met eenvoudige metrische instrumentatie. De . NET-runtime heeft ook verschillende ingebouwde metrische gegevens. Zie System.Diagnostics.Metrics.Meter voor meer informatie over het maken van nieuwe metrische gegevens met behulp van de API.

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

Vervang de inhoud van Program.cs met de volgende 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));
        }
    }
}

De voorgaande code simuleert het verkopen van hoeden met willekeurige intervallen en willekeurige tijden.

Metingen weergeven met dotnet-counters

dotnet-counters is een opdrachtregelprogramma waarmee live metrische gegevens voor .NET Core-apps op aanvraag kunnen worden weergegeven. Hiervoor is geen installatie vereist, waardoor het nuttig is voor ad-hoconderzoeken of om te controleren of metrische instrumentatie werkt. Het werkt met zowel System.Diagnostics.Metrics op gebaseerde API's als EventCounters.

Als het hulpprogramma dotnet-counters niet is geïnstalleerd, voert u de volgende opdracht uit:

dotnet tool update -g dotnet-counters

Als in uw app een versie van .NET wordt uitgevoerd die ouder is dan .NET 9, ziet de uitvoerinterface van dotnet-tellers er iets anders uit dan hieronder; zie dotnet-tellers voor meer informatie.

Terwijl de voorbeeld-app draait, start u dotnet-counters. De volgende opdracht toont een voorbeeld van het bewaken van dotnet-counters alle metrische gegevens van de HatCo.HatStore meter. De naam van de meter is hoofdlettergevoelig. Onze voorbeeld-app is metric-instr.exe. Vervang deze door de naam van uw voorbeeld-app.

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

Uitvoer die lijkt op het volgende wordt weergegeven:

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

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

dotnet-counters kan worden uitgevoerd met een andere set metrische gegevens om enkele van de ingebouwde instrumentatie van de .NET-runtime te bekijken:

dotnet-counters monitor -n metric-instr

Uitvoer die lijkt op het volgende wordt weergegeven:

System.Runtime
  Press p to pause, r to resume, q to quit.
      Status: Running
  Name                                              Current Value
  [System.Runtime]
  dotnet.assembly.count ({assembly})                    11
  dotnet.gc.collections ({collection})
    gc.heap.generation
    ------------------
      gen0                                              0
      gen1                                              0
      gen2                                              0
  dotnet.gc.heap.total_allocated (By)                   1,376,024
  dotnet.gc.last_collection.heap.fragmentation.size (By)
    gc.heap.generation
    ------------------
      gen0                                              0
      gen1                                              0
      gen2                                              0
      loh                                               0
      poh                                               0
  dotnet.gc.last_collection.heap.size (By)
    gc.heap.generation
    ------------------
      gen0                                              0
      gen1                                              0
      gen2                                              0
      loh                                               0
      poh                                               0
  dotnet.gc.last_collection.memory.committed_size (By)   0
  dotnet.gc.pause.time (s)                              0
  dotnet.jit.compilation.time (s)                       0.253
  dotnet.jit.compiled_il.size (By)                      79,536
  dotnet.jit.compiled_methods ({method})                743
  dotnet.monitor.lock_contentions ({contention})        0
  dotnet.process.cpu.count ({cpu})                      22
  dotnet.process.cpu.time (s)
    cpu.mode
    --------
      system                                            0.125
      user                                              46.453
  dotnet.process.memory.working_set (By)                34,447,360
  dotnet.thread_pool.queue.length ({work_item})         0
  dotnet.thread_pool.thread.count ({thread})            0
  dotnet.thread_pool.work_item.count ({work_item})      0
  dotnet.timer.count ({timer})                          0

Zie dotnet-counters voor meer informatie. Zie ingebouwde metrische gegevens voor meer informatie over metrische gegevens in .NET.

Metrische gegevens weergeven in Grafana met OpenTelemetry en Prometheus

Overzicht

OpenTelemetry:

  • Is een leverancierneutraal opensource-project dat wordt ondersteund door de Cloud Native Computing Foundation.
  • Standaardiseert het genereren en verzamelen van telemetrie voor cloudeigen software.
  • Werkt met .NET met behulp van de metrische .NET-API's.
  • Wordt goedgekeurd door Azure Monitor en veel APM-leveranciers.

In deze zelfstudie ziet u een van de integraties die beschikbaar zijn voor metrische gegevens van OpenTelemetry met behulp van de OSS Prometheus - en Grafana-projecten . De metrische gegevensstroom:

  1. De metrische .NET-API's registreren metingen uit de voorbeeld-app.

  2. De OpenTelemetry-bibliotheek die in de app draait, voegt de metingen samen.

  3. De Prometheus-exportbibliotheek maakt de geaggregeerde gegevens beschikbaar via een eindpunt voor metrische HTTP-gegevens. 'Exporteur' is wat OpenTelemetry de bibliotheken aanroept die telemetrie verzenden naar leverancierspecifieke back-ends.

  4. Een Prometheus-server:

    • Peilt het eindpunt van metrieken
    • De gegevens lezen
    • Slaat de gegevens op in een database voor persistentie op lange termijn. Prometheus verwijst naar het lezen en opslaan van gegevens als het scrapen van een eindpunt.
    • Kan worden uitgevoerd op een andere computer
  5. De Grafana-server:

    • Query's uitvoeren op de gegevens die zijn opgeslagen in Prometheus en deze weergeven op een bewakingsdashboard op internet.
    • Kan worden uitgevoerd op een andere computer.

De voorbeeld-app configureren voor het gebruik van de Prometheus-exporteur van OpenTelemetry

Voeg een verwijzing naar de OpenTelemetry Prometheus-exporteur toe aan de voorbeeld-app:

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

Opmerking

In deze zelfstudie wordt gebruikgemaakt van een voorlopige build van de Prometheus-ondersteuning van OpenTelemetry die beschikbaar is op het moment van schrijven.

Bijwerken Program.cs met OpenTelemetry-configuratie:

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

In de voorgaande code:

  • AddMeter("HatCo.HatStore") configureert OpenTelemetry om alle metrische gegevens te verzenden die zijn verzameld door de meter die in de app is gedefinieerd.
  • AddPrometheusHttpListener configureert OpenTelemetry voor:
    • Eindpunt voor metrische gegevens van Prometheus beschikbaar maken op poort 9184
    • Gebruik httplistener.

Zie de documentatie over OpenTelemetry voor meer informatie over configuratieopties voor OpenTelemetry. De OpenTelemetry-documentatie bevat hostingopties voor ASP.NET-apps.

Voer de app uit en laat deze actief zodat metingen kunnen worden verzameld:

dotnet run

Prometheus instellen en configureren

Volg de eerste stappen van Prometheus om een Prometheus-server in te stellen en te bevestigen dat deze werkt.

Wijzig het prometheus.yml configuratiebestand zodat Prometheus het eindpunt voor metrische gegevens verwijdert dat de voorbeeld-app weergeeft. Voeg de volgende gemarkeerde tekst toe in de scrape_configs sectie:

# 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']

Prometheus starten

  1. Laad de configuratie opnieuw of start de Prometheus-server opnieuw op.

  2. Controleer of OpenTelemetryTest de UP-status heeft op de pagina Statusdoelen> van de Prometheus-webportal. Prometheus-status

  3. Op de grafiekpagina van de Prometheus-webportal voer je hats in het tekstvak voor expressie in en selecteer je hats_sold_Hatshat op het tabblad Grafiek. Prometheus toont de toenemende waarde van de teller 'verkochte-hoeden' die door de voorbeeldapp wordt verzonden. Grafiek van verkochte Prometheus hoeden

In de voorgaande afbeelding wordt de grafiektijd ingesteld op 5 min. Dit is 5 minuten.

Als de Prometheus-server nog niet lang bezig is met het ophalen van de voorbeeldapp, moet u mogelijk wachten totdat gegevens zijn verzameld.

Metrische gegevens weergeven op een Grafana-dashboard

  1. Volg de standaardinstructies om Grafana te installeren en deze te verbinden met een Prometheus-gegevensbron.

  2. Maak een Grafana-dashboard door op het + pictogram op de linkerwerkbalk in de Grafana-webportal te klikken en vervolgens Dashboard te selecteren. Voer in de dashboardeditor die wordt weergegeven Hats Sold/Sec in het invoervak Titel en rate (hats_sold[5m]) in het veld PromQL-expressie in:

    Hats verkocht Grafana dashboard editor

  3. Klik op Toepassen om het nieuwe dashboard op te slaan en weer te geven.

    Hattenverkoop Grafana-dashboard ]

Een aangepast verzamelingsprogramma maken met behulp van de .NET MeterListener-API

Met de .NET-API MeterListener kunt u aangepaste in-processlogica maken om de metingen te observeren die worden vastgelegd door System.Diagnostics.Metrics.Meter. Zie EventCounters voor hulp bij het maken van aangepaste logica die compatibel is met de oudere EventCounters-instrumentatie.

Wijzig de code voor Program.cs gebruik MeterListener:

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

De volgende uitvoer toont de resultaten van de app met een aangepaste callback voor elke meting:

> 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
...

Uitleg van voorbeeldcode

De codefragmenten in deze sectie zijn afkomstig uit het voorgaande voorbeeld.

In de volgende gemarkeerde code wordt een exemplaar van de MeterListener code gemaakt om metingen te ontvangen. Het using trefwoord zorgt ervoor dat Dispose wordt aangeroepen wanneer het meterListener buiten bereik raakt.

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

Met de volgende gemarkeerde code wordt geconfigureerd van welke instrumenten de listener metingen ontvangt. InstrumentPublished is een gemachtigde die wordt aangeroepen wanneer een nieuw instrument in de app wordt gemaakt.

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

De gedelegeerde kan het instrument onderzoeken om te bepalen of hij zich moet abonneren. De gedelegeerde kan bijvoorbeeld de naam, de meter of een andere openbare eigenschap controleren. EnableMeasurementEvents maakt het ontvangen van metingen van het opgegeven instrument mogelijk. Code waarmee een verwijzing naar een instrument wordt verkregen door een andere benadering:

  • Dit gebeurt meestal niet.
  • Kan EnableMeasurementEvents() op elk gewenst moment aanroepen met de verwijzing.

De gemachtigde die wordt aangeroepen wanneer metingen van een instrument worden ontvangen, wordt geconfigureerd door het aanroepen SetMeasurementEventCallbackvan:

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

De algemene parameter bepaalt welk gegevenstype meting wordt ontvangen door de callback. Bijvoorbeeld, een Counter<int> genereert int metingen, Counter<double> genereert double metingen. Instrumenten kunnen worden gemaakt met byte, , short, int, long, , floaten doubledecimaltypen. We raden u aan een callback te registreren voor elk gegevenstype, tenzij u scenariospecifieke kennis hebt dat niet alle gegevenstypen nodig zijn. Het maken van herhaalde aanroepen SetMeasurementEventCallback met verschillende algemene argumenten kan een beetje ongebruikelijk lijken. De API is op deze manier ontworpen om een MeterListener metingen te laten ontvangen met weinig prestatienadelen, meestal slechts een paar nanoseconden.

Wanneer MeterListener.EnableMeasurementEvents wordt aangeroepen, kan een state object worden opgegeven als een van de parameters. Het state object is willekeurig. Als u een statusobject in die aanroep opgeeft, wordt het met dat instrument opgeslagen en naar u geretourneerd als de state parameter in de callback. Dit is zowel bedoeld voor gebruiksgemak als voor optimalisatie van de prestaties. Vaak moeten listeners het volgende doen:

  • Maak een object voor elk instrument dat metingen in het geheugen opslaat.
  • Code hebben om berekeningen uit te voeren voor deze metingen.

U kunt ook een Dictionary maken dat het instrument aan het opslagobject koppelt en het bij elke meting opzoekt. Het gebruik van een Dictionary is veel langzamer dan toegang krijgen tot state.

meterListener.Start();

De voorgaande code start de MeterListener, waarmee callbacks worden ingeschakeld. De InstrumentPublished gemachtigde wordt aangeroepen voor elk bestaand instrument in het proces. Nieuw gecreëerde Instrument-objecten zorgen er ook voor dat InstrumentPublished wordt aangeroepen.

using MeterListener meterListener = new MeterListener();

Wanneer de app klaar is met luisteren, stopt het verwijderen van de listener de stroom van callbacks en worden eventuele interne verwijzingen naar het listenerobject vrijgegeven. Het trefwoord using dat wordt gebruikt bij het declareren van meterListener, zorgt ervoor dat Dispose wordt aangeroepen wanneer de variabele buiten scope raakt. Houd er rekening mee dat het Dispose alleen belooft dat er geen nieuwe callbacks worden gestart. Omdat callbacks plaatsvinden op verschillende threads, kunnen er nog steeds callbacks actief zijn nadat de aanroep naar Dispose is geretourneerd.

Om ervoor te zorgen dat een bepaalde coderegio in de callback momenteel niet wordt uitgevoerd en in de toekomst niet wordt uitgevoerd, moet threadsynchronisatie worden toegevoegd. Dispose bevat standaard synchronisatie niet omdat:

  • Synchronisatie voegt prestatieoverhead toe aan elke callback voor metingen.
  • MeterListener is ontworpen als een zeer prestatiebewuste API.