Partage via


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

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

À partir de .NET 8, les composants System.Net.Http et System.Net.NameResolution sont instrumentés pour publier des métriques en utilisant la nouvelle API System.Diagnostics.Metrics de .NET . Ces métriques ont été conçues en collaboration avec OpenTelemetry pour s’assurer qu’elles sont cohérentes avec la norme et fonctionnent bien avec les outils populaires tels que Prometheus et Grafana. Ils sont également multidimensionnels, ce qui signifie que les mesures sont associées à des paires clé-valeur appelées balises (également appelées attributs ou étiquettes). Les balises permettent la catégorisation de la mesure pour faciliter l’analyse.

Conseil

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

Collecter les métriques de System.Net

Pour tirer parti de l’instrumentation des métriques intégrées, une application .NET doit être configurée pour collecter ces métriques. Cela signifie généralement les transformer pour le stockage externe et l’analyse, par exemple, pour surveiller les systèmes.

Il existe plusieurs façons de collecter des métriques de mise en réseau dans .NET.

  • Pour obtenir une vue d’ensemble rapide à l’aide d’un exemple simple et indépendant, voir Collectez des métriques avec dotnet-counters.
  • Pour la collecte et la surveillance des métriques au moment de la production, vous pouvez utiliser Grafana avec OpenTelemetry et Prometheus ou azure Monitor Application Insights. Toutefois, ces outils peuvent être gênants à utiliser au moment du développement en raison de leur complexité.
  • Pour la collecte et résolution des problèmes de métriques au moment de la production, nous vous recommandons d’utiliser .NET Aspire, qui offre un moyen simple, mais extensible de lancer des métriques et un suivi distribué dans votre application et de diagnostiquer les problèmes localement.
  • Il est également possible de réutiliser le projet de défaut de service Aspire sans l’orchestration Aspire, ce qui constitue une façon pratique d’introduire dans votre projet ASP.NET les API de configuration de suivi et de métriques OpenTelemetry.

Rassembler des métriques avec dotnet-counters

dotnet-counters est un outil en ligne de commande multiplateforme pour l’examen ad hoc des métriques .NET et l’examen des performances de premier niveau.

Dans le cadre de ce tutoriel, créez une application 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)];
        try
        {
            byte[] bytes = await client.GetByteArrayAsync(uri, ct);
            await Console.Out.WriteLineAsync($"{uri} - received {bytes.Length} bytes.");
        }
        catch { await Console.Out.WriteLineAsync($"{uri} - failed."); }
    });
}

Vérifiez que dotnet-counters est installé :

dotnet tool install --global dotnet-counters

Démarrez l’application HelloBuiltinMetrics.

dotnet run -c Release

Démarrez dotnet-counters dans une fenêtre CLI distincte et spécifiez le nom du processus et les compteurs à surveiller, puis appuyez sur une touche dans l’application HelloBuiltinMetrics afin qu’elle commence à envoyer des demandes. Dès que les mesures commencent à atterrir, dotnet-counters actualise en permanence la console avec les derniers nombres :

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

Sortie dotnet-counters

Collecter des métriques avec .NET Aspire

Un moyen simple de collecter des traces et des métriques dans ASP.NET applications consiste à utiliser .NET Aspire . .NET Aspire est un ensemble d’extensions à .NET pour faciliter la création et l’utilisation d’applications distribuées. L’un des avantages de l’utilisation de .NET Aspire est que la télémétrie est intégrée, à l’aide des bibliothèques OpenTelemetry pour .NET.

Les modèles de projet par défaut pour .NET Aspire contiennent un projet ServiceDefaults. Chaque service de la solution .NET Aspire a une référence au projet Service Defaults. Les services l’utilisent pour mettre en place et configurer OTel.

Le modèle de projet Service Defaults inclut les packages OTel SDK, ASP.NET, HttpClient et Runtime Instrumentation. Ces composants d’instrumentation sont configurés dans le fichier Extensions.cs. Pour prendre en charge la visualisation de télémétrie dans le tableau de bord Aspire, le projet Service Defaults inclut par défaut l'exportateur OTLP.

Le tableau de bord Aspire est conçu pour apporter une observation de télémétrie au cycle de débogage local, ce qui permet aux développeurs de s’assurer que les applications produisent des données de télémétrie. La visualisation de télémétrie permet également de diagnostiquer ces applications localement. La possibilité d’observer les appels entre les services est aussi utile au moment du débogage qu’en production. Le tableau de bord Aspire .NET est lancé automatiquement lorsque vous F5 le projet AppHost à partir de Visual Studio ou dotnet run le projet AppHost à partir de la ligne de commande.

Procédure pas à pas rapide

  1. Créez une application .NET Aspire 9 Starter à l’aide de dotnet new.

    dotnet new aspire-starter-9 --output AspireDemo
    

    Ou, dans Visual Studio, créez un nouveau projet et sélectionnez le modèle application de démarrage .NET Aspire 9 :

    Créer une application De démarrage Aspire 9 .NET dans Visual Studio

  2. Ouvrez Extensions.cs dans le projet ServiceDefaults, puis faites défiler jusqu’à la méthode ConfigureOpenTelemetry. Notez que l’appel AddHttpClientInstrumentation() s’abonne aux compteurs de mise en réseau.

    .WithMetrics(metrics =>
    {
        metrics.AddAspNetCoreInstrumentation()
            .AddHttpClientInstrumentation()
            .AddRuntimeInstrumentation();
    })
    

    Notez que sur .NET 8+, AddHttpClientInstrumentation() peut être remplacé par des abonnements manuels aux compteurs :

    .WithMetrics(metrics =>
    {
        metrics.AddAspNetCoreInstrumentation()
            .AddMeter("System.Net.Http")
            .AddMeter("System.Net.NameResolution")
            .AddRuntimeInstrumentation();
    })
    
  3. Exécutez le projet AppHost. Cela doit lancer le tableau de bord Aspire.

  4. Accédez à la page Météo de l’application webfrontend pour générer une demande de HttpClient vers apiservice. Actualisez la page plusieurs fois pour envoyer plusieurs requêtes.

  5. Revenez au tableau de bord, accédez à la page Métriques , puis sélectionnez la ressource webfrontend. En faisant défiler vers le bas, vous devriez être en mesure de parcourir les métriques intégrées System.Net.

    Métriques de mise en réseau dans le tableau de bord Aspire

Pour plus d’informations sur .NET Aspire, consultez :

Réutiliser le projet Défauts du Service sans orchestration de .NET Aspire

Le projet Aspire Service Defaults offre un moyen simple de configurer OTel pour les projets ASP.NET, même si l'on n'utilise pas les autres composants de .NET Aspire, tels que AppHost pour l’orchestration. Le projet Service Defaults est disponible en tant que modèle de projet via Visual Studio ou dotnet new. Il configure OTel et configure l’exportateur OTLP. Vous pouvez ensuite utiliser les variables d’environnement OTel pour configurer le point de terminaison OTLP pour envoyer des données de télémétrie et fournir les propriétés de ressource de l’application.

Les étapes à suivre pour utiliser ServiceDefaults en dehors de .NET Aspire sont les suivantes :

  1. Ajoutez le projet ServiceDefaults à la solution à l’aide d’Ajouter un nouveau projet dans Visual Studio ou utilisez dotnet new:

    dotnet new aspire-servicedefaults --output ServiceDefaults
    
  2. Référencez le projet ServiceDefaults à partir de votre application ASP.NET. Dans Visual Studio, sélectionnez Ajouter> de référence de projet, puis sélectionnez le projet ServiceDefaults »

  3. Appelez la fonction de configuration OpenTelemetry ConfigureOpenTelemetry() dans le cadre de l’initialisation de votre générateur d’applications.

    var builder = WebApplication.CreateBuilder(args)
    builder.ConfigureOpenTelemetry(); // Extension method from ServiceDefaults.
    var app = builder.Build();
    app.MapGet("/", () => "Hello World!");
    app.Run();
    

Pour obtenir une procédure pas à pas complète, consultez Exemple : Utiliser OpenTelemetry avec OTLP et le tableau de bord Aspire autonome.

Afficher les métriques dans Grafana avec OpenTelemetry et Prometheus

Pour voir comment connecter un exemple d’application avec Prometheus et Grafana, suivez la procédure pas à pas dans Utilisation d’OpenTelemetry avec Prometheus, Grafana et Jaeger.

Pour stresser HttpClient en envoyant des requêtes parallèles à différents points de terminaison, étendez l’exemple d’application avec le point de terminaison suivant :

app.MapGet("/ClientStress", async Task<string> (ILogger<Program> logger, HttpClient client) =>
{
    string[] uris = ["http://example.com", "http://httpbin.org/get", "https://example.com", "https://httpbin.org/get"];
    await Parallel.ForAsync(0, 50, async (_, ct) =>
    {
        string uri = uris[Random.Shared.Next(uris.Length)];

        try
        {
            await client.GetAsync(uri, ct);
            logger.LogInformation($"{uri} - done.");
        }
        catch { logger.LogInformation($"{uri} - failed."); }
    });
    return "Sent 50 requests to example.com and httpbin.org.";
});

Créez un tableau de bord Grafana en sélectionnant l’icône + dans la barre d’outils supérieure, puis en sélectionnant Tableau de bord. Dans l’éditeur de tableau de bord qui s’affiche, saisissez Connexions Ouvertes HTTP/1.1 dans la zone Titre et la requête suivante dans le champ d’expression PromQL :

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

Sélectionnez Appliquer pour enregistrer et afficher le nouveau tableau de bord. Il affiche le nombre de connexions HTTP/1.1 actives et inactives dans le pool.

Connexions HTTP/1.1 dans Grafana

Enrichissement

L’enrichissement est l’ajout de tags personnalisés (également appelés attributs ou étiquettes) à une métrique. Cela est utile si une application souhaite ajouter une catégorisation personnalisée aux tableaux de bord ou aux alertes créées avec des métriques. L’instrument http.client.request.duration prend en charge l’enrichissement en inscrivant des rappels auprès du 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 d’effectuer l’enregistrement de rappel en un seul endroit consiste à implémenter un DelegatingHandlerpersonnalisé. Cela vous permet d’intercepter et de 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 utilisez IHttpClientFactory, vous pouvez utiliser AddHttpMessageHandler pour inscrire le 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 quelque chose doit collecter les métriques. Cela peut être dotnet-monitor, l’exportateur Prometheus, un MeterListener ou un MetricCollector<T>.

Intégration IMeterFactory et IHttpClientFactory

Les métriques HTTP ont été conçues avec isolation et testabilité à l’esprit. Ces aspects sont pris en charge par l’utilisation de IMeterFactory, qui permet de publier des métriques par une instance de Meter personnalisée afin de garder les compteurs isolés les uns des autres. Par défaut, une Meter globale est utilisée pour émettre toutes les métriques. Ce Meter est interne à la bibliothèque System.Net.Http. Ce comportement peut être substitué en affectant une instance de IMeterFactory personnalisée à SocketsHttpHandler.MeterFactory ou HttpClientHandler.MeterFactory.

Remarque

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

Lorsque vous utilisez Microsoft.Extensions.Http et IHttpClientFactory sur .NET 8+, l’implémentation par défaut IHttpClientFactory sélectionne automatiquement l’instance de IMeterFactory inscrite dans le IServiceCollection et l’affecte 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 par défaut IMeterFactory avec IServiceCollection. La IMeterFactory par défaut met en cache des instances Meter par nom, ce qui signifie qu’il y a une Meter avec le nom System.Net.Http par IServiceCollection.

Métriques de test

L’exemple suivant montre comment valider des métriques intégrées dans des tests unitaires à l’aide de xUnit, IHttpClientFactoryet 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 vs. EventCounters

Les métriques sont plus riches en fonctionnalités que EventCounters, notamment en raison de leur nature multidimensionnelle. Cette multidimensionnalité vous permet de créer des requêtes sophistiquées dans des outils tels que Prometheus et d’obtenir des insights sur un niveau qui n’est pas possible avec EventCounters.

Néanmoins, à partir de .NET 8, seuls les System.Net.Http et les composants System.Net.NameResolutions sont instrumentés à l’aide de métriques, ce qui signifie que si vous avez besoin de compteurs provenant des niveaux inférieurs de la pile tels que System.Net.Sockets ou System.Net.Security, vous devez utiliser EventCounters.

De plus, il existe certaines différences sémantiques entre les métriques et leurs EventCounters correspondants. Par exemple, lors de l’utilisation de HttpCompletionOption.ResponseContentRead, l'current-requests EventCounter considère qu’une demande est active jusqu’au moment où le dernier octet du corps de la demande a été lu. Son équivalent de métriques http.client.active_requests 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 pour d’autres informations utiles qui peuvent être exposées via des métriques, créez un problème dotnet/runtime.