Compartilhar via


Métricas de rede no .NET

Métricas são medidas numéricas registradas ao longo do tempo. Normalmente, eles são usados para monitorar a integridade de um aplicativo e gerar alertas.

A partir do .NET 8, os componentes System.Net.Http e System.Net.NameResolution são instrumentados para publicar métricas usando a nova API System.Diagnostics.Metrics do .NET . Essas métricas foram projetadas em cooperação com OpenTelemetry para garantir que sejam consistentes com o padrão e funcionem bem com ferramentas populares como Prometheus e Grafana. Eles também são multidimensional, o que significa que as medidas estão associadas a pares chave-valor chamados marcas (também conhecidos como atributos ou rótulos). As tags permitem a categorização da medição para ajudar na análise.

Dica

Para obter uma lista abrangente de todos os instrumentos integrados, juntamente com seus atributos, consulte System.Net metrics.

Coletar métricas de System.Net

Para aproveitar a instrumentação de métricas internas, um aplicativo .NET precisa ser configurado para coletar essas métricas. Isso normalmente significa transformá-los para armazenamento e análise externos, por exemplo, em sistemas de monitoramento.

Há várias maneiras de coletar métricas de rede no .NET.

  • Para obter uma visão geral rápida utilizando um exemplo simples e autossuficiente, consulte Coletar métricas com dotnet-counters.
  • Para a coleta e monitoramento de métricas do tempo de produção, é possível utilizar o Grafana com OpenTelemetry e Prometheus ou o Azure Monitor Application Insights. No entanto, essas ferramentas podem ser inconvenientes para serem usadas em tempo de desenvolvimento devido à sua complexidade.
  • Para a coleta e resolução de problemas de métricas do tempo de desenvolvimento, recomendamos utilizar o .NET Aspire, que proporciona uma maneira simples, mas extensível, de iniciar métricas e rastreamento distribuído em seu aplicativo e diagnosticar problemas localmente.
  • Também é possível reutilizar o projeto Aspire Service Defaults sem a orquestração Aspire, o que é uma maneira prática de introduzir as APIs de configuração de rastreamento e métricas do OpenTelemetry no seu projeto de ASP.NET.

Coletar métricas com dotnet-counters

dotnet-counters é uma ferramenta de linha de comando multiplataforma para exame ad hoc de métricas do .NET e investigação de desempenho de primeiro nível.

Para os fins deste tutorial, crie um aplicativo que envia solicitações HTTP para vários endereços em paralelo.

dotnet new console -o HelloBuiltinMetrics
cd ..\HelloBuiltinMetrics

Substitua o conteúdo de Program.cs pelo seguinte código de exemplo:

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

Verifique se dotnet-counters está instalado:

dotnet tool install --global dotnet-counters

Inicie o aplicativo HelloBuiltinMetrics.

dotnet run -c Release

Inicie dotnet-counters em uma janela da CLI separada e especifique o nome do processo e os medidores a serem observados e pressione uma tecla no aplicativo HelloBuiltinMetrics para que ele comece a enviar solicitações. Assim que as medidas começarem a chegar, dotnet-counters atualizará continuamente o console com os números mais recentes.

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

saída do dotnet-counters

Coletar métricas com o .NET Aspire

Uma maneira simples de coletar rastreamentos e métricas em aplicativos ASP.NET é usar .NET Aspire. O .NET Aspire é um conjunto de extensões para .NET para facilitar a criação e o trabalho com aplicativos distribuídos. Um dos benefícios de usar o .NET Aspire é que a telemetria é interna, usando as bibliotecas OpenTelemetry para .NET.

Os modelos de projeto padrão do .NET Aspire contêm um projeto ServiceDefaults. Cada serviço na solução .NET Aspire tem uma referência ao projeto Padrões de Serviço. Os serviços o utilizam para definir e configurar o OTel.

O modelo de projeto de padrões de serviço inclui os pacotes de instrumentação OTel SDK, ASP.NET, HttpClient e Runtime. Esses componentes de instrumentação são configurados no arquivo Extensions.cs. Para dar suporte à visualização de telemetria no Painel do Aspire, o projeto Padrões de Serviço também inclui o exportador OTLP por padrão.

O Aspire Dashboard foi projetado para integrar a observação de telemetria ao ciclo de depuração local, permitindo que os desenvolvedores assegurem que os aplicativos estejam gerando telemetria. A visualização de telemetria também ajuda a diagnosticar esses aplicativos localmente. Ser capaz de observar as chamadas entre serviços é tão útil no momento do debug quanto na produção. O painel do .NET Aspire é iniciado automaticamente quando você F5 o projeto AppHost do Visual Studio ou dotnet run o projeto AppHost por linha de comando.

Passo a passo rápido

  1. Crie um .aplicativo de projeto inicial do .NET Aspire 9 utilizando dotnet new:

    dotnet new aspire-starter-9 --output AspireDemo
    

    Ou, no Visual Studio, crie um novo projeto e selecione o template de aplicativo inicial do .NET Aspire 9 :

    criar um aplicativo .NET Aspire 9 Starter no Visual Studio

  2. Abra Extensions.cs no projeto ServiceDefaults e role até o método ConfigureOpenTelemetry. Observe a chamada AddHttpClientInstrumentation() inscrevendo-se nos medidores de sistemas de rede.

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

    Observe que, no .NET 8+, AddHttpClientInstrumentation() pode ser substituído por assinaturas de medidores manuais.

    .WithMetrics(metrics =>
    {
        metrics.AddAspNetCoreInstrumentation()
            .AddMeter("System.Net.Http")
            .AddMeter("System.Net.NameResolution")
            .AddRuntimeInstrumentation();
    })
    
  3. Execute o projeto AppHost. Isso deve iniciar o painel do Aspire.

  4. Navegue até a página de previsão do tempo do aplicativo webfrontend, para gerar uma solicitação HttpClient destinada a apiservice. Atualize a página diversas vezes para enviar várias solicitações.

  5. Retorne ao Painel, navegue até a página de Métricas e selecione o recurso webfrontend. Ao rolar para baixo, deve ser possível procurar as métricas internas de System.Net.

    Métricas de sistemas de rede no painel do Aspire

Para obter mais informações sobre o .NET Aspire, consulte:

Reutilizar projeto de padrões de serviço sem a orquestração do .NET Aspire

O projeto de padrões de serviço do Aspire proporciona uma maneira fácil de configurar o OTel para projetos ASP.NET, mesmo sem que se esteja usando o restante do .NET Aspire, como o AppHost para orquestração. O projeto Padrões de Serviço está disponível como um modelo de projeto via Visual Studio ou dotnet new. Ele configura o OTel e define o exportador OTLP. Em seguida, você pode usar as variáveis de ambiente OTel para configurar o ponto de extremidade OTLP para o qual enviar telemetria e fornecer as propriedades de recurso para o aplicativo.

As etapas para usar ServiceDefaults fora do .NET Aspire são:

  1. Adicione o projeto ServiceDefaults à solução usando Adicionar Novo Projeto no Visual Studio ou use dotnet new:

    dotnet new aspire-servicedefaults --output ServiceDefaults
    
  2. Faça referência ao projeto ServiceDefaults do seu aplicativo ASP.NET. No Visual Studio, selecione Adicionar>Referência de Projeto e selecione o projeto ServiceDefaults."

  3. Chame a função de configuração do OpenTelemetry ConfigureOpenTelemetry() como parte da inicialização do configurador de aplicativos.

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

Para obter um passo a passo completo, consulte Exemplo: Usar o OpenTelemetry com OTLP e o painel autônomo do Aspire.

Exibir métricas no Grafana com OpenTelemetry e Prometheus

Para ver como conectar um aplicativo de exemplo com Prometheus e Grafana, siga o passo a passo em Usando o OpenTelemetry com Prometheus, Grafana e Jaeger.

Para enfatizar o HttpClient enviando solicitações paralelas para vários pontos de extremidade, amplie o aplicativo de exemplo com o seguinte ponto de extremidade:

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

Crie um painel do Grafana selecionando o ícone + na barra de ferramentas superior e selecionando Painel. No editor do painel exibido, insira Conexões Abrir Conexões HTTP/1.1 na caixa Título e a seguinte consulta no campo de expressão PromQL:

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

Selecione Aplicar para salvar e exibir o novo painel. Ele exibe o número de conexões HTTP/1.1 ativas versus ociosas no pool.

Conexões HTTP/1.1 no Grafana

Enriquecimento

Enriquecimento é a adição de marcas personalizadas (também conhecidas como atributos ou rótulos) a uma métrica. Isso será útil se um aplicativo quiser adicionar uma categorização personalizada a dashboards ou alertas criados com métricas. O instrumento http.client.request.duration dá suporte ao enriquecimento registrando retornos de chamada com o HttpMetricsEnrichmentContext. Observe que esta é uma API de baixo nível e um registro de callback separado é necessário para cada HttpRequestMessage.

Uma forma simples de fazer o registro de retorno de chamada em um único local é implementar um DelegatingHandler personalizado. Isso permite que você intercepte e modifique as solicitações antes que elas sejam encaminhadas para o manipulador interno e enviadas ao servidor:

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

Se você estiver trabalhando com IHttpClientFactory, poderá usar AddHttpMessageHandler para registrar o 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");

Nota

Por motivos de desempenho, o retorno de chamada do enriquecimento só é invocado quando o instrumento http.client.request.duration está habilitado, o que significa que algo deve estar coletando as métricas. Isso pode ser o dotnet-monitor, o exportador do Prometheus, um MeterListener ou um MetricCollector<T>.

Integração de IMeterFactory e IHttpClientFactory

As métricas HTTP foram projetadas com isolamento e capacidade de teste em mente. Esses aspectos são respaldados pelo uso de IMeterFactory, que permite a publicação de métricas por uma instância personalizada de Meter, a fim de manter os medidores isolados uns dos outros. Por padrão, um Meter global é utilizado para emitir todas as métricas. Esse Meter interno para a biblioteca de System.Net.Http. Esse comportamento pode ser substituído atribuindo uma instância de IMeterFactory personalizada a SocketsHttpHandler.MeterFactory ou HttpClientHandler.MeterFactory.

Nota

O Meter.Name é System.Net.Http para todas as métricas emitidas por HttpClientHandler e SocketsHttpHandler.

Ao trabalhar com Microsoft.Extensions.Http e IHttpClientFactory no .NET 8+, a implementação de IHttpClientFactory padrão escolhe automaticamente a instância de IMeterFactory registrada no IServiceCollection e a atribui ao manipulador primário que cria internamente.

Nota

A partir do .NET 8, o método AddHttpClient chama automaticamente AddMetrics para inicializar os serviços de métricas e registrar a implementação de IMeterFactory padrão com IServiceCollection. O IMeterFactory padrão armazena em cache Meter instâncias por nome, o que significa que existe uma Meter com o nome System.Net.Http por IServiceCollection.

Métricas de teste

O exemplo a seguir demonstra como validar métricas internas em testes de unidade usando xUnit, IHttpClientFactorye MetricCollector<T> do pacote 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"]);
        });
}

Métricas versus EventCounters

As métricas têm recursos mais avançados do que EventCounters, principalmente devido à sua natureza multidimensional. Essa multidimensionalidade permite criar consultas sofisticadas em ferramentas como o Prometheus e obter insights sobre um nível que não é possível com EventCounters.

No entanto, a partir do .NET 8, somente os componentes System.Net.Http e System.Net.NameResolutions são instrumentados usando métricas, o que significa que, se você precisar de contadores dos níveis inferiores da pilha, como System.Net.Sockets ou System.Net.Security, deverá usar EventCounters.

Além disso, há algumas diferenças semânticas entre Métricas e seus EventCounters correspondentes. Por exemplo, ao usar HttpCompletionOption.ResponseContentRead, o current-requests EventCounter considera uma solicitação ativa até o momento em que o último byte do corpo da solicitação foi lido. Seu equivalente de métricas http.client.active_requests não inclui o tempo gasto lendo o corpo da resposta ao contar as solicitações ativas.

Precisa de mais métricas?

Se você tiver sugestões para outras informações úteis que possam ser expostas por meio de métricas, crie um problema dotnet/runtime.