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
- 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 :
Les API de métrique .NET enregistrent les mesures de l’exemple d’application.
La bibliothèque OpenTelemetry en cours d’exécution dans l’application agrège les mesures.
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.
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.
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 compteursSystem.Net.Http
etSystem.Net.NameResolution
intégrés.AddPrometheusHttpListener
configure OpenTelemetry pour exposer le point de terminaison HTTP des métriques de Prometheus sur le port9184
.
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
Rechargez la configuration ou redémarrez le serveur Prometheus.
Vérifiez qu’OpenTelemetryTest est dans l’état UP sur la page État>Cibles du portail web Prometheus.
Dans la page Graph du portail web de Prometheus, entrez
http
dans la zone de texte de l’expression et sélectionnezhttp_client_active_requests
. Sous l’onglet Graph, Prometheus affiche la valeur du compteurhttp.client.active_requests
émis par l’exemple d’application.
Afficher les métriques sur un tableau de bord Grafana
Suivez les instructions standard pour installer Grafana et la connecter à une source de données Prometheus.
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"})
- 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.