Metriche di rete in .NET
Le metriche sono misurazioni numeriche segnalate nel tempo. Vengono in genere usate per monitorare l'integrità di un'app e generare avvisi.
A partire da .NET 8, i componenti System.Net.Http
e System.Net.NameResolution
vengono instrumentati per pubblicare le metriche usando la nuova API System.Diagnostics.Metrics di NET.
Queste metriche sono state progettate in collaborazione con OpenTelemetry per assicurarsi che siano coerenti con lo standard e funzionino bene con gli strumenti più diffusi come Prometheus e Grafana.
Sono anche multidimensionali, ovvero le misurazioni sono associate a coppie chiave-valore denominate tag (attributi o etichette) che consentono di classificare i dati per l'analisi.
Suggerimento
Per un elenco completo di tutti gli strumenti predefiniti insieme ai relativi attributi, vedere le Metriche System.Net.
Raccogliere metriche di System.Net
Esistono due parti per l'uso delle metriche in un'app .NET:
- Strumentazione: il codice nelle librerie .NET accetta misure e associa queste misurazioni a un nome di metrica. .NET e ASP.NET Core includono molte metriche predefinite.
- Raccolta: un'app .NET configura le metriche denominate da trasmettere dall'app per l'archiviazione e l'analisi esterne. Alcuni strumenti possono eseguire la configurazione all'esterno dell'app usando file di configurazione o uno strumento dell'interfaccia utente.
Questa sezione illustra vari metodi per raccogliere e visualizzare metriche System.Net.
App di esempio
Ai fini di questa esercitazione, creare una semplice app che invia richieste HTTP a vari endpoint in parallelo.
dotnet new console -o HelloBuiltinMetrics
cd ..\HelloBuiltinMetrics
Sostituire il contenuto di Program.cs
con il codice di esempio seguente:
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.");
});
}
Visualizzare le metriche con dotnet-counters
dotnet-counters
è uno strumento di monitoraggio delle prestazioni multipiattaforma per il monitoraggio ad hoc dello stato e l'analisi delle prestazioni di primo livello.
dotnet tool install --global dotnet-counters
Quando si esegue su un processo .NET 8+, dotnet-counters
abilita gli strumenti definiti dall'argomento --counters
e visualizza le misurazioni. Aggiorna continuamente la console con i numeri più recenti:
dotnet-counters monitor --counters System.Net.Http,System.Net.NameResolution -n HelloBuiltinMetrics
Visualizzare le metriche in Grafana con OpenTelemetry e Prometheus
Panoramica
- 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. Il flusso di lavoro delle metriche è costituito dai passaggi seguenti:
Le API delle metriche .NET registrano le misurazioni dall'app di esempio.
La libreria OpenTelemetry in esecuzione nell'app aggrega le misurazioni.
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.
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.
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.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.");
});
}
Nel codice precedente:
AddMeter("System.Net.Http", "System.Net.NameResolution")
configura OpenTelemetry per trasmettere tutte le metriche raccolte dai contatoriSystem.Net.Http
eSystem.Net.NameResolution
predefiniti.AddPrometheusHttpListener
configura OpenTelemetry per esporre l'endpoint HTTP delle metriche di Prometheus sulla porta9184
.
Nota
Questa configurazione è diversa per le app ASP.NET Core, in cui le metriche vengono esportate con OpenTelemetry.Exporter.Prometheus.AspNetCore
invece di HttpListener
. Vedere l'esempio correlato ASP.NET Core.
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
Ricaricare la configurazione o riavviare il server Prometheus.
Verificare che OpenTelemetryTest sia nello stato UP nella pagina Stato> Destinazioni del portale Web Prometheus.
Nella pagina Grafo del portale Web di Prometheus immettere
http
nella casella di testo dell'espressione e selezionarehttp_client_active_requests
. Nella scheda del grafo Prometheus mostra il valore del contatorehttp.client.active_requests
generato dall'app di esempio.
Visualizzare le metriche in una dashboard di Grafana
Seguire le istruzioni standard per installare Grafana e connetterlo a un'origine dati Prometheus.
Creare un dashboard di Grafana selezionando l'icona + sulla barra degli strumenti superiore e quindi selezionando Dashboard. Nell'editor del dashboard visualizzato immettere Apri connessioni HTTP/1.1 nella casella Titolo e la query seguente nel campo espressione PromQL:
sum by(http_connection_state) (http_client_open_connections{network_protocol_version="1.1"})
- Selezionare Applica per salvare e visualizzare la nuova dashboard. Visualizza il numero di connessioni HTTP/1.1 attive e inattive nel pool.
Arricchimento
L'arricchimento è l'aggiunta di tag personalizzati (ad esempio attributi o etichette) a una metrica. Ciò è utile se un'app vuole aggiungere una categorizzazione personalizzata ai dashboard o agli avvisi compilati con le metriche.
Lo strumento http.client.request.duration
supporta l'arricchimento registrando i callback con HttpMetricsEnrichmentContext.
Si noti che si tratta di un'API di basso livello e una registrazione di callback separata è necessaria per ogni HttpRequestMessage
.
Un modo semplice per eseguire la registrazione di callback in un'unica posizione consiste nell'implementare un oggetto personalizzato DelegatingHandler. In questo modo sarà possibile intercettare e modificare le richieste prima che vengano inoltrate al gestore interno e inviate al server:
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);
}
}
Se si usa IHttpClientFactory
, è possibile usare AddHttpMessageHandler per registrare 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");
Nota
Per motivi di prestazioni, il callback di arricchimento viene richiamato solo quando lo strumentohttp.client.request.duration
è abilitato, vale a dire che qualcosa deve raccogliere le metriche.
Può essere dotnet-monitor
, l'utilità di esportazione Prometheus, un MeterListener
o un MetricCollector<T>
.
Integrazione IMeterFactory
e IHttpClientFactory
Le metriche HTTP sono state progettate tenendo conto dell'isolamento e della possibilità di test. Questi aspetti sono supportati dall'uso di IMeterFactory, che consente la pubblicazione di metriche da un'istanza Meter personalizzata per mantenere i contatori isolati l'uno dall'altro.
Per impostazione predefinita, tutte le metriche vengono generate da un Meter interno globale alla libreria System.Net.Http
. Questo comportamento può essere sottoposto a override assegnando un'istanza personalizzata IMeterFactory a SocketsHttpHandler.MeterFactory o HttpClientHandler.MeterFactory.
Nota
Meter.Name è System.Net.Http
per tutte le metriche generate da HttpClientHandler
e SocketsHttpHandler
.
Quando si lavora con Microsoft.Extensions.Http
e IHttpClientFactory
in .NET 8+, l'implementazione predefinita IHttpClientFactory
seleziona automaticamente l'istanza IMeterFactory
registrata in IServiceCollection e la assegna al gestore primario che crea internamente.
Nota
A partire da .NET 8, il metodo AddHttpClient chiama automaticamente AddMetrics per inizializzare i servizi delle metriche e registrare l'implementazione IMeterFactory predefinita con IServiceCollection. IMeterFactory predefinito memorizza nella cache le istanze Meter in base al nome, vale a dire che ne sarà presente una Meter con il nome System.Net.Http
per IServiceCollection.
Metriche di test
L'esempio seguente illustra come convalidare le metriche predefinite negli unit test usando xUnit, IHttpClientFactory
e MetricCollector<T>
dal pacchetto 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"]);
});
}
Metrica ed EventCounters
Le metriche sono più ricche di funzionalità rispetto a EventCounters, in particolare grazie alla loro natura multidimensionale. Questa multidimensionalità consente di creare query sofisticate in strumenti come Prometheus e ottenere informazioni dettagliate su un livello non possibile con EventCounters.
Tuttavia, a partire da .NET 8, solo i componenti System.Net.Http
e System.Net.NameResolutions
vengono instrumentati usando metriche, il che significa che se sono necessari contatori dai livelli inferiori dello stack, ad esempio System.Net.Sockets
o System.Net.Security
, è necessario usare EventCounters.
Esistono inoltre alcune differenze semantiche tra le metriche e i relativi EventCounters corrispondenti.
Ad esempio, quando si usa HttpCompletionOption.ResponseContentRead
, current-requests
EventCounter considera attiva una richiesta fino al momento in cui è stato letto l'ultimo byte del corpo della richiesta.
La controparte delle metriche http.client.active_requests
non include il tempo impiegato per leggere il corpo della risposta durante il conteggio delle richieste attive.
Sono necessarie altre metriche?
Se si hanno suggerimenti per altre informazioni utili che potrebbero essere esposte tramite metriche, creare un problema dotnet/runtime.