Partilhar via


Métricas de rede no .NET

Métricas são medidas numéricas reportadas ao longo do tempo. Eles geralmente 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 da .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 medições estão associadas a pares chave-valor chamados tags (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 internos, juntamente com seus atributos, consulte System.Net métricas.

Recolher métricas System.Net

Para aproveitar a instrumentação de métricas interna, 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 usando um exemplo simples e independente, consulte Coletar métricas com dotnet-counters.
  • Para coleta e monitoramento de métricas de de tempo de produção, você pode usar o Grafana com OpenTelemetry e Prometheus ou Azure Monitor Application Insights. No entanto, essas ferramentas podem ser inconvenientes de usar no momento do desenvolvimento devido à sua complexidade.
  • Para coleta de métricas de e solução de problemas em tempo de desenvolvimento, recomendamos o uso do .NET Aspire, que fornece 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 do Aspire , que é uma forma prática de introduzir as APIs de configuração de métricas e rastreio OpenTelemetry no seu projeto ASP.NET.

Colete métricas com dotnet-counters

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

Para este tutorial, crie uma aplicação que envie pedidos HTTP para vários endpoints em paralelo.

dotnet new console -o HelloBuiltinMetrics
cd ..\HelloBuiltinMetrics

Substitua o conteúdo do 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."); }
    });
}

Certifique-se de que dotnet-counters está instalado:

dotnet tool install --global dotnet-counters

Inicie o aplicativo HelloBuiltinMetrics.

dotnet run -c Release

Inicie dotnet-counters em uma janela de CLI separada e especifique o nome do processo e os medidores a serem observados e, em seguida, pressione uma tecla no aplicativo HelloBuiltinMetrics para que ele comece a enviar solicitações. Assim que as medições começam a chegar, o dotnet-counters atualiza continuamente a consola com os números mais recentes.

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

saída do dotnet-counters

Colete 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 o .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 é incorporada, usando as bibliotecas OpenTelemetry para .NET.

Os modelos de projeto padrão para o .NET Aspire contêm um projeto ServiceDefaults. Cada serviço na solução .NET Aspire tem uma referência ao projeto Service Defaults. Os serviços usam-no para instalar e configurar o OTel.

O modelo de projeto Service Defaults inclui os pacotes OTel SDK, ASP.NET, HttpClient e Runtime Instrumentation. Esses componentes de instrumentação são configurados no arquivo Extensions.cs. Para suportar a visualização por telemetria no Aspire Dashboard, o projeto Service Defaults também inclui o exportador OTLP por predefinição.

O Aspire Dashboard foi concebido para trazer a observação de telemetria para o ciclo de depuração local, o que permite aos programadores garantir que as aplicações estão a produzir telemetria. A visualização por telemetria também ajuda a diagnosticar esses aplicativos localmente. Ser capaz de observar as chamadas entre serviços é tão útil no momento da depuração 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 da linha de comando.

Passo a passo rápido

  1. Crie um do .NET Aspire 9 Starter App usando dotnet new:

    dotnet new aspire-starter-9 --output AspireDemo
    

    Ou no Visual Studio, crie um novo projeto e selecione o template .NET Aspire 9 Starter App:

    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() a subscrever os medidores 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. Isto deve iniciar o Painel de Controlo Aspire.

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

  5. Volte para o Dashboard, navegue até a página de Métricas e selecione o recurso webfrontend. Ao descer a página, você deve ser capaz de navegar pelas métricas incorporadas de System.Net.

    Métricas de Rede no Aspire Dashboard

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

Reutilizar projeto de Padrões de Serviço sem orquestração do .NET Aspire

O projeto Aspire Service Defaults fornece uma maneira fácil de configurar o OTel para projetos ASP.NET, mesmo que não use o resto do .NET Aspire como o AppHost para orquestração. O projeto Service Defaults está disponível como um modelo de projeto via Visual Studio ou dotnet new. Ele configura OTel e configura o exportador OTLP. Em seguida, você pode usar as variáveis de ambiente OTel para configurar o ponto de extremidade OTLP para enviar telemetria e fornecer as propriedades do recurso para o aplicativo.

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

  1. Adicione o ServiceDefaults projeto à 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 a partir do seu aplicativo ASP.NET. No Visual Studio, selecione Adicionar> de referência de projeto e selecione o ServiceDefaults projeto"

  3. Chame a função de configuração OpenTelemetry ConfigureOpenTelemetry() como parte da inicialização do construtor 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 OpenTelemetry com OTLP e o Painel de Instrumentos Aspire autónomo.

Veja 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 OpenTelemetry com Prometheus, Grafana e Jaeger.

Para sobrecarregar HttpClient enviando pedidos paralelos para vários endpoints, expanda a aplicação de exemplo com o seguinte endpoint:

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, após, selecionando Painel. No editor de painel exibido, digite Open HTTP/1.1 Connections 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 visualizar o novo painel. Ele exibe o número de conexões HTTP/1.1 ativas vs ociosas no pool.

Conexões HTTP/1.1 no Grafana

Enriquecimento

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

Uma maneira simples de fazer o registo de callback num único local é implementar um DelegatingHandlercustomizado. Isso permite intercetar e modificar as solicitações antes que elas sejam encaminhadas para o manipulador interno e enviadas para o 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 estiver a trabalhar com IHttpClientFactory, pode utilizar AddHttpMessageHandler para registar 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");

Observação

Por motivos de desempenho, a chamada de enriquecimento só é invocada quando o instrumento http.client.request.duration está ativado, o que indica que algum processo deve estar a recolher as métricas. Pode ser dotnet-monitor, exportador de Prometheus, um MeterListenerou um MetricCollector<T>.

IMeterFactory e IHttpClientFactory integração

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

Observação

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 seleciona automaticamente a instância de IMeterFactory registrada no IServiceCollection e a atribui ao manipulador primário que cria internamente.

Observação

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 armazenamento em cache padrão do IMeterFactory guarda instâncias Meter por nome, o que significa que existe um 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 vs. Contadores de Eventos

As métricas são mais ricas em funcionalidades do que os Contadores de Eventos, sobretudo devido à sua natureza multidimensional. Essa multidimensionalidade permite criar consultas sofisticadas em ferramentas como o Prometheus e obter insights em um nível que não é possível com o EventCounters.

No entanto, a partir do .NET 8, apenas 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, existem algumas diferenças semânticas entre Metrics 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. Sua métrica homóloga 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 de dotnet/runtime.