Partager via


Collecter des métriques

Cet article s’applique à : ✔️ .NET Core 3.1 et versions ultérieures✔️ .NET Framework 4.6.1 et versions ultérieures

Le code instrumenté peut enregistrer des mesures numériques, mais les mesures doivent généralement être agrégées, transmises et stockées pour créer des métriques utiles pour la surveillance. Le processus d’agrégation, de transmission et de stockage des données est appelé collecte. Ce tutoriel présente plusieurs exemples de collecte de métriques :

Pour plus d’informations sur l’instrumentation et les options de métrique personnalisées, consultez Comparer les API des métriques.

Prérequis

Créer un exemple d’application

Pour que les métriques puissent être collectées, des mesures doivent être produites. Ce tutoriel crée une application qui dispose d’une instrumentation de métrique de base. Le runtime .NET dispose également de différentes métriques intégrées. Pour plus d’informations sur la création de métriques à l’aide de l’API System.Diagnostics.Metrics.Meter, consultez le tutoriel sur l’instrumentation.

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

Remplacez le contenu de Program.cs par le code suivant :

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

Le code précédent simule la vente de chapeaux à intervalles aléatoires et à des moments aléatoires.

Afficher les métriques avec dotnet-counters

dotnet-counters est un outil en ligne de commande qui peut afficher les métriques actives pour les applications .NET Core à la demande. Il ne nécessite pas d’installation, ce qui le rend utile pour les enquêtes ad hoc ou pour vérifier que l’instrumentation des métriques fonctionne. Il fonctionne avec les API basées sur System.Diagnostics.Metrics et EventCounters.

Si l’outil dotnet-counters n’est pas installé, exécutez la commande suivante :

dotnet tool update -g dotnet-counters

Pendant l’exécution de l’exemple d’application, lancez dotnet-counters. La commande suivante montre un exemple de dotnet-counters surveillant toutes les métriques du compteur HatCo.HatStore. Le nom du compteur respecte la casse. Notre exemple d’application était metric-instr.exe, remplacez-le par le nom de votre exemple d’application.

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

Une sortie similaire à la suivante s’affiche à l’écran :

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

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

Nous pouvons également exécuter dotnet-counters avec un autre ensemble de métriques pour voir une partie de l’instrumentation intégrée à partir du runtime .NET :

dotnet-counters monitor -n metric-instr

Une sortie similaire à la suivante s’affiche à l’écran :

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

Pour plus d’informations, consultez compteurs dotnet. Pour en savoir plus sur les métriques dans .NET, consultez Métriques intégrées.

Afficher les métriques dans Grafana avec OpenTelemetry et Prometheus

Vue d'ensemble

OpenTelemetry :

  • Il s’agit d’un projet open source indépendant du fournisseur soutenu par la Cloud Native Computing Foundation.
  • Standardise la génération et la collecte des données de télémétrie pour les logiciels natifs cloud.
  • Fonctionne avec .NET à l’aide des API de métrique .NET.
  • Est approuvé par Azure Monitor et de nombreux fournisseurs APM.

Ce tutoriel montre l’une des intégrations disponibles pour les métriques OpenTelemetry à l’aide des projets OSS Prometheus et Grafana. Flux de données des métriques :

  1. Les API de métrique .NET enregistrent les mesures de l’exemple d’application.

  2. La bibliothèque OpenTelemetry en cours d’exécution dans l’application agrège les mesures.

  3. La bibliothèque d’exportation (« Exporter ») Prometheus rend les données agrégées disponibles via un point de terminaison de métriques HTTP. « Exporter » est le nom donné par OpenTelemetry aux bibliothèques qui transmettent les données de télémétrie aux back-ends spécifiques au fournisseur.

  4. Un serveur Prometheus :

    • Interroge le point de terminaison des métriques
    • Lit les données
    • Stocke les données dans une base de données pour une persistance à long terme. Prometheus fait référence au processus de lecture et de stockage de données sous le nom de scraping de point de terminaison.
    • Peut s’exécuter sur une autre machine
  5. Le serveur Grafana :

    • Interroge les données stockées dans Prometheus et les affiche sur un tableau de bord de supervision web.
    • Peut s’exécuter sur une autre machine.

Configurer l’exemple d’application pour utiliser la bibliothèque d’exportation Prometheus d’OpenTelemetry

Ajoutez une référence à la bibliothèque d’exportation Prometheus d’OpenTelemetry à l’exemple d’application :

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

Notes

Ce tutoriel utilise une version préliminaire de la prise en charge Prometheus d’OpenTelemetry disponible au moment de l’écriture.

Mettez à jour Program.cs avec la configuration 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));
        }
    }
}

Dans le code précédent :

  • AddMeter("HatCo.HatStore") configure OpenTelemetry pour transmettre toutes les métriques collectées par le compteur défini dans notre application.
  • AddPrometheusHttpListener configure OpenTelemetry pour :
    • Exposer le point de terminaison de métriques de Prometheus sur le port 9184
    • Utiliser HttpListener.

Pour plus d’informations sur les options de configuration d’OpenTelemetry, consultez la documentation OpenTelemetry. La documentation OpenTelemetry présente les options d’hébergement pour les applications ASP.NET.

Exécutez l’application et laissez-la en cours d’exécution afin que les mesures puissent être collectées :

dotnet run

Installer et configurer Prometheus

Suivez les Premières étapes de Prometheus pour configurer un serveur Prometheus et vérifier qu’il fonctionne.

Modifiez le fichier de configuration prometheus.yml afin que Prometheus scrape le point de terminaison des métriques que l’exemple d’application expose. Ajoutez le texte en surbrillance suivant dans la section 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']

Démarrer Prometheus

  1. Rechargez la configuration ou redémarrez le serveur Prometheus.

  2. Vérifiez qu’OpenTelemetryTest est dans l’état UP sur la page État>Cibles du portail web Prometheus. Prometheus status

  3. Dans la page Graphique du portail web Prometheus, entrez hats dans la zone de texte de l’expression et sélectionnez hats_sold_Hatshat. Dans l’onglet Graphique, Prometheus affiche la valeur croissante du compteur « hats-sold » émis par l’exemple d’application. Prometheus hats sold graph

Dans l’image précédente, le temps du graphique est défini sur 5m, soit 5 minutes.

Si le serveur Prometheus n’a effectué de scraping de l’exemple d’application depuis longtemps, vous devrez peut-être attendre un moment pour que les données s’accumulent.

Afficher les métriques sur un tableau de bord Grafana

  1. Suivez les instructions standard pour installer Grafana et la connecter à une source de données Prometheus.

  2. Créez un tableau de bord Grafana en cliquant sur l’icône + dans la barre d’outils de gauche dans le portail web Grafana, puis sélectionnez Tableau de bord. Dans l’éditeur de tableau de bord qui s’affiche, entrez Hats Sold/Sec comme Titre et rate(hats_sold[5m]) dans le champ d’expression PromQL :

    Hats sold Grafana dashboard editor

  3. Cliquez sur Appliquer pour enregistrer et afficher le nouveau tableau de bord.

    Hats sold Grafana dashboard]

Créez un outil de collecte personnalisé à l’aide de l’API .NET MeterListener

L’API .NET MeterListener vous permet de créer une logique personnalisée dans le processus pour observer les mesures enregistrées par System.Diagnostics.Metrics.Meter. Pour obtenir des conseils sur la création d’une logique personnalisée compatible avec l’instrumentation EventCounters plus ancienne, consultez EventCounters.

Modifiez le code de Program.cs pour utiliser 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}");
    }
}

La sortie suivante montre la sortie de l’application avec un rappel personnalisé sur chaque mesure :

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

Explication de l’exemple de code

Les extraits de code de cette section proviennent de l’exemple précédent.

Dans le code en surbrillance suivant, une instance de MeterListener est créée pour recevoir des mesures. Le mot clé using entraîne l’appel de Dispose lorsque meterListener est hors de portée.

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

Le code mis en évidence suivant configure les instruments à partir desquels l’écouteur reçoit des mesures. InstrumentPublished est un délégué appelé quand un nouvel instrument est créé dans l’application.

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

Le délégué peut examiner l’instrument pour décider s’il doit s’abonner. Par exemple, le délégué peut vérifier le nom, le compteur ou toute autre propriété publique. EnableMeasurementEvents permet de recevoir des mesures à partir de l’instrument spécifié. Code qui obtient une référence à un instrument par une autre approche :

  • Il ne s’agit pas de la procédure courante.
  • Peut appeler EnableMeasurementEvents() à tout moment avec la référence.

Nous configurons le délégué invoqué lorsque les mesures sont reçues d’un instrument en appelant 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}");
}

Le paramètre générique contrôle le type de données de mesure qui sera reçu par le rappel. Par exemple, un Counter<int> génère des mesures int, et un Counter<double> génère des mesures double. Les instruments peuvent être créés avec les types byte, short, int, long, float, double et decimal. Nous vous recommandons d’inscrire un rappel pour chaque type de données, sauf si vous avez des connaissances spécifiques au scénario que tous les types de données ne seront pas nécessaires. L’exécution d’appels répétés à SetMeasurementEventCallback avec différents arguments génériques peut sembler un peu inhabituel. L’API est conçue de cette façon pour permettre à MeterListener de recevoir des mesures avec une surcharge de performances faible, généralement seulement quelques nanosecondes.

Quand MeterListener.EnableMeasurementEvents est appelé, un objet state peut être fourni comme l’un des paramètres. L’objet state est arbitraire. Si vous fournissez un objet d’état dans cet appel, il sera stocké avec cet instrument et vous sera retourné en tant que paramètre state dans le rappel. Cela est prévu à la fois comme une commodité et une optimisation des performances. Souvent, les écouteurs doivent :

  • Créer un objet pour chaque instrument qui stocke des mesures en mémoire.
  • Avoir du code pour effectuer des calculs sur ces mesures.

Vous pouvez également créer un Dictionary qui mappe l’instrument sur l’objet de stockage et le recherche lors de chaque mesure. L’utilisation de Dictionary est beaucoup plus lente que l’accès à partir de state.

meterListener.Start();

Le code précédent démarre le MeterListener qui active les rappels. Le délégué InstrumentPublished sera invoqué pour chaque instrument préexistant dans le processus. Les objets Instrument nouvellement créés déclenchent également InstrumentPublished pour être appelés.

using MeterListener meterListener = new MeterListener();

Une fois que l’application a terminé l’écoute, la suppression de l’écouteur arrête le flux des rappels et libère toutes les références internes à l’objet écouteur. Le mot clé using utilisé lors de la déclaration de meterListener entraîne l’appel de Dispose lorsque la variable sort de la portée. Notez que Dispose ne s’engage qu’à ne pas initier de nouveaux rappels. Étant donné que les rappels se produisent sur différents threads, il peut toujours y avoir des rappels en cours après le retour de l’appel Dispose.

Pour garantir qu’une certaine région de code dans le rappel n’est pas en cours d’exécution et ne s’exécutera pas à l’avenir, la synchronisation de threads doit être ajoutée. Dispose n’inclut pas la synchronisation par défaut, car :

  • La synchronisation ajoute une surcharge de performances dans chaque rappel de mesure.
  • MeterListener est conçu comme une API hautement soucieuse des performances.