Udostępnij za pośrednictwem


Metryki sieci na platformie .NET

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

Począwszy od platformy .NET 8, składniki System.Net.Http i System.Net.NameResolution są instrumentowane do publikowania metryk przy użyciu polecenia . Nowy interfejs 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 (atrybutami lub etykietami), które umożliwiają kategoryzowanie danych na potrzeby analizy.

Napiwek

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

Zbieranie metryk System.Net

Istnieją dwie części używania metryk w aplikacji platformy .NET:

  • Instrumentacja: Kod w bibliotekach platformy .NET wykonuje pomiary i kojarzy te miary z nazwą metryki. Platforma .NET i ASP.NET Core zawierają wiele wbudowanych metryk.
  • Kolekcja: aplikacja platformy .NET konfiguruje nazwane metryki do przesyłania z aplikacji na potrzeby magazynu zewnętrznego i analizy. Niektóre narzędzia mogą wykonywać konfigurację poza aplikacją przy użyciu plików konfiguracji lub narzędzia interfejsu użytkownika.

W tej sekcji przedstawiono różne metody zbierania i wyświetlania System.Net metryk.

Przykładowa aplikacja

Na potrzeby tego samouczka utwórz prostą 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)];
        byte[] bytes = await client.GetByteArrayAsync(uri, ct);
        await Console.Out.WriteLineAsync($"{uri} - received {bytes.Length} bytes.");
    });
}

Wyświetlanie metryk za pomocą liczników dotnet-counter

dotnet-counters to międzyplatformowe narzędzie do monitorowania wydajności na potrzeby monitorowania kondycji ad hoc i badania wydajności pierwszego poziomu.

dotnet tool install --global dotnet-counters

W przypadku uruchamiania względem procesu dotnet-counters .NET 8+ włącza instrumenty zdefiniowane przez --counters argument i wyświetla pomiary. Stale odświeża konsolę przy użyciu najnowszych numerów:

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

Wyświetlanie metryk w narzędziu Grafana przy użyciu rozwiązań OpenTelemetry i Prometheus

Omówienie

OpenTelemetry:

  • Jest neutralnym dla dostawcy projektem open source obsługiwanym przez Cloud Native Computing Foundation.
  • Standaryzacja generowania i zbierania danych telemetrycznych dla oprogramowania natywnego dla chmury.
  • Współpracuje z platformą .NET przy użyciu interfejsów API metryk platformy .NET.
  • Jest zatwierdzony przez usługę Azure Monitor i wielu dostawców APM.

W tym samouczku przedstawiono jedną z integracji dostępnych dla metryk OpenTelemetry przy użyciu projektów Prometheus i Grafana systemu operacyjnego. Przepływ danych metryk składa się z następujących kroków:

  1. Interfejsy API metryk platformy .NET rejestrują pomiary z przykładowej aplikacji.

  2. Biblioteka OpenTelemetry uruchomiona w aplikacji agreguje miary.

  3. Biblioteka eksportera Prometheus udostępnia zagregowane dane za pośrednictwem punktu końcowego metryk HTTP. Funkcja "Eksporter" wywołuje biblioteki, które przesyłają dane telemetryczne do zapleczy specyficznych dla dostawcy.

  4. Serwer Prometheus:

    • Sonduje punkt końcowy metryk.
    • Odczytuje dane.
    • Przechowuje dane w bazie danych pod kątem trwałości długoterminowej. Prometheus odnosi się do odczytywania i przechowywania danych jako złomowania punktu końcowego.
    • Można uruchomić na innej maszynie.
  5. Serwer Grafana:

    • Wykonuje zapytanie dotyczące danych przechowywanych w rozwiązaniu Prometheus i wyświetla je na internetowym pulpicie nawigacyjnym monitorowania.
    • Można uruchomić na innej maszynie.

Konfigurowanie przykładowej aplikacji do korzystania z eksportera Prometheus firmy OpenTelemetry

Dodaj odwołanie do eksportera OpenTelemetry Prometheus do przykładowej aplikacji:

dotnet add package OpenTelemetry.Exporter.Prometheus.HttpListener --prerelease

Uwaga

W tym samouczku jest używana kompilacja wstępna obsługi rozwiązania Prometheus firmy OpenTelemetry dostępna w momencie pisania tekstu.

Zaktualizuj Program.cs za pomocą konfiguracji 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.");
    });
}

Powyższy kod:

  • AddMeter("System.Net.Http", "System.Net.NameResolution") Konfiguruje bibliotekę OpenTelemetry w celu przesyłania wszystkich metryk zebranych przez wbudowane System.Net.Http i System.Net.NameResolution mierniki.
  • AddPrometheusHttpListener Konfiguruje bibliotekę OpenTelemetry w celu uwidocznienia punktu końcowego HTTP metryk rozwiązania Prometheus na porcie 9184.

Uwaga

Ta konfiguracja różni się w przypadku aplikacji ASP.NET Core, w których metryki są eksportowane za pomocą OpenTelemetry.Exporter.Prometheus.AspNetCore polecenia HttpListenerzamiast . Zobacz powiązany przykład ASP.NET Core.

Uruchom aplikację i pozostaw ją uruchomioną, aby można było zbierać pomiary:

dotnet run

Konfigurowanie i konfigurowanie rozwiązania Prometheus

Wykonaj pierwsze kroki rozwiązania Prometheus, aby skonfigurować serwer Prometheus i upewnij się, że działa.

Zmodyfikuj plik konfiguracji prometheus.yml , tak aby rozwiązanie Prometheus zeskrobało punkt końcowy metryk, na który uwidacznia przykładowa aplikacja. Dodaj następujący wyróżniony tekst w scrape_configs sekcji:

# 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']

Rozpocznij prometheus

  1. Załaduj ponownie konfigurację lub uruchom ponownie serwer Prometheus.

  2. Upewnij się, że plik OpenTelemetryTest jest w stanie UP na stronie Cele stanu>w portalu internetowym Prometheus. Prometheus status

  3. Na stronie Graf portalu internetowego Prometheus wprowadź http w polu tekstowym wyrażenie i wybierz pozycję http_client_active_requests. http_client_active_requests Na karcie grafu prometheus pokazuje wartość http.client.active_requests licznika emitowanego przez przykładowej aplikacji. Prometheus active requests graph

Wyświetlanie metryk na pulpicie nawigacyjnym narzędzia Grafana

  1. Postępuj zgodnie ze standardowymi instrukcjami , aby zainstalować aplikację Grafana i połączyć ją ze źródłem danych Prometheus.

  2. 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ź ciąg Otwórz Połączenie ions HTTP/1.1 w polu Tytuł i następujące zapytanie w polu Wyrażenia PromQL:

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

Grafana HTTP/1.1 Connections

  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.

Wzbogacenie

Wzbogacanie to dodawanie tagów niestandardowych (atrybutów lub etykiet) do metryki. Jest to przydatne, jeśli aplikacja chce dodać niestandardową kategoryzację do pulpitów nawigacyjnych lub alertów utworzonych za pomocą metryk. http.client.request.duration Instrument obsługuje wzbogacanie przez zarejestrowanie wywołań zwrotnych w obiekcie HttpMetricsEnrichmentContext. Należy pamiętać, że jest to interfejs API niskiego poziomu i wymagana jest oddzielna rejestracja wywołania zwrotnego dla każdego HttpRequestMessageelementu .

Prostym sposobem przeprowadzenia rejestracji wywołania zwrotnego w jednym miejscu jest zaimplementowanie niestandardowego DelegatingHandlerobiektu . Umożliwi to przechwycenie i zmodyfikowanie żądań przed przekazaniem ich do programu obsługi wewnętrznej 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 usługą IHttpClientFactory, możesz użyć AddHttpMessageHandler polecenia , aby zarejestrować element 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

Ze względu na wydajność wywołanie zwrotne wzbogacania jest wywoływane tylko wtedy, gdy http.client.request.duration instrument jest włączony, co oznacza, że coś powinno 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 wszystkie metryki są emitowane przez globalny Meter element wewnętrzny do System.Net.Http biblioteki. To zachowanie może zostać zastąpione przez przypisanie wystąpienia niestandardowego IMeterFactory do SocketsHttpHandler.MeterFactory lub HttpClientHandler.MeterFactory.

Uwaga

Parametr Meter.Name dotyczy System.Net.Http wszystkich metryk emitowanych przez HttpClientHandler metryki i SocketsHttpHandler.

Podczas pracy z platformą Microsoft.Extensions.HttpIHttpClientFactory .NET 8 lub nowszym domyślna IHttpClientFactory implementacja automatycznie wybiera IMeterFactory wystąpienie zarejestrowane w IServiceCollection obiekcie i przypisuje je do podstawowej procedury obsługi, która tworzy wewnętrznie.

Uwaga

Począwszy od platformy .NET 8, AddHttpClient metoda automatycznie wywołuje AddMetrics metodę w celu zainicjowania usług metryk i zarejestrowania domyślnej IMeterFactory implementacji za pomocą polecenia IServiceCollection. Domyślne IMeterFactory buforuje Meter wystąpienia według nazwy, co oznacza, że będzie 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 a 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 System.Net.Http składniki 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ć funkcji EventCounters.

Ponadto istnieją pewne semantyczne różnice między metrykami a ich pasującymi elementami EventCounters. Na przykład w przypadku korzystania z elementu HttpCompletionOption.ResponseContentReadelement current-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ć uwidocznione za pośrednictwem metryk, utwórz problem z dotnet/runtime.