Raccogliere le metriche

Questo articolo si applica a: ✔️ .NET Core 3.1 e versioni successive ✔️ .NET Framework 4.6.1 e versioni successive

Il codice instrumentato può registrare misurazioni numeriche, ma le misurazioni in genere devono essere aggregate, trasmesse e archiviate per creare metriche utili per il monitoraggio. Il processo di aggregazione, trasmissione e archiviazione dei dati viene chiamato raccolta. Questa esercitazione illustra diversi esempi di raccolta delle metriche:

Per ulteriori informazioni sulla strumentazione e le opzioni delle metriche personalizzate, consultare Confrontare le API delle metriche.

Prerequisiti

Creare un'app di esempio

Prima di poter raccogliere le metriche, è necessario produrre le misurazioni. Questa esercitazione crea un'app con strumentazione metrica di base. Il runtime .NET include anche diverse metriche predefinite. Per ulteriori informazioni sulla creazione di nuove metriche con l'API System.Diagnostics.Metrics.Meter, consultare l'esercitazione sulla strumentazione.

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

Sostituire il contenuto di Program.cs con il codice seguente:

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

Il codice precedente simula la vendita di cappelli a intervalli casuali e orari casuali.

Visualizzare le metriche con dotnet-counters

dotnet-counters è uno strumento da riga di comando in grado di visualizzare le metriche in tempo reale per le app .NET Core su richiesta. Non richiede la configurazione, rendendola utile per le ricerche ad hoc o per verificare che la strumentazione delle metriche funzioni. Funziona sia con le API basate su System.Diagnostics.Metrics che EventCounters.

Se lo strumento dotnet-counters non è installato, eseguire il comando seguente:

dotnet tool update -g dotnet-counters

Mentre l'app di esempio è in esecuzione, avviare dotnet-counters. Il comando seguente mostra un esempio di dotnet-counters il monitoraggio di tutte le metriche dal contatore HatCo.HatStore. Il nome del contatore fa distinzione tra maiuscole e minuscole. L'app di esempio è stata metric-instr.exe, sostituirla con il nome dell'app di esempio.

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

Viene visualizzato output simile al seguente:

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

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

dotnet-counters può essere eseguito con un set diverso di metriche per visualizzare alcune delle strumentazioni predefinite del runtime .NET:

dotnet-counters monitor -n metric-instr

Viene visualizzato output simile al seguente:

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

Per ulteriori informazioni, consultare dotnet-counters. Per ulteriori informazioni sulle metriche in .NET, consultare metriche predefinite.

Visualizzare le metriche in Grafana con OpenTelemetry e Prometheus

Panoramica

OpenTelemetry:

  • Progetto open source indipendente dal fornitore supportato da Cloud Native Computing Foundation.
  • Standardizza la generazione e la raccolta dei dati di telemetria per il software nativo del cloud.
  • Funziona con .NET usando le API delle metriche .NET.
  • È approvato da Monitoraggio di Azure e da molti fornitori di APM.

Questa esercitazione illustra una delle integrazioni disponibili per le metriche OpenTelemetry usando i progetti OSS Prometheus e Grafana. Flusso di dati delle metriche:

  1. Le API delle metriche .NET registrano le misurazioni dall'app di esempio.

  2. La libreria OpenTelemetry in esecuzione nell'app aggrega le misurazioni.

  3. La libreria di esportazione Prometheus rende disponibili i dati aggregati tramite un endpoint delle metriche HTTP. 'Exporter' è ciò che OpenTelemetry chiama le librerie che trasmettono dati di telemetria a back-end specifici del fornitore.

  4. Un server Prometheus:

    • Esegue il polling dell'endpoint delle metriche
    • Legge i dati
    • Archivia i dati in un database per la persistenza a lungo termine. Prometheus si riferisce alla lettura e all'archiviazione dei dati come scraping di un endpoint.
    • Può essere eseguito in un computer diverso
  5. Server Grafana:

    • Esegue una query sui dati archiviati in Prometheus e li visualizza in una dashboard di monitoraggio basata sul Web.
    • Può essere eseguito in un computer diverso.

Configurare l'app di esempio per l'uso dell'utilità di esportazione Prometheus di OpenTelemetry

Aggiungere un riferimento all'utilità di esportazione Prometheus OpenTelemetry all'app di esempio:

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

Nota

Questa esercitazione usa una build non definitiva del supporto Prometheus di OpenTelemetry disponibile al momento della scrittura.

Aggiornare Program.cs con la configurazione di OpenTelemetry:

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

Nel codice precedente:

  • AddMeter("HatCo.HatStore") configura OpenTelemetry per trasmettere tutte le metriche raccolte dal contatore definito nell'app.
  • AddPrometheusHttpListener configura OpenTelemetry per:
    • Esporre l'endpoint delle metriche di Prometheus sulla porta 9184
    • Usare HttpListener.

Per ulteriori informazioni sulle opzioni di configurazione di OpenTelemetry, consultare la documentazione di OpenTelemetry. La documentazione di OpenTelemetry mostra le opzioni di hosting per le app ASP.NET.

Eseguire l'app e lasciarla in esecuzione in modo che le misurazioni possano essere raccolte:

dotnet run

Impostare e configurare Prometheus

Seguire i primi passaggi di Prometheus per configurare un server Prometheus e verificare che funzioni.

Modificare il file di configurazione prometheus.yml in modo che Prometheus esemplifichi l'endpoint delle metriche esposto dall'app di esempio. Aggiungere il testo evidenziato seguente nella sezione scrape_configs:

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

Avviare Prometheus

  1. Ricaricare la configurazione o riavviare il server Prometheus.

  2. Verificare che OpenTelemetryTest sia nello stato UP nella pagina Stato> Destinazioni del portale Web Prometheus. Prometheus status

  3. Nella pagina Grafico del portale Web Prometheus immettere hats nella casella di testo dell'espressione e selezionare hats_sold_Hatshat Nella scheda del grafo Prometheus viene visualizzato il valore crescente del contatore "hats-sold" generato dall'app di esempio. Prometheus hats sold graph

Nell'immagine precedente, l'ora del grafico è impostata su 5m, ovvero 5 minuti.

Se il server Prometheus non ha eliminato l'app di esempio per molto tempo, potrebbe essere necessario attendere l'accumulo dei dati.

Visualizzare le metriche in una dashboard di Grafana

  1. Seguire le istruzioni standard per installare Grafana e connetterlo a un'origine dati Prometheus.

  2. Creare una dashboard di Grafana facendo clic sull'icona + sulla barra degli strumenti a sinistra nel portale Web di Grafana, quindi selezionare Dashboard. Nell'editor della dashboard visualizzata immettere Hats Sold/Sec nella casella di input Titolo e rate(hats_sold[5m]) nel campo dell'espressione PromQL:

    Hats sold Grafana dashboard editor

  3. Fare clic su Applica per salvare e visualizzare la nuova dashboard.

    Hats sold Grafana dashboard]

Creare uno strumento di raccolta personalizzato usando l'API MeterListener .NET

L'API .NET MeterListener consente di creare logica in-process personalizzata per osservare le misurazioni registrate da System.Diagnostics.Metrics.Meter. Per indicazioni sulla creazione di una logica personalizzata compatibile con la strumentazione EventCounters precedente, consultare EventCounters.

Modificare il codice di Program.cs per usare 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}");
    }
}

L'output seguente mostra l'output dell'app con callback personalizzato per ogni misura:

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

Spiegazione del codice di esempio

I frammenti di codice in questa sezione provengono dall'esempio precedente.

Nel codice evidenziato seguente viene creata un'istanza MeterListener di per ricevere le misurazioni. La parola chiave using fa sì che Dispose venga chiamato quando il meterListener esce dall'ambito.

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

Il codice evidenziato seguente consente di configurare gli strumenti da cui il listener riceve le misurazioni. InstrumentPublished è un delegato richiamato quando viene creato un nuovo strumento all'interno dell'app.

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

Il delegato può esaminare lo strumento per decidere se sottoscrivere. Ad esempio, il delegato può controllare il nome, il contatore o qualsiasi altra proprietà pubblica. EnableMeasurementEvents consente di ricevere misurazioni dallo strumento specificato. Codice che ottiene un riferimento a uno strumento da un altro approccio:

  • in genere non viene eseguito.
  • Può richiamare EnableMeasurementEvents() in qualsiasi momento con il riferimento.

Il delegato richiamato quando le misurazioni vengono ricevute da uno strumento viene configurato chiamando SetMeasurementEventCallback:

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

Il parametro generico controlla il tipo di dati di misura ricevuto dal callback. Ad esempio, un Counter<int> genera misurazioni int, Counter<double> genera misurazioni double. Gli strumenti possono essere creati con tipi byte, short, int, long, float, double e decimal. È consigliabile registrare un callback per ogni tipo di dati, a meno che non siano necessarie conoscenze specifiche dello scenario che non tutti i tipi di dati. L'esecuzione di chiamate ripetute a SetMeasurementEventCallback con argomenti generici diversi può sembrare un po' insolito. L'API è stata progettata in questo modo per consentire a un MeterListener di ricevere misurazioni con un sovraccarico ridotto delle prestazioni, in genere solo pochi nanosecondi.

Quando viene chiamato MeterListener.EnableMeasurementEvents, è possibile specificare un oggetto state come uno dei parametri. L'oggetto state è arbitrario. Se si specifica un oggetto di stato in tale chiamata, viene archiviato con tale strumento e restituito all'utente come parametro state nel callback. Questo è progettato sia come praticità che come ottimizzazione delle prestazioni. Spesso i listener devono:

  • Creare un oggetto per ogni strumento che archivia le misurazioni in memoria.
  • Disporre del codice per eseguire calcoli su tali misurazioni.

In alternativa, creare un oggetto Dictionary mappato dallo strumento all'oggetto di archiviazione e cercarlo in ogni misura. L'uso di un Dictionary è molto più lento rispetto all'accesso da state.

meterListener.Start();

Il codice precedente avvia l'oggetto MeterListener che abilita i callback. Il delegato InstrumentPublished viene richiamato per ogni strumento preesistente nel processo. Gli oggetti Instrument appena creati attivano anche InstrumentPublished da richiamare.

using MeterListener meterListener = new MeterListener();

Quando l'app viene eseguita in ascolto, l'eliminazione del listener arresta il flusso dei callback e rilascia tutti i riferimenti interni all'oggetto listener. La parola chiave using usata per dichiarare meterListener determina la chiamata di Dispose quando la variabile esce dall'ambito. Si noti che Dispose promette solo che non avvierà nuovi callback. Poiché i callback si verificano in thread diversi, potrebbero essere ancora in corso callback dopo che la chiamata a Dispose restituisce.

Per garantire che una determinata area di codice nel callback non sia attualmente in esecuzione e non venga eseguita in futuro, è necessario aggiungere la sincronizzazione dei thread. Dispose non include la sincronizzazione per impostazione predefinita perché:

  • La sincronizzazione comporta un sovraccarico delle prestazioni in ogni callback di misurazione.
  • MeterListener è progettato come API con prestazioni elevate.