Поделиться через


Сетевые метрики в .NET

Метрики — это числовые измерения, которые фиксируются на протяжении времени. Обычно они используются для мониторинга работоспособности приложения и создания оповещений.

Начиная с .NET 8, компоненты System.Net.Http и System.Net.NameResolution инструментируются для публикации метрик с использованием нового API System.Diagnostics.Metrics из .NET. Эти метрики были разработаны в сотрудничестве с OpenTelemetry, чтобы убедиться, что они соответствуют стандарту и хорошо работают с популярными инструментами, такими как Prometheus и Grafana. Они также многомерные, что означает, что измерения связаны с парами "ключ-значение", называемыми тегами (также известными как атрибуты или метки). Теги позволяют классифицировать измерение для анализа.

Совет

Для получения полного списка всех встроенных инструментов вместе с их атрибутами см. раздел метрик System.Net.

Сбор метрик System.Net

Чтобы воспользоваться преимуществами встроенного инструментария для метрик, необходимо настроить приложение .NET для сбора этих метрик. Обычно это означает преобразование их для внешнего хранилища и анализа, например в системы мониторинга.

Существует несколько способов сбора сетевых метрик в .NET.

  • Чтобы получить краткий обзор с использованием простого автономного примера, см. раздел "Сбор метрик с помощью dotnet-counters".
  • Для рабочей среды сбор и мониторинг метрик можно использовать Grafana с OpenTelemetry и Prometheus или Azure Monitor Application Insights. Однако эти средства могут быть неудобны для использования во время разработки из-за их сложности.
  • Для сбора метрик и устранения неполадок на этапе разработки рекомендуется использовать .NET Aspire, который обеспечивает простой, но расширяемый способ запуска метрик и распределенной трассировки в вашем приложении и диагностики проблем на локальном уровне.
  • Кроме того, можно повторно использовать проект по умолчанию службы Aspire без оркестрации Aspire, что является удобным способом внедрения API-интерфейсов трассировки и конфигурации метрик OpenTelemetry в ваш проект ASP.NET.

Сбор метрик с помощью утилиты dotnet-counters

dotnet-counters — это кроссплатформенное средство командной строки для нерегламентированного изучения метрик .NET и исследования производительности первого уровня.

Для этого руководства создайте приложение, которое отправляет HTTP-запросы в различные конечные точки параллельно.

dotnet new console -o HelloBuiltinMetrics
cd ..\HelloBuiltinMetrics

Замените содержимое Program.cs следующим примером кода:

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

Убедитесь, что установлен dotnet-counters:

dotnet tool install --global dotnet-counters

Запустите приложение HelloBuiltinMetrics.

dotnet run -c Release

Запустите dotnet-counters в отдельном окне ИНТЕРФЕЙСА командной строки и укажите имя процесса и метрики, а затем нажмите клавишу в приложении HelloBuiltinMetrics, чтобы начать отправку запросов. Как только измерения начинают поступать, dotnet-counters постоянно обновляет консоль с последними данными.

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

выход dotnet-counters

Сбор метрик с помощью .NET Aspire

Простой способ сбора трассировок и метрик в приложениях ASP.NET — использовать .NET Aspire. .NET Aspire — это набор расширений для .NET, чтобы упростить создание и работу с распределенными приложениями. Одним из преимуществ использования .NET Aspire является встроенная телеметрия с помощью библиотек OpenTelemetry для .NET.

Шаблоны проектов по умолчанию для .NET Aspire содержат проект ServiceDefaults. Каждая служба в решении .NET Aspire содержит ссылку на проект Service Defaults. Службы используют его для установки и настройки OTel.

Шаблон проекта Service Defaults включает пакеты OTel SDK, ASP.NET, HttpClient и пакеты инструментирования среды выполнения. Эти компоненты инструментирования настраиваются в файле Extensions.cs. Для поддержки визуализации телеметрии в Aspire Dashboard проект Service Defaults также включает экспортер OTLP по умолчанию.

Панель управления Aspire предназначена для внедрения наблюдения телеметрии в локальный цикл отладки, что позволяет разработчикам удостовериться, что приложения генерируют данные телеметрии. Визуализация телеметрии также помогает диагностировать эти приложения локально. Возможность наблюдать за вызовами между службами очень полезна во время отладки, как и в рабочей среде. Панель управления .NET Aspire запускается автоматически при F5 проекте AppHost из Visual Studio или dotnet run проекте AppHost из командной строки.

Краткое пошаговое руководство

  1. Создайте .NET Aspire 9 Starter App с помощью dotnet new:

    dotnet new aspire-starter-9 --output AspireDemo
    

    Или в Visual Studio создайте проект и выберите шаблон .NET Aspire 9 Starter App:

    создание начального приложения .NET Aspire 9 в Visual Studio

  2. Откройте Extensions.cs в проекте ServiceDefaults и прокрутите страницу до метода ConfigureOpenTelemetry. Обратите внимание на вызов AddHttpClientInstrumentation(), который выполняет подписку на сетевые счетчики.

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

    Обратите внимание, что в .NET 8+ AddHttpClientInstrumentation() можно заменить ручными подписками на метрики:

    .WithMetrics(metrics =>
    {
        metrics.AddAspNetCoreInstrumentation()
            .AddMeter("System.Net.Http")
            .AddMeter("System.Net.NameResolution")
            .AddRuntimeInstrumentation();
    })
    
  3. Запустите проект AppHost. Это должно запустить панель мониторинга Aspire.

  4. Перейдите на страницу «Погода» в приложении webfrontend, чтобы направить запрос HttpClient к apiservice. Обновите страницу несколько раз, чтобы отправить несколько запросов.

  5. Вернитесь на панель мониторинга, перейдите на страницу метрик и выберите ресурс webfrontend. Прокручивая вниз, вы сможете просматривать встроенные метрики System.Net.

    сетевые метрики на панели мониторинга Aspire

Дополнительные сведения о .NET Aspire см. в следующем разделе:

Повторное использование проекта Service Defaults без оркестрации .NET Aspire

Проект Aspire Service Defaults предоставляет простой способ настройки OTel для проектов ASP.NET, даже если не использует остальную часть .NET Aspire, например AppHost для оркестрации. Проект Service Defaults доступен в виде шаблона проекта через Visual Studio или dotnet new. Он настраивает OTel и устанавливает экспортер OTLP. Затем можно использовать переменные среды OTel для настройки конечной точки OTLP для отправки данных телеметрии и предоставления свойств ресурсов для приложения.

Ниже приведены шаги по использованию ServiceDefaults за пределами .NET Aspire:

  1. Добавьте проект ServiceDefaults в решение с помощью добавления нового проекта в Visual Studio или используйте dotnet new:

    dotnet new aspire-servicedefaults --output ServiceDefaults
    
  2. Используйте проект ServiceDefaults в вашем приложении ASP.NET. В Visual Studio выберите Добавить>ссылка на проект, затем выберите проект ServiceDefaults.

  3. Вызовите функцию установки OpenTelemetry ConfigureOpenTelemetry() в рамках инициализации построителя приложений.

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

Полное пошаговое руководство см. в разделе Пример. Использование OpenTelemetry с OTLP и автономной панелью мониторинга Aspire.

Просмотр метрик в Grafana с OpenTelemetry и Prometheus

Чтобы узнать, как подключить пример приложения с Prometheus и Grafana, выполните пошаговое руководство в по использованию OpenTelemetry с Prometheus, Grafana и Jaeger.

Чтобы подчеркнуть HttpClient путем отправки параллельных запросов в различные конечные точки, расширьте пример приложения со следующей конечной точкой:

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

Создайте панель мониторинга Grafana, сначала выбрав значок + на верхней панели инструментов, затем выберите Панель мониторинга. В появившемся редакторе панели мониторинга введите Открытые подключения HTTP/1.1 в поле заголовка и следующий запрос в поле выражения PromQL:

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

Выберите Применить, чтобы сохранить и просмотреть новую панель мониторинга. В нем отображается количество активных и неактивных подключений HTTP/1.1 в пуле.

подключения HTTP/1.1 в Grafana

Обогащение

обогащение — это добавление пользовательских тегов (также известных как атрибуты или метки) к метрике. Это полезно, если приложение хочет добавить настраиваемую категорию на панели мониторинга или оповещения, созданные с помощью метрик. Инструмент http.client.request.duration поддерживает обогащение путем регистрации обратных вызовов с помощью HttpMetricsEnrichmentContext. Обратите внимание, что это низкоуровневый API, а для каждой HttpRequestMessageтребуется отдельная регистрация обратного вызова.

Простой способ выполнить регистрацию обратного вызова в одном месте — реализовать собственную DelegatingHandler. Это позволяет перехватывать и изменять запросы, прежде чем они перенаправляются во внутренний обработчик и отправляются на сервер:

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

Если вы работаете с IHttpClientFactory, вы можете использовать AddHttpMessageHandler для регистрации 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");

Заметка

По соображениям производительности обратный вызов обогащения вызывается только при включенном инструменте http.client.request.duration, то есть что-то должно собирать метрики. Это может быть dotnet-monitor, экспортер Prometheus, MeterListenerили MetricCollector<T>.

интеграция IMeterFactory и IHttpClientFactory

Метрики HTTP были разработаны с учетом изоляции и тестирования. Эти аспекты поддерживаются использованием IMeterFactory, что позволяет публиковать метрики пользовательским Meter экземпляром, чтобы обеспечить изоляцию метрик друг от друга. По умолчанию глобальный Meter используется для выдачи всех метрик. Это Meter, находящееся внутри библиотеки System.Net.Http. Это поведение можно переопределить, назначив настраиваемый экземпляр IMeterFactorySocketsHttpHandler.MeterFactory или HttpClientHandler.MeterFactory.

Заметка

Meter.Name является System.Net.Http для всех метрик, передаваемых HttpClientHandler и SocketsHttpHandler.

При работе с Microsoft.Extensions.Http и IHttpClientFactory в .NET 8+ реализация IHttpClientFactory по умолчанию автоматически выбирает экземпляр IMeterFactory, зарегистрированный в IServiceCollection, и назначает его основному обработчику, который он создает внутренне.

Заметка

Начиная с .NET 8 метод AddHttpClient автоматически вызывает AddMetrics для инициализации служб метрик и регистрации реализации IMeterFactory по умолчанию с помощью IServiceCollection. IMeterFactory по умолчанию кэширует Meter экземпляры по имени, это означает, что есть один Meter, с именем System.Net.Http, на IServiceCollection.

Тестовые метрики

В следующем примере показано, как проверить встроенные метрики в модульных тестах с помощью xUnit, IHttpClientFactoryи MetricCollector<T> из пакета 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"]);
        });
}

Метрики против счетчиков событий

Метрики более функционально насыщенные, чем EventCounters, особенно благодаря их многомерной природе. Эта многомерность позволяет создавать сложные запросы в таких инструментах, как Prometheus, и получать аналитические сведения на уровне, который невозможно использовать в EventCounters.

Тем не менее, по состоянию на .NET 8 только System.Net.Http и компоненты System.Net.NameResolutions инструментируются с помощью метрик, то есть если вам нужны счетчики из более низких уровней стека, например System.Net.Sockets или System.Net.Security, необходимо использовать EventCounters.

Кроме того, существуют некоторые семантические различия между метриками и соответствующими значениями EventCounters. Например, при использовании HttpCompletionOption.ResponseContentReadcurrent-requests EventCounter считает запрос активным до момента чтения последнего байта текста запроса. Его аналог метрик http.client.active_requests не включает время, затраченное на чтение текста ответа при подсчете активных запросов.

Требуются дополнительные метрики?

Если у вас есть предложения по другим полезным сведениям, которые можно предоставить с помощью метрик, создайте задачу в dotnet/runtime .