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.

  • Aby zapoznać się z krótkim omówieniem przy użyciu prostego, samodzielnego przykładu, zobacz Zbieranie metryk za pomocą liczników dotnet-counter.
  • W przypadku zbierania i monitorowania metryk w czasie produkcyjnym można użyć narzędzia Grafana z usługami OpenTelemetry i Prometheus lub Azure Monitor Application Insights. Jednak te narzędzia mogą być niewygodne do użycia w czasie programowania ze względu na ich złożoność.
  • W przypadku zbierania metryk czasu programowania i rozwiązywania problemów zalecamy użycie metody Aspire, która zapewnia prosty, ale rozszerzalny sposób na rozpoczęcie metryk i rozproszone śledzenie w aplikacji oraz diagnozowanie problemów lokalnie.
  • Istnieje również możliwość ponownego wykorzystania projektu Service DefaultsAspire bez Aspire, co jest przydatnym sposobem wprowadzenia interfejsów API konfiguracji śledzenia i metryk OpenTelemetry do projektu ASP.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ą polecenia Aspire

Prostym sposobem zbierania śladów i metryk w aplikacjach ASP.NET jest użycie Aspire. Aspire jest zestawem rozszerzeń do .NET, aby ułatwić tworzenie i pracę z aplikacjami rozproszonymi. Jedną z zalet korzystania z Aspire jest to, że telemetria jest wbudowana przy użyciu bibliotek OpenTelemetry dla .NET.

Domyślne szablony projektów dla Aspire zawierają ServiceDefaults projekt. Każda usługa w rozwiązaniu 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 na Aspire pulpicie nawigacyjnym, projekt Service Defaults domyślnie zawiera również eksportera OTLP.

Aspire Pulpit nawigacyjny został zaprojektowany w celu umożliwienia obserwacji telemetrii w lokalnym cyklu debugowania, co umożliwia deweloperom zapewnienie, ż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. Konsola Aspire jest uruchamiana automatycznie po naciśnięciu F5 dla AppHost Projektu z Visual Studio lub dotnet run Projektu z wiersza polecenia.

Szybki przewodnik

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

    dotnet new aspire-starter-9 --output AspireDemo
    

    Lub w Visual Studio utwórz nowy projekt i wybierz szablon Aspire 9 Starter App:

    Utwórz aplikację startową Aspire 9 w 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 sieci na Aspire pulpicie nawigacyjnym

Aby uzyskać więcej informacji na temat funkcji Aspire, zobacz:

Użycie ponowne projektu Domyślne ustawienia serwisu bez Aspire orkiestracji

Projekt Aspire Service Defaults zapewnia łatwy sposób konfigurowania OTel dla projektów ASP.NET, nawet jeśli nie korzysta się z reszty Aspire, takich jak AppHost do 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 elementu ServiceDefaults poza programem Aspire to:

  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 OpenTelemetry z OTLP i samodzielnym Aspire pulpitem nawigacyjnym.

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.