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
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
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 :
Abra
Extensions.cs
no projetoServiceDefaults
e role até o métodoConfigureOpenTelemetry
. Observe a chamadaAddHttpClientInstrumentation()
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(); })
Execute o projeto
AppHost
. Isso deve iniciar o painel do Aspire.Navegue até a página de previsão do tempo do aplicativo
webfrontend
, para gerar uma solicitaçãoHttpClient
destinada aapiservice
. Atualize a página diversas vezes para enviar várias solicitações.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 deSystem.Net
.
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:
Adicione o projeto ServiceDefaults à solução usando Adicionar Novo Projeto no Visual Studio ou use
dotnet new
:dotnet new aspire-servicedefaults --output ServiceDefaults
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."
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.
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, IHttpClientFactory
e 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.