Dela via


Nätverksmått i .NET

Mått är numeriska mått som rapporteras över tid. De används vanligtvis för att övervaka hälsotillståndet för en app och generera aviseringar.

Från och med .NET 8 instrumenteras System.Net.Http- och System.Net.NameResolution-komponenterna för att publicera mått med hjälp av . NET:s nya System.Diagnostics.Metrics API. Dessa mått utformades i samarbete med OpenTelemetry för att se till att de överensstämmer med standarden och fungerar bra med populära verktyg som Prometheus och Grafana. De är också flerdimensionella, vilket innebär att mått associeras med nyckel/värde-par som kallas taggar (även kallade attribut eller etiketter). Taggar gör det möjligt att kategorisera måttet för att hjälpa till med analysen.

Tips

En omfattande lista över alla inbyggda instrument tillsammans med deras attribut finns i System.Net mått.

Samla in System.Net metrik

För att kunna dra nytta av den inbyggda måttinstrumentationen måste en .NET-app konfigureras för att samla in dessa mått. Detta innebär vanligtvis att omvandla dem för extern lagring och analys, till exempel till övervakningssystem.

Det finns flera sätt att samla in nätverksmått i .NET.

  • En snabb översikt med ett enkelt, fristående exempel finns i Samla in mått med dotnet-counters.
  • För insamling och övervakning av för produktionstid kan du använda Grafana med OpenTelemetry och Prometheus eller Azure Monitor Application Insights. Dessa verktyg kan dock vara obekväma att använda vid utvecklingstillfället på grund av deras komplexitet.
  • För insamling och felsökning av utvecklingstidsmått för rekommenderar vi att du använder .NET Aspire, vilket ger ett enkelt men utökningsbart sätt att påbörja mätvärden och för distribuerad spårning i din applikation och diagnostisera problem lokalt.
  • Det går också att återanvända projektet Aspire Service Defaults utan Aspire-orkestrering, vilket är ett praktiskt sätt att introducera OpenTelemetry-spårnings- och måttkonfigurations-API:erna i ditt ASP.NET projekt.

Samla in metrik med dotnet-counters

dotnet-counters är ett plattformsoberoende kommandoradsverktyg för ad hoc-undersökning av .NET-mått och prestandaundersökning på första nivån.

För den här självstudien skapar du en app som skickar HTTP-begäranden till olika slutpunkter parallellt.

dotnet new console -o HelloBuiltinMetrics
cd ..\HelloBuiltinMetrics

Ersätt innehållet i Program.cs med följande exempelkod:

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

Kontrollera att dotnet-counters är installerat:

dotnet tool install --global dotnet-counters

Starta appen HelloBuiltinMetrics.

dotnet run -c Release

Starta dotnet-counters i ett separat CLI-fönster och ange processnamnet och de mätare som ska övervakas och tryck sedan på en tangent i HelloBuiltinMetrics-appen så att den börjar skicka begäranden. När mätningarna börjar landa uppdaterar dotnet-counters konsolen kontinuerligt med de senaste siffrorna:

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

dotnet-counters utdata

Samla in mått med .NET Aspire

Ett enkelt sätt att samla in spårningar och mått i ASP.NET program är att använda .NET Aspire-. .NET Aspire är en uppsättning tillägg till .NET för att göra det enkelt att skapa och arbeta med distribuerade program. En av fördelarna med att använda .NET Aspire är att telemetri är inbyggd med hjälp av OpenTelemetry-biblioteken för .NET.

Standardprojektmallarna för .NET Aspire innehåller ett ServiceDefaults projekt. Varje tjänst i .NET Aspire-lösningen har en referens till servicestandardprojektet. Tjänsterna använder den för att konfigurera OTel.

Projektmallen Service Defaults innehåller paketen OTel SDK, ASP.NET, HttpClient och Runtime Instrumentation. Dessa instrumentationskomponenter konfigureras i filen Extensions.cs. För att stödja telemetrivisualisering i Aspire-instrumentpanelen innehåller servicestandardprojektet även OTLP-exportören som standard.

Aspire Dashboard är utformad för att ge telemetriobservation till den lokala felsökningscykeln, vilket gör det möjligt för utvecklare att se till att programmen producerar telemetri. Telemetrivisualiseringen hjälper också till att diagnostisera dessa program lokalt. Att kunna observera anropen mellan tjänster är lika användbart vid felsökning som i produktion. .NET Aspire-instrumentpanelen startas automatiskt när du F5AppHost Project från Visual Studio eller dotnet runAppHost-projektet från kommandoraden.

Snabb genomgång

  1. Skapa en .NET Aspire 9 Starter App med hjälp av dotnet new:

    dotnet new aspire-starter-9 --output AspireDemo
    

    Eller i Visual Studio skapar du ett nytt projekt och väljer mallen .NET Aspire 9 Starter App:

    Skapa en .NET Aspire 9 Starter-app i Visual Studio

  2. Öppna Extensions.cs i projektet ServiceDefaults och rulla till metoden ConfigureOpenTelemetry. Observera det AddHttpClientInstrumentation()-samtal som används för att prenumerera på data från nätverksmätarna.

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

    Observera att på .NET 8+ kan AddHttpClientInstrumentation() ersättas med manuella mätarprenumerationer:

    .WithMetrics(metrics =>
    {
        metrics.AddAspNetCoreInstrumentation()
            .AddMeter("System.Net.Http")
            .AddMeter("System.Net.NameResolution")
            .AddRuntimeInstrumentation();
    })
    
  3. Kör projektet AppHost. Detta bör öppna Aspire-kontrollpanelen.

  4. Gå till vädersidan i appen webfrontend för att skicka en HttpClient-förfrågan till apiservice. Uppdatera sidan flera gånger för att skicka flera begäranden.

  5. Gå tillbaka till instrumentpanelen, gå till sidan Mått och välj resursen webfrontend. Om du rullar nedåt bör du kunna bläddra bland de inbyggda måtten för System.Net.

    Nätverksmått i Aspire-instrumentpanelen

Mer information om .NET Aspire finns i:

Återanvänd servicestandardprojekt utan .NET Aspire-orkestrering

Projektet Aspire Service Defaults är ett enkelt sätt att konfigurera OTel för ASP.NET projekt, även om du inte använder resten av .NET Aspire- som AppHost för orkestrering. Service Defaults-projektet är tillgängligt som en projektmall via Visual Studio eller dotnet new. Den konfigurerar OTel och ställer in OTLP-exportören. Du kan sedan använda miljövariablerna OTel för att konfigurera OTLP-slutpunkten att skicka telemetri till och ange resursegenskaperna för programmet.

Stegen för att använda ServiceDefaults utanför .NET Aspire är:

  1. Lägg till projektet ServiceDefaults i lösningen med hjälp av Lägg till nytt projekt i Visual Studio eller använd dotnet new:

    dotnet new aspire-servicedefaults --output ServiceDefaults
    
  2. Referera till projektet ServiceDefaults från ditt ASP.NET-program. I Visual Studio väljer du Lägg till>projektreferens och väljer projektet ServiceDefaults"

  3. Anropa installationsfunktionen OpenTelemetry ConfigureOpenTelemetry() som en del av programverktygets initiering.

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

En fullständig genomgång finns i Exempel: Använd OpenTelemetry med OTLP och den fristående Aspire-instrumentpanelen.

Visa mått i Grafana med OpenTelemetry och Prometheus

Om du vill se hur du ansluter en exempelapp med Prometheus och Grafana följer du genomgången i Using OpenTelemetry with Prometheus, Grafana och Jaeger.

För att betona HttpClient genom att skicka parallella begäranden till olika slutpunkter utökar du exempelappen med följande slutpunkt:

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

Skapa en Grafana-instrumentpanel genom att välja ikonen + i det översta verktygsfältet och sedan välja Instrumentpanel. I instrumentpanelsredigeraren som visas anger du Öppna HTTP/1.1-anslutningar i rutan Rubrik och följande fråga i fältet PromQL-uttryck:

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

Välj Använd för att spara och visa den nya instrumentpanelen. Det visar antalet aktiva och inaktiva HTTP/1.1-anslutningar i poolen.

HTTP/1.1 Anslutningar i Grafana

Anrikning

Berikning är att lägga till anpassade taggar (även kallade attribut eller etiketter) till ett mätvärde. Detta är användbart om en app vill lägga till en anpassad kategorisering till instrumentpaneler eller aviseringar som byggs på metrik. http.client.request.duration-instrumentet stöder berikning genom att registrera återanrop med HttpMetricsEnrichmentContext. Observera att detta är ett API på låg nivå och att det krävs en separat återanropsregistrering för varje HttpRequestMessage.

Ett enkelt sätt att göra återanropsregistreringen på en enda plats är att implementera en anpassad DelegatingHandler. På så sätt kan du fånga upp och ändra begäranden innan de vidarebefordras till den inre hanteraren och skickas till servern:

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

Om du arbetar med IHttpClientFactorykan du använda AddHttpMessageHandler för att registrera 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");

Anteckning

Av prestandaskäl anropas återanropet för berikning endast när http.client.request.duration instrument är aktiverat, vilket innebär att något bör samla in måtten. Detta kan vara dotnet-monitor, Prometheus-exportör, en MeterListener, eller en MetricCollector<T>.

IMeterFactory och IHttpClientFactory integrering

HTTP-mått utformades med isolering och testbarhet i åtanke. Dessa aspekter stöds av användningen av IMeterFactory, som möjliggör publicering av mått från en anpassad Meter-instans för att hålla Meters isolerade från varandra. Som standard används en global Meter för att sända ut alla mått. Detta är Meter internt i System.Net.Http-biblioteket. Det här beteendet kan åsidosättas genom att tilldela en anpassad IMeterFactory-instans till SocketsHttpHandler.MeterFactory eller HttpClientHandler.MeterFactory.

Anmärkning

Meter.Name är System.Net.Http för alla mått som genereras av HttpClientHandler och SocketsHttpHandler.

När du arbetar med Microsoft.Extensions.Http och IHttpClientFactory på .NET 8+ väljer standardimplementeringen IHttpClientFactory automatiskt den IMeterFactory instans som registrerats i IServiceCollection och tilldelar den till den primära hanteraren som skapas internt.

Not

Från och med .NET 8 anropar AddHttpClient-metoden automatiskt AddMetrics för att initiera måtttjänsterna och registrera standardimplementeringen IMeterFactory med IServiceCollection. Standard-IMeterFactory cachelagrar Meter instanser efter namn, vilket innebär att det finns en Meter med namnet System.Net.Http per IServiceCollection.

Testmått

I följande exempel visas hur du validerar inbyggda mått i enhetstester med xUnit, IHttpClientFactoryoch MetricCollector<T> från Microsoft.Extensions.Diagnostics.Testing NuGet-paketet:

[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ått jämfört med EventCounters

Metriker är mer funktionsrika än EventCounters, framför allt på grund av deras flerdimensionella natur. Med den här flerdimensionelliteten kan du skapa avancerade frågor i verktyg som Prometheus och få insikter på en nivå som inte är möjlig med EventCounters.

Från och med .NET 8 instrumenteras dock endast System.Net.Http- och System.Net.NameResolutions-komponenterna med hjälp av mått, vilket innebär att om du behöver räknare från de lägre nivåerna i stacken, till exempel System.Net.Sockets eller System.Net.Security, måste du använda EventCounters.

Dessutom finns det vissa semantiska skillnader mellan Mått och deras matchande EventCounters. När du till exempel använder HttpCompletionOption.ResponseContentReadanser current-requests EventCounter att en begäran är aktiv fram till det ögonblick då den sista byte av begärandetexten har lästs. Dess måttmotsvarighet http.client.active_requests inkluderar inte den tid som ägnas åt att läsa svarstexten när de aktiva begärandena räknas.

Behöver du fler mått?

Om du har förslag på annan användbar information som kan exponeras via mått, skapa ett dotnet/runtime-ärende.