Condividi tramite


Metriche di rete in .NET

le metriche sono misurazioni numeriche registrate nel tempo. Vengono in genere usati 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 strumentati per pubblicare le metriche utilizzando 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 (note anche come attributi o etichette). I tag consentono la categorizzazione della misurazione per facilitare l'analisi.

Suggerimento

Per un elenco completo di tutti gli strumenti predefiniti insieme ai relativi attributi, vedere System.Net metriche.

Raccogliere metriche di System.Net

Per sfruttare i vantaggi della strumentazione delle metriche predefinita, è necessario configurare un'app .NET per raccogliere queste metriche. Questo significa in genere trasformarli per l'archiviazione esterna e l'analisi, ad esempio, per i sistemi di monitoraggio.

Esistono diversi modi per raccogliere le metriche di rete in .NET.

Raccogliere metriche con dotnet-counters

dotnet-counters è uno strumento da riga di comando multipiattaforma per l'esame ad hoc delle metriche .NET e dell'analisi delle prestazioni di primo livello.

Ai fini di questa esercitazione, creare un'app che invii 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)];
        try
        {
            byte[] bytes = await client.GetByteArrayAsync(uri, ct);
            await Console.Out.WriteLineAsync($"{uri} - received {bytes.Length} bytes.");
        }
        catch { await Console.Out.WriteLineAsync($"{uri} - failed."); }
    });
}

Assicurarsi che dotnet-counters sia installato:

dotnet tool install --global dotnet-counters

Avviare l'app HelloBuiltinMetrics.

dotnet run -c Release

Avviare dotnet-counters in una finestra separata dell'interfaccia della riga di comando e specificare il nome del processo e i contatori da controllare, quindi premere un tasto nell'app HelloBuiltinMetrics in modo che inizi a inviare richieste. Non appena le misurazioni iniziano l'atterraggio, dotnet-counters aggiorna continuamente la console con i numeri più recenti:

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

dotnet-counters output

Raccogliere metriche con .NET Aspire

Un modo semplice per raccogliere tracce e metriche nelle applicazioni ASP.NET consiste nell'usare .NET Aspire. .NET Aspire è un set di estensioni per .NET per semplificare la creazione e l'uso di applicazioni distribuite. Uno dei vantaggi dell'uso di .NET Aspire è che i dati di telemetria sono incorporati, usando le librerie OpenTelemetry per .NET.

I modelli di progetto predefiniti per .NET Aspire contengono un progetto ServiceDefaults. Ogni servizio nella soluzione .NET Aspire ha un riferimento al progetto Service Defaults. I servizi lo usano per impostare e configurare OTel.

Il modello di progetto Service Defaults include i pacchetti OTel SDK, ASP.NET, HttpClient e Strumentazione di esecuzione. Questi componenti di strumentazione vengono configurati nel file Extensions.cs. Per supportare la visualizzazione dei dati di telemetria in Aspira dashboard, il progetto Service Defaults include anche l'utilità di esportazione OTLP per impostazione predefinita.

Aspira dashboard è progettato per portare l'osservazione dei dati di telemetria al ciclo di debug locale, che consente agli sviluppatori di garantire che le applicazioni producano dati di telemetria. La visualizzazione dei dati di telemetria consente anche di diagnosticare tali applicazioni in locale. La possibilità di osservare le chiamate tra i servizi è utile in fase di debug, come nell'ambiente di produzione. Il Dashboard .NET Aspire viene avviato automaticamente quando si F5 il progetto AppHost da Visual Studio o dotnet run il progetto AppHost dalla riga di comando.

Procedura dettagliata rapida

  1. Crea un'app .NET Aspire 9 Starter utilizzando dotnet new:

    dotnet new aspire-starter-9 --output AspireDemo
    

    In alternativa, in Visual Studio, creare un nuovo progetto e selezionare il modello app iniziale .NET Aspire 9:

    Creare un'app starter .NET Aspira 9 in Visual Studio

  2. Aprire Extensions.cs nel progetto di ServiceDefaults e scorrere fino al metodo ConfigureOpenTelemetry. Si noti la chiamata AddHttpClientInstrumentation() che abbonata ai contatori di rete.

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

    Nota che in .NET 8+, AddHttpClientInstrumentation() può essere sostituito da iscrizioni manuali al misuratore.

    .WithMetrics(metrics =>
    {
        metrics.AddAspNetCoreInstrumentation()
            .AddMeter("System.Net.Http")
            .AddMeter("System.Net.NameResolution")
            .AddRuntimeInstrumentation();
    })
    
  3. Eseguire il progetto AppHost. Verrà avviata la dashboard Aspire.

  4. Navigare alla pagina Meteo dell'app webfrontend per generare una richiesta di HttpClient verso apiservice. Aggiornare la pagina più volte per inviare più richieste.

  5. Torna al Dashboard , passa alla pagina delle metriche e seleziona la risorsa webfrontend. Scorrendo verso il basso, dovresti riuscire a esplorare le metriche predefinite System.Net.

    Metriche di rete in Aspira dashboard

Per altre informazioni su .NET Aspire, vedere:

Riutilizzare il progetto Service Defaults senza orchestrazione .NET Aspire

Il progetto Aspira servizio predefinito offre un modo semplice per configurare OTel per i progetti ASP.NET, anche se non si usano il resto di .NET Aspire come AppHost per l'orchestrazione. Il progetto Service Defaults è disponibile come modello di progetto tramite Visual Studio o dotnet new. Configura OTel e prepara l'esportatore OTLP. È quindi possibile usare le variabili di ambiente OTel per configurare l'endpoint OTLP per inviare dati di telemetria e fornire le proprietà delle risorse per l'applicazione.

I passaggi per usare ServiceDefaults all'esterno di .NET Aspire sono:

  1. Aggiungere il progetto ServiceDefaults alla soluzione usando Aggiungi nuovo progetto in Visual Studio oppure usare dotnet new:

    dotnet new aspire-servicedefaults --output ServiceDefaults
    
  2. Fai riferimento al progetto ServiceDefaults dalla tua applicazione ASP.NET. In Visual Studio, selezionare Aggiungi>Riferimento al Progetto e selezionare il progetto ServiceDefaults

  3. Chiamare la funzione di installazione di OpenTelemetry ConfigureOpenTelemetry() come parte dell'inizializzazione del generatore di applicazioni.

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

Per una guida completa, vedere Esempio: Usare OpenTelemetry con OTLP e Aspire Dashboard autonomo.

Visualizzare le metriche in Grafana con OpenTelemetry e Prometheus

Per informazioni su come connettere un'app di esempio con Prometheus e Grafana, seguire la procedura dettagliata descritta in Uso di OpenTelemetry con Prometheus, Grafana e Jaeger.

Per stressare HttpClient inviando richieste parallele a vari endpoint, estendere l'app di esempio con l'endpoint seguente:

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

Creare un dashboard di Grafana selezionando l'icona + sulla barra degli strumenti superiore e quindi selezionando Dashboard. Nell'editor del dashboard visualizzato immettere Connessioni HTTP/1.1 Aperte nella casella Titolo e la seguente query nel campo espressione PromQL:

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

Selezionare Applica per salvare e visualizzare il nuovo dashboard. Visualizza il numero di connessioni HTTP/1.1 attive e inattive nel pool.

connessioni HTTP/1.1 in Grafana

Arricchimento

L'arricchimento è l'aggiunta di tag personalizzati (noti anche come attributi o etichette) a metriche. 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 il HttpMetricsEnrichmentContext. Si noti che si tratta di un'API di basso livello e per ogni HttpRequestMessageè necessaria una registrazione di callback separata.

Un modo semplice per eseguire la registrazione del callback in un'unica posizione consiste nell'implementare un DelegatingHandlerpersonalizzato. In questo modo è 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 il 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 strumento http.client.request.duration è abilitato, vale a dire che qualcosa deve raccogliere le metriche. Può essere dotnet-monitor, un esportatore Prometheus, un MeterListenero un MetricCollector<T>.

integrazione di IMeterFactory e IHttpClientFactory

Le metriche HTTP sono state progettate tenendo conto dell'isolamento e della testabilità. Questi aspetti sono supportati dall'uso di IMeterFactory, che consente la pubblicazione di metriche da un'istanza di Meter personalizzata per mantenere i contatori isolati l'uno dall'altro. Per impostazione predefinita, viene utilizzato un Meter globale per emettere tutte le metriche. Questa Meter è interna alla libreria System.Net.Http. Questo comportamento può essere modificato assegnando un'istanza personalizzata di IMeterFactory a SocketsHttpHandler.MeterFactory o HttpClientHandler.MeterFactory.

Nota

Il 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 di IHttpClientFactory predefinita seleziona automaticamente l'istanza IMeterFactory registrata nel 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. Il IMeterFactory predefinito memorizza nella cache Meter istanze in base al nome, ovvero è presente un 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, IHttpClientFactorye 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"]);
        });
}

Metriche e EventCounters

Le metriche sono più ricche di funzionalità rispetto a EventCounters, soprattutto per via della 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 le metriche, ovvero 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, l'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 avete suggerimenti per altre informazioni utili che potrebbero essere espresse tramite le metriche, creare una segnalazione dotnet/runtime.