Partager via


Métriques de mise en réseau dans .NET

Les métriques sont des mesures numériques signalées au fil du temps. Elles sont généralement utilisées pour surveiller l’intégrité d’une application et générer des alertes.

À compter de .NET 8, les composants System.Net.Http et System.Net.NameResolution sont instrumentés pour publier des métriques à l’aide de la nouvelle API System.Diagnostics.Metrics de .NET. Ces métriques ont été conçues en association avec OpenTelemetry afin qu’elles respectent la norme et qu’elles fonctionnent bien avec des outils populaires tels que Prometheus et Grafana. Elles sont également multidimensionnelles, ce qui signifie que les mesures sont associées à des paires clé-valeur appelées étiquettes (ou attributs) qui permettent de catégoriser les données à des fins d’analyse.

Conseil

Pour obtenir la liste complète de tous les instruments intégrés avec leurs attributs, consultez Métriques System.Net.

Collecter les métriques System.Net

L’utilisation des métriques dans une application .NET se décompose en deux parties :

  • Instrumentation : le code des bibliothèques .NET prend des mesures et les associe à un nom de métrique. .NET et ASP.NET Core comptent de nombreuses métriques intégrées.
  • Collecte : une applications .NET configure les mesures nommées qui doivent être transmises à partir de l’application à des fins de stockage et d’analyse externes. Certains outils peuvent effectuer une configuration en dehors de l’application à l’aide de fichiers de configuration ou d’un outil d’IU.

Cette section présente différentes méthodes pour collecter et afficher les métriques System.Net.

Exemple d’application

Dans le cadre de ce tutoriel, créez une application simple qui envoie des requêtes HTTP à différents points de terminaison en parallèle.

dotnet new console -o HelloBuiltinMetrics
cd ..\HelloBuiltinMetrics

Remplacez le contenu de Program.cs par l’exemple de code suivant :

using System.Net;

string[] uris = ["http://example.com", "http://httpbin.org/get", "https://example.com", "https://httpbin.org/get"];
using HttpClient client = new()
{
    DefaultRequestVersion = HttpVersion.Version20
};

Console.WriteLine("Press any key to start.");
Console.ReadKey();

while (!Console.KeyAvailable)
{
    await Parallel.ForAsync(0, Random.Shared.Next(20), async (_, ct) =>
    {
        string uri = uris[Random.Shared.Next(uris.Length)];
        byte[] bytes = await client.GetByteArrayAsync(uri, ct);
        await Console.Out.WriteLineAsync($"{uri} - received {bytes.Length} bytes.");
    });
}

Afficher les métriques avec dotnet-counters

dotnet-counters est un outil de monitoring des performances multiplateforme pour le monitoring de l’intégrité ad hoc et l’investigation des performances de premier niveau.

dotnet tool install --global dotnet-counters

Lors de l’exécution sur un processus .NET 8+, dotnet-counters active les instruments définis par l’argument --counters et affiche les mesures. Il actualise continuellement la console avec les derniers chiffres :

dotnet-counters monitor --counters System.Net.Http,System.Net.NameResolution -n HelloBuiltinMetrics

Afficher les métriques dans Grafana avec OpenTelemetry et Prometheus

Vue d'ensemble

OpenTelemetry :

  • Est un projet open source indépendant du fournisseur pris en charge 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. Le flux de données des métriques se compose des étapes suivantes :

  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.Metrics;
using OpenTelemetry;
using System.Net;

using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder()
    .AddMeter("System.Net.Http", "System.Net.NameResolution")
    .AddPrometheusHttpListener(options => options.UriPrefixes = new string[] { "http://localhost:9184/" })
    .Build();

string[] uris = ["http://example.com", "http://httpbin.org/get", "https://example.com", "https://httpbin.org/get"];
using HttpClient client = new()
{
    DefaultRequestVersion = HttpVersion.Version20
};

while (!Console.KeyAvailable)
{
    await Parallel.ForAsync(0, Random.Shared.Next(20), async (_, ct) =>
    {
        string uri = uris[Random.Shared.Next(uris.Length)];
        byte[] bytes = await client.GetByteArrayAsync(uri, ct);
        await Console.Out.WriteLineAsync($"{uri} - received {bytes.Length} bytes.");
    });
}

Dans le code précédent :

  • AddMeter("System.Net.Http", "System.Net.NameResolution") configure OpenTelemetry pour transmettre toutes les métriques collectées par les compteurs System.Net.Http et System.Net.NameResolution intégrés.
  • AddPrometheusHttpListener configure OpenTelemetry pour exposer le point de terminaison HTTP des métriques de Prometheus sur le port 9184.

Remarque

Cette configuration diffère pour les applications ASP.NET Core, où les métriques sont exportées avec OpenTelemetry.Exporter.Prometheus.AspNetCore au lieu de HttpListener. Consultez l’exemple ASP.NET Core associé.

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 Graph du portail web de Prometheus, entrez http dans la zone de texte de l’expression et sélectionnez http_client_active_requests. http_client_active_requests Sous l’onglet Graph, Prometheus affiche la valeur du compteur http.client.active_requests émis par l’exemple d’application. Prometheus active requests graph

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 sélectionnant l’icône + dans la barre d’outils supérieure, puis en sélectionnant Dashboard. Dans l’éditeur de tableau de bord qui s’affiche, entrez Open HTTP/1.1 Connections dans la zone Title et la requête suivante dans le champ de l’expression PromQL :

sum by(http_connection_state) (http_client_open_connections{network_protocol_version="1.1"})

Grafana HTTP/1.1 Connections

  1. Sélectionnez Apply pour enregistrer et afficher le nouveau tableau de bord. Celui-ci présente le nombre de connexions HTTP/1.1 actives et inactives dans le pool.

Enrichissement

L’enrichissement est l’ajout d’étiquettes (ou attributs) personnalisées à une métrique. Ceci est utile si une application souhaite ajouter une catégorisation personnalisée aux tableaux de bord ou aux alertes créées avec des indicateurs de performance. L’instrument http.client.request.duration prend en charge l’enrichissement en inscrivant les rappels à HttpMetricsEnrichmentContext. Notez qu’il s’agit d’une API de bas niveau et qu’une inscription de rappel distincte est nécessaire pour chaque HttpRequestMessage.

Un moyen simple pour inscrire les rappels au même endroit consiste à implémenter un DelegatingHandlerpersonnalisé. Vous pouvez ainsi intercepter et modifier les requêtes avant qu’elles ne soient transférées au gestionnaire interne et envoyées au serveur :

using System.Net.Http.Metrics;

using HttpClient client = new(new EnrichmentHandler() { InnerHandler = new HttpClientHandler() });

await client.GetStringAsync("https://httpbin.org/response-headers?Enrichment-Value=A");
await client.GetStringAsync("https://httpbin.org/response-headers?Enrichment-Value=B");

sealed class EnrichmentHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        HttpMetricsEnrichmentContext.AddCallback(request, static context =>
        {
            if (context.Response is not null) // Response is null when an exception occurs.
            {
                // Use any information available on the request or the response to emit custom tags.
                string? value = context.Response.Headers.GetValues("Enrichment-Value").FirstOrDefault();
                if (value != null)
                {
                    context.AddCustomTag("enrichment_value", value);
                }
            }
        });
        return base.SendAsync(request, cancellationToken);
    }
}

Si vous travaillez avec IHttpClientFactory, vous pouvez utiliser AddHttpMessageHandler pour inscrire EnrichmentHandler :

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System.Net.Http.Metrics;

ServiceCollection services = new();
services.AddHttpClient(Options.DefaultName).AddHttpMessageHandler(() => new EnrichmentHandler());

ServiceProvider serviceProvider = services.BuildServiceProvider();
HttpClient client = serviceProvider.GetRequiredService<HttpClient>();

await client.GetStringAsync("https://httpbin.org/response-headers?Enrichment-Value=A");
await client.GetStringAsync("https://httpbin.org/response-headers?Enrichment-Value=B");

Remarque

Pour des raisons de performances, le rappel d’enrichissement n’est appelé que lorsque l’instrument http.client.request.duration est activé, ce qui signifie que les métriques doivent être collectées par un moyen ou un autre. Il peut s’agir de dotnet-monitor, de l’exportateur Prometheus, d’un MeterListener ou d’un MetricCollector<T>.

Intégration d’IMeterFactory et d’IHttpClientFactory

Les métriques HTTP ont été conçues dans un souci d’isolation et de testabilité. Ces aspects sont pris en charge par l’utilisation de IMeterFactory, qui autorise la publication de métriques par une instance de Meter personnalisée afin que les compteurs restent isolés les uns des autres. Par défaut, toutes les métriques sont émises par un Meter global interne à la bibliothèque System.Net.Http. Vous pouvez remplacer ce comportement en attribuant une instance d’IMeterFactory personnalisée à SocketsHttpHandler.MeterFactory ou HttpClientHandler.MeterFactory.

Remarque

Meter.Name est System.Net.Http pour toutes les métriques émises par HttpClientHandler et SocketsHttpHandler.

Lorsque vous travaillez avec Microsoft.Extensions.Http et IHttpClientFactory sur .NET 8+, l’implémentation d’IHttpClientFactory par défaut sélectionne automatiquement l’instance d’IMeterFactory inscrite dans IServiceCollection et l’attribue au gestionnaire principal qu’elle crée en interne.

Remarque

À compter de .NET 8, la méthode AddHttpClient appelle automatiquement AddMetrics pour initialiser les services de métriques et inscrire l’implémentation d’IMeterFactory par défaut à IServiceCollection. Par défaut, IMeterFactory met en cache les instances de Meter par nom, ce qui signifie qu’il y a une instance de Meter portant le nom System.Net.Http par IServiceCollection.

Métriques de test

L’exemple suivant montre comment valider les métriques intégrées dans des tests unitaires avec xUnit, IHttpClientFactory et MetricCollector<T> à partir du package NuGet Microsoft.Extensions.Diagnostics.Testing :

[Fact]
public async Task RequestDurationTest()
{
    // Arrange
    ServiceCollection services = new();
    services.AddHttpClient();
    ServiceProvider serviceProvider = services.BuildServiceProvider();
    var meterFactory = serviceProvider.GetService<IMeterFactory>();
    var collector = new MetricCollector<double>(meterFactory,
        "System.Net.Http", "http.client.request.duration");
    var client = serviceProvider.GetRequiredService<HttpClient>();

    // Act
    await client.GetStringAsync("http://example.com");

    // Assert
    await collector.WaitForMeasurementsAsync(minCount: 1).WaitAsync(TimeSpan.FromSeconds(5));
    Assert.Collection(collector.GetMeasurementSnapshot(),
        measurement =>
        {
            Assert.Equal("http", measurement.Tags["url.scheme"]);
            Assert.Equal("GET", measurement.Tags["http.request.method"]);
        });
}

Métriques et EventCounters

Les métriques sont plus riches en fonctionnalités que les EventCounters, notamment en raison de leur nature multidimensionnelle. Cette multidimensionnalité vous permet de créer des requêtes sophistiquées dans des outils comme Prometheus et d’obtenir des insights d’un niveau supérieur à ceux générés par EventCounters.

Néanmoins, à compter de .NET 8, seuls les composants System.Net.Http et System.Net.NameResolutions sont instrumentés à l’aide de métriques. Par conséquent, si vous avez besoin de compteurs des niveaux inférieurs de la pile, comme System.Net.Sockets ou System.Net.Security, vous devez utiliser des EventCounters.

Il existe également des différences sémantiques entre les métriques et leurs EventCounters correspondants. Par exemple, lors de l’utilisation de HttpCompletionOption.ResponseContentRead, l’EventCounter current-requests considère qu’une requête est active jusqu’à ce que le dernier octet du corps de la requête soit lu. Son équivalent http.client.active_requests dans les métriques n’inclut pas le temps passé à lire le corps de la réponse lors du comptage des requêtes actives.

Vous avez besoin d’autres métriques ?

Si vous avez des suggestions au sujet d’autres informations utiles pouvant être exposées via des métriques, créez un problème dotnet/runtime.