Udostępnij za pośrednictwem


Metryki sieci na platformie .NET

Metryki to pomiary liczbowe raportowane w czasie. Są one zwykle używane do monitorowania kondycji aplikacji i generowania alertów.

Począwszy od .NET 8, składniki System.Net.Http i System.Net.NameResolution są instrumentowane do publikowania metryk za pomocą nowego interfejsu API System.Diagnostics.Metrics platformy .NET. Te metryki zostały zaprojektowane we współpracy z usługą OpenTelemetry , aby upewnić się, że są one zgodne ze standardem i działają dobrze z popularnymi narzędziami, takimi jak Prometheus i Grafana. Są one również wielowymiarowe, co oznacza, że miary są skojarzone z parami klucz-wartość nazywanymi tagami (nazywanymi również atrybutami lub etykietami). Tagi umożliwiają kategoryzacja miary w celu ułatwienia analizy.

Wskazówka

Aby uzyskać kompleksową listę wszystkich wbudowanych instrumentów wraz z ich atrybutami, zobacz System.Net metryki.

Zbieranie metryk System.Net

Aby móc korzystać z wbudowanej instrumentacji metryk, należy skonfigurować aplikację platformy .NET do zbierania tych metryk. Zazwyczaj oznacza to przekształcenie ich na potrzeby przechowywania i analiz zewnętrznych, na przykład dla systemów monitorujących.

Istnieje kilka sposobów zbierania metryk sieci na platformie .NET.

Zbieranie metryk za pomocą liczników dotnet-counter

dotnet-counters to międzyplatformowe narzędzie wiersza polecenia do badania ad hoc metryk platformy .NET i badania wydajności pierwszego poziomu.

Na potrzeby tego samouczka utwórz aplikację, która równolegle wysyła żądania HTTP do różnych punktów końcowych.

dotnet new console -o HelloBuiltinMetrics
cd ..\HelloBuiltinMetrics

Zastąp zawartość Program.cs następującym przykładowym kodem:

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

Upewnij się, że dotnet-counters jest zainstalowany:

dotnet tool install --global dotnet-counters

Uruchom aplikację HelloBuiltinMetrics.

dotnet run -c Release

Uruchom dotnet-counters w osobnym oknie interfejsu wiersza polecenia i określ nazwę procesu oraz mierniki do obejrzenia, a następnie naciśnij w aplikacji HelloBuiltinMetrics, aby rozpocząć wysyłanie żądań. Gdy tylko rozpoczną się pomiary, dotnet-counters stale odświeża konsolę najnowszymi danymi.

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

dane wyjściowe dotnet-counters

Zbieranie metryk za pomocą platformy .NET Aspire

Prostym sposobem zbierania śladów i metryk w aplikacjach ASP.NET jest użycie .NET Aspire. .NET Aspire to zestaw rozszerzeń platformy .NET, który ułatwia tworzenie i pracę z aplikacjami rozproszonymi. Jedną z zalet korzystania z platformy .NET Aspire jest wbudowana telemetria przy użyciu bibliotek OpenTelemetry dla platformy .NET.

Domyślne szablony projektów dla platformy .NET Aspire zawierają projekt ServiceDefaults. Każda usługa w rozwiązaniu .NET Aspire ma odwołanie do projektu Service Defaults. Usługi używają tego do ustawiania i konfigurowania OTel.

Szablon projektu Service Defaults zawiera pakiety OTel SDK, ASP.NET, HttpClient i Runtime Instrumentation. Te składniki instrumentacji są konfigurowane w pliku Extensions.cs. Aby obsługiwać wizualizację telemetrii w Aspire Dashboard, projekt Service Defaults zawiera również eksportera OTLP jako domyślną opcję.

Aspire Dashboard jest zaprojektowany do umożliwienia obserwacji danych telemetrycznych w lokalnym cyklu debugowania, aby deweloperzy mogli zapewnić, że aplikacje generują dane telemetryczne. Wizualizacja telemetrii pomaga również diagnozować te aplikacje lokalnie. Możliwość obserwowania wywołań między usługami jest tak przydatna w czasie debugowania, jak w środowisku produkcyjnym. Panel .NET Aspire uruchamia się automatycznie, gdy F5 projekt AppHost z programu Visual Studio lub dotnet run projekt AppHost z wiersza poleceń.

Szybki przewodnik

  1. Utwórz aplikację startową .NET Aspire 9 przy użyciu dotnet new:

    dotnet new aspire-starter-9 --output AspireDemo
    

    Możesz też w programie Visual Studio utworzyć nowy projekt i wybrać szablon aplikacji startowej .NET Aspire 9 :

    Tworzenie aplikacji startowej platformy .NET Aspire 9 w programie Visual Studio

  2. Otwórz Extensions.cs w projekcie ServiceDefaults i przewiń do metody ConfigureOpenTelemetry. Zwróć uwagę na AddHttpClientInstrumentation() połączenie subskrybujące mierniki sieciowe.

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

    Należy pamiętać, że w wersji .NET 8 lub nowszej, AddHttpClientInstrumentation() można zastąpić subskrypcjami ręcznymi mierników.

    .WithMetrics(metrics =>
    {
        metrics.AddAspNetCoreInstrumentation()
            .AddMeter("System.Net.Http")
            .AddMeter("System.Net.NameResolution")
            .AddRuntimeInstrumentation();
    })
    
  3. Uruchom projekt AppHost. Powinno to spowodować uruchomienie pulpitu nawigacyjnego Aspire.

  4. Przejdź do strony Pogoda w aplikacji webfrontend, aby wygenerować żądanie HttpClient do apiservice. Odśwież stronę kilka razy, aby wysłać wiele żądań.

  5. Wróć do pulpitu nawigacyjnego, przejdź do strony Metryki i wybierz webfrontend zasób. Powinieneś móc przewijać w dół, aby przeglądać wbudowane metryki System.Net.

    Metryki sieciowe w Aspire Dashboard

Aby uzyskać więcej informacji na temat platformy .NET Aspire, zobacz:

Ponowne używanie projektu ustawień domyślnych usługi bez orkiestracji platformy .NET Aspire

Projekt Aspire Service Defaults zapewnia łatwy sposób konfigurowania OTel dla projektów ASP.NET, nawet jeśli nie korzystasz z reszty .NET Aspire, takich jak AppHost do celów orkiestracji. Projekt Domyślne ustawienia usługi jest dostępny jako szablon projektu za pośrednictwem programu Visual Studio lub dotnet new. Konfiguruje OTel i ustawia eksportera OTLP. Następnie możesz użyć zmiennych środowiskowych OTel, aby skonfigurować punkt końcowy OTLP do wysyłania danych telemetrycznych i podać właściwości zasobu dla aplikacji.

Kroki korzystania z usługi ServiceDefaults poza platformą .NET Aspire są następujące:

  1. Dodaj projekt ServiceDefaults do rozwiązania przy użyciu polecenia Dodaj nowy projekt w programie Visual Studio lub użyj dotnet new:

    dotnet new aspire-servicedefaults --output ServiceDefaults
    
  2. Odwołaj się do projektu ServiceDefaults z aplikacji ASP.NET. W programie Visual Studio wybierz pozycję Dodaj>Dokumentacja projektu i wybierz projekt ServiceDefaults"

  3. Wywołaj funkcję instalacji OpenTelemetry ConfigureOpenTelemetry() w ramach inicjowania konstruktora aplikacji.

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

Aby zapoznać się z pełnym przewodnikiem, zobacz Przykład: Używanie biblioteki OpenTelemetry z funkcją OTLP i autonomicznym pulpitem nawigacyjnym Aspirator.

Wyświetlanie metryk w Grafanie z użyciem OpenTelemetry i Prometheus.

Aby zobaczyć, jak połączyć przykładową aplikację z rozwiązaniami Prometheus i Grafana, postępuj zgodnie z przewodnikiem w temacie Using OpenTelemetry with Prometheus, Grafana i Jaeger.

Aby obciążyć HttpClient wysyłając równoległe żądania do różnych punktów końcowych, rozszerz przykładową aplikację o następujący punkt końcowy:

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

Utwórz pulpit nawigacyjny narzędzia Grafana, wybierając ikonę + na górnym pasku narzędzi, a następnie wybierając pozycję Pulpit nawigacyjny. W wyświetlonym edytorze pulpitu nawigacyjnego wprowadź Open HTTP/1.1 Connections w polu Tytuł oraz następujące zapytanie w polu wyrażenia PromQL:

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

Wybierz pozycję Zastosuj , aby zapisać i wyświetlić nowy pulpit nawigacyjny. Wyświetla liczbę aktywnych i bezczynnych połączeń HTTP/1.1 w puli.

Połączenia HTTP/1.1 w narzędziu Grafana

Wzbogacenie

Wzbogacanie to dodawanie tagów niestandardowych (nazywanych również atrybutami lub etykietami) do metryki. Jest to przydatne, jeśli aplikacja chce dodać niestandardową kategoryzację do pulpitów nawigacyjnych lub alertów utworzonych za pomocą metryk. Instrument http.client.request.duration obsługuje wzbogacanie, rejestrując wywołania zwrotne w HttpMetricsEnrichmentContext. Należy pamiętać, że jest to interfejs API niskiego poziomu i wymagana jest oddzielna rejestracja wywołania zwrotnego dla każdego elementu HttpRequestMessage.

Prostym sposobem przeprowadzenia rejestracji wywołania zwrotnego w jednym miejscu jest zaimplementowanie niestandardowego DelegatingHandler. Dzięki temu można przechwycić i zmodyfikować żądania przed przekazaniem ich do wewnętrznego programu obsługi i wysłaniem ich do serwera:

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

Jeśli pracujesz z IHttpClientFactory, możesz użyć AddHttpMessageHandler, aby zarejestrować 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");

Uwaga / Notatka

Ze względów wydajnościowych wywołanie zwrotne wzbogacania jest inicjowane tylko wtedy, gdy http.client.request.duration instrument jest włączony, co oznacza, że powinno coś zbierać metryki. Może to być dotnet-monitor, eksporter Prometheus, MeterListener, lub MetricCollector<T>.

IMeterFactory i IHttpClientFactory integracja

Metryki HTTP zostały zaprojektowane z uwzględnieniem izolacji i możliwości testowania. Te aspekty są obsługiwane przez użycie klasy IMeterFactory, co umożliwia publikowanie metryk przez wystąpienie niestandardowe Meter w celu zachowania izolacji mierników od siebie. Domyślnie wartość globalna Meter służy do emitowania wszystkich metryk. To Meter wewnętrzne dla biblioteki System.Net.Http . Można zastąpić to zachowanie, przypisując niestandardowe wystąpienie IMeterFactory do SocketsHttpHandler.MeterFactory lub HttpClientHandler.MeterFactory.

Uwaga / Notatka

Meter.Name jest System.Net.Http dla wszystkich metryk emitowanych przez HttpClientHandler i SocketsHttpHandler.

Podczas pracy z Microsoft.Extensions.Http i IHttpClientFactory na .NET 8 lub nowszych, domyślna implementacja IHttpClientFactory automatycznie wybiera zarejestrowane wystąpienie IMeterFactory w IServiceCollection i przypisuje je do głównego handlera, który tworzy wewnątrz.

Uwaga / Notatka

Począwszy od platformy .NET 8, metoda AddHttpClient automatycznie wywołuje metodę AddMetrics, aby zainicjować usługi metryk i zarejestrować domyślną implementację IMeterFactory w ramach IServiceCollection. Domyślnie IMeterFactory buforuje Meter wystąpienia według nazwy, co oznacza, że istnieje jeden Meter z nazwą System.Net.Http na IServiceCollection.

Metryki testów

W poniższym przykładzie pokazano, jak zweryfikować wbudowane metryki w testach jednostkowych przy użyciu narzędzia xUnit, IHttpClientFactoryi MetricCollector<T> z Microsoft.Extensions.Diagnostics.Testing pakietu NuGet:

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

Metryki kontra EventCounters

Metryki są bardziej bogate w funkcje niż EventCounters, zwłaszcza ze względu na ich wielowymiarowy charakter. Ta wielowymiarowość umożliwia tworzenie zaawansowanych zapytań w narzędziach, takich jak Prometheus, i uzyskiwanie szczegółowych informacji na poziomie, który nie jest możliwy w przypadku usługi EventCounters.

Niemniej jednak od platformy .NET 8 tylko składniki System.Net.Http i System.Net.NameResolutions są instrumentowane przy użyciu metryk, co oznacza, że jeśli potrzebujesz liczników z niższych poziomów stosu, takich jak System.Net.Sockets lub System.Net.Security, należy użyć EventCounters.

Ponadto istnieją pewne semantyczne różnice między metrikami a odpowiadającymi im EventCounterami. Na przykład w przypadku korzystania z elementu HttpCompletionOption.ResponseContentReadelementcurrent-requests EventCounter uznaje żądanie za aktywne do momentu odczytania ostatniego bajtu treści żądania. Jego odpowiednik http.client.active_requests metryk nie zawiera czasu spędzonego na odczytywaniu treści odpowiedzi podczas liczenia aktywnych żądań.

Potrzebujesz więcej metryk?

Jeśli masz sugestie dotyczące innych przydatnych informacji, które mogą być udostępnione za pośrednictwem metryk, utwórz zgłoszenie w dotnet/runtime.