Compartilhar via


Métricas de rede no .NET

As métricas são medidas numéricas relatadas ao longo do tempo. Normalmente, elas são usadas 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 o OpenTelemetry para garantir que sejam consistentes com o padrão e funcionem bem com ferramentas populares como Prometheus e Grafana. Elas também são multidimensionais, o que significa que as medidas são associadas a pares chave-valor chamados marcas (também conhecidos como atributos ou rótulos) que permitem que os dados sejam categorizados para análise.

Dica

Para obter uma lista abrangente de todos os instrumentos internos, juntamente com seus atributos, confira Métricas do System.Net.

Coletar métricas do System.Net

Há duas partes para usar métricas em um aplicativo .NET:

  • Instrumentação: O código em bibliotecas do .NET faz medições e associa essas medidas a um nome de métrica. O .NET e o ASP.NET Core incluem muitas métricas internas.
  • Coleção: um aplicativo .NET configura as métricas nomeadas para serem transmitidas do aplicativo para armazenamento externo e análise. Algumas ferramentas podem executar a configuração fora do aplicativo usando arquivos de configuração ou uma ferramenta de interface do usuário.

Esta seção demonstra vários métodos para coletar e exibir métricas do System.Net.

Aplicativo de exemplo

Para este tutorial, crie um aplicativo simples que envia solicitações HTTP para vários pontos de extremidade em paralelo.

dotnet new console -o HelloBuiltinMetrics
cd ..\HelloBuiltinMetrics

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

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

Exibir métricas com dotnet-counters

O dotnet-counters é uma ferramenta de monitoramento multiplataforma de desempenho para monitoramento de integridade de primeiro nível e investigação de desempenho.

dotnet tool install --global dotnet-counters

Ao executar em um processo do .NET 8+, o dotnet-counters habilita os instrumentos definidos pelo argumento --counters e exibe as medidas. Ele atualiza continuamente o console com os números mais recentes:

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

Preencher métricas no Grafana com OpenTelemetry e Prometheus.

Visão geral

OpenTelemetry:

  • É um projeto de software livre sem relação com fornecedor compatível com a Cloud Native Computing Foundation.
  • Padroniza a geração e coleta de telemetria para software nativo de nuvem.
  • Funciona com o .NET usando as APIs de métrica do .NET.
  • É endossado pelo Azure Monitor e por muitos fornecedores do APM.

Este tutorial mostra uma das integrações disponíveis para métricas OpenTelemetry usando os projetos do Prometheus e do Grafana. O fluxo de dados das métricas consiste nas seguintes etapas:

  1. As APIs de métrica do .NET registram medidas do aplicativo de exemplo.

  2. A biblioteca OpenTelemetry em execução no aplicativo agrega as medidas.

  3. A biblioteca do exportador prometheus disponibiliza os dados agregados por meio de um ponto de extremidade de métricas HTTP. 'Exportador' é o que o OpenTelemetry chama as bibliotecas que transmitem telemetria para back-ends específicos do fornecedor.

  4. Um servidor Prometheus:

    • Sonda o ponto de extremidade de métricas.
    • Lê os dados.
    • Armazena os dados em um banco de dados para persistência de longo prazo. Prometheus refere-se à leitura e armazenamento de dados como extração de um ponto de extremidade.
    • Pode ser executado em um computador diferente.
  5. O servidor Grafana:

    • Consulta os dados armazenados no Prometheus e os exibe em um painel de monitoramento baseado na Web.
    • Pode ser executado em um computador diferente.

Configurar o aplicativo de exemplo para usar o exportador Prometheus da OpenTelemetry

Adicione uma referência ao exportador do OpenTelemetry Prometheus ao aplicativo de exemplo:

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

Observação

Este tutorial usa um build de pré-lançamento do suporte do Prometheus do OpenTelemetry disponível no momento em que este artigo foi escrito.

Atualizar Program.cs com a configuração do 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.");
    });
}

No código anterior:

  • O AddMeter("System.Net.Http", "System.Net.NameResolution") configura o OpenTelemetry para transmitir todas as métricas coletadas pelos medidores System.Net.Http e System.Net.NameResolution.
  • O AddPrometheusHttpListener configura o OpenTelemetry para expor o ponto de extremidade HTTP de métricas do Prometheus na porta 9184.

Observação

Essa configuração é diferente para aplicativos ASP.NET Core, em que as métricas são exportadas com OpenTelemetry.Exporter.Prometheus.AspNetCore em vez de HttpListener. Consulte o exemplo relacionado do ASP.NET Core.

Execute o aplicativo e deixe-o em execução para que as medidas possam ser coletadas:

dotnet run

Instalar e configurar o Prometheus

Siga as primeiras etapas do Prometheus para configurar seu servidor Prometheus e confirmar que ele está funcionando.

Modifique o arquivo de configuração prometheus.yml para que o Prometheus extraia o ponto de extremidade de métricas que nosso aplicativo de exemplo está expondo. Adicione o seguinte texto realçado na seção scrape_configs:

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

Iniciar o Prometheus

  1. Recarregue a configuração ou reinicie o servidor Prometheus.

  2. Confirme se o OpenTelemetryTest está no estado UP na página Status>Destinos do portal da Web Prometheus. Prometheus status

  3. Na página Grafo do portal da Web do Prometheus, insira http na caixa de texto de expressão e selecione http_client_active_requests. http_client_active_requests Na guia grafo, o Prometheus mostra o valor do contador http.client.active_requests emitido pelo aplicativo de exemplo. Prometheus active requests graph

Mostrar métricas em um painel do Grafana

  1. Siga as instruções padrão para instalar o Grafana e conectá-lo a uma fonte de dados do Prometheus.

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

Grafana HTTP/1.1 Connections

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

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 desejar adicionar uma categorização personalizada a painéis 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 retorno de chamada 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 permitirá 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 o IHttpClientFactory, poderá usar o 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");

Observação

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 compatíveis com o uso de IMeterFactory, que permite a publicação de métricas por uma instância personalizada do Meter, a fim de manter os Medidores isolados uns dos outros. Por padrão, todas as métricas são emitidas por um Meter interno global para a biblioteca System.Net.Http. Esse comportamento pode ser substituído atribuindo uma instância personalizada IMeterFactory 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 padrão IHttpClientFactory escolhe automaticamente a instância IMeterFactory registrada no IServiceCollection e a atribui ao manipulador primário que ela cria internamente.

Observação

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

Métricas de teste

O exemplo a seguir demonstra como validar métricas internas em testes de unidade usando xUnit, IHttpClientFactory e MetricCollector<T> no 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 em 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 EventCounter current-requests considera uma solicitação como ativa até o momento em que o último byte do corpo da solicitação foi lido. A métrica http.client.active_requests equivalente 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.