Coletar métricas
Este artigo aplica-se a: ✔️ .NET Core 3.1 e posterior .NET Framework 4.6.1 e posterior ✔️
O código instrumentado pode registrar medições numéricas, mas as medições geralmente precisam ser agregadas, transmitidas e armazenadas para criar métricas úteis para monitoramento. O processo de agregação, transmissão e armazenamento de dados é chamado de coleta. Este tutorial mostra vários exemplos de coleta de métricas:
- Preenchimento de métricas no Grafana com OpenTelemetry e Prometheus.
- Visualizando métricas em tempo real com
dotnet-counters
- Criando uma ferramenta de coleta personalizada usando a API .NET MeterListener subjacente.
Para obter mais informações sobre instrumentação e opções de métricas personalizadas, consulte Comparar APIs de métricas.
Pré-requisitos
- SDK do .NET Core 3.1 ou posterior
Criar um aplicativo de exemplo
Antes que as métricas possam ser coletadas, as medições devem ser produzidas. Este tutorial cria um aplicativo que tem instrumentação métrica básica. O tempo de execução do .NET também tem várias métricas internas. Para obter mais informações sobre como criar novas métricas usando a System.Diagnostics.Metrics.Meter API, consulte o tutorial de instrumentação.
dotnet new console -o metric-instr
cd metric-instr
dotnet add package System.Diagnostics.DiagnosticSource
Substitua o conteúdo do Program.cs
pelo seguinte código:
using System.Diagnostics.Metrics;
class Program
{
static Meter s_meter = new("HatCo.HatStore", "1.0.0");
static Counter<int> s_hatsSold = s_meter.CreateCounter<int>("hats-sold");
static void Main(string[] args)
{
var rand = Random.Shared;
Console.WriteLine("Press any key to exit");
while (!Console.KeyAvailable)
{
//// Simulate hat selling transactions.
Thread.Sleep(rand.Next(100, 2500));
s_hatsSold.Add(rand.Next(0, 1000));
}
}
}
O código anterior simula a venda de chapéus em intervalos aleatórios e tempos aleatórios.
Veja métricas com contadores de pontos
dotnet-counters é uma ferramenta de linha de comando que pode exibir métricas em tempo real para aplicativos .NET Core sob demanda. Ele não requer configuração, tornando-o útil para investigações ad hoc ou verificação de que a instrumentação métrica está funcionando. Ele funciona com System.Diagnostics.Metrics APIs baseadas e EventCounters.
Se a ferramenta dotnet-counters não estiver instalada, execute o seguinte comando:
dotnet tool update -g dotnet-counters
Enquanto o aplicativo de exemplo estiver em execução, inicie contadores de pontos. O comando a seguir mostra um exemplo de monitoramento de dotnet-counters
todas as métricas do HatCo.HatStore
medidor. O nome do medidor diferencia maiúsculas de minúsculas. Nosso aplicativo de exemplo foi metric-instr.exe, substitua isso pelo nome do seu aplicativo de exemplo.
dotnet-counters monitor -n metric-instr HatCo.HatStore
É apresentado um resultado semelhante ao seguinte:
Press p to pause, r to resume, q to quit.
Status: Running
[HatCo.HatStore]
hats-sold (Count / 1 sec) 4
dotnet-counters
pode ser executado com um conjunto diferente de métricas para ver algumas das instrumentações internas do tempo de execução do .NET:
dotnet-counters monitor -n metric-instr
É apresentado um resultado semelhante ao seguinte:
Press p to pause, r to resume, q to quit.
Status: Running
[System.Runtime]
% Time in GC since last GC (%) 0
Allocation Rate (B / 1 sec) 8,168
CPU Usage (%) 0
Exception Count (Count / 1 sec) 0
GC Heap Size (MB) 2
Gen 0 GC Count (Count / 1 sec) 0
Gen 0 Size (B) 2,216,256
Gen 1 GC Count (Count / 1 sec) 0
Gen 1 Size (B) 423,392
Gen 2 GC Count (Count / 1 sec) 0
Gen 2 Size (B) 203,248
LOH Size (B) 933,216
Monitor Lock Contention Count (Count / 1 sec) 0
Number of Active Timers 1
Number of Assemblies Loaded 39
ThreadPool Completed Work Item Count (Count / 1 sec) 0
ThreadPool Queue Length 0
ThreadPool Thread Count 3
Working Set (MB) 30
Para obter mais informações, consulte dotnet-counters. Para saber mais sobre métricas no .NET, consulte Métricas internas.
Veja métricas no Grafana com OpenTelemetry e Prometheus
Descrição geral
- É um projeto de código aberto neutro do fornecedor suportado pela Cloud Native Computing Foundation.
- Padroniza a geração e coleta de telemetria para software nativo da nuvem.
- Funciona com .NET usando as APIs de métrica .NET.
- É endossado pelo Azure Monitor e por muitos fornecedores de APM.
Este tutorial mostra uma das integrações disponíveis para métricas OpenTelemetry usando os projetos OSS Prometheus e Grafana . O fluxo de dados das métricas:
As APIs de métrica .NET registram medições do aplicativo de exemplo.
A biblioteca OpenTelemetry em execução no aplicativo agrega as medidas.
A biblioteca de exportadores Prometheus disponibiliza os dados agregados por meio de um ponto de extremidade de métricas HTTP. 'Exportador' é o que a OpenTelemetry chama de bibliotecas que transmitem telemetria para back-ends específicos do fornecedor.
Um servidor Prometheus:
- Sonda o ponto de extremidade das 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 raspagem de um ponto de extremidade.
- Pode ser executado em uma máquina diferente
O servidor Grafana:
- Consulta os dados armazenados no Prometheus e os exibe em um painel de monitoramento baseado na Web.
- Pode ser executado em uma máquina diferente.
Configurar o aplicativo de exemplo para usar o exportador Prometheus da OpenTelemetry
Adicione uma referência ao exportador OpenTelemetry Prometheus ao aplicativo de exemplo:
dotnet add package OpenTelemetry.Exporter.Prometheus.HttpListener --prerelease
Nota
Este tutorial usa uma compilação de pré-lançamento do suporte Prometheus da OpenTelemetry, disponível no momento da escrita.
Atualize Program.cs
com a configuração OpenTelemetry :
using OpenTelemetry;
using OpenTelemetry.Metrics;
using System.Diagnostics.Metrics;
class Program
{
static Meter s_meter = new("HatCo.HatStore", "1.0.0");
static Counter<int> s_hatsSold = s_meter.CreateCounter<int>(
name: "hats-sold",
unit: "Hats",
description: "The number of hats sold in our store");
static void Main(string[] args)
{
using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder()
.AddMeter("HatCo.HatStore")
.AddPrometheusHttpListener(options => options.UriPrefixes = new string[] { "http://localhost:9184/" })
.Build();
var rand = Random.Shared;
Console.WriteLine("Press any key to exit");
while (!Console.KeyAvailable)
{
//// Simulate hat selling transactions.
Thread.Sleep(rand.Next(100, 2500));
s_hatsSold.Add(rand.Next(0,1000));
}
}
}
No código anterior:
AddMeter("HatCo.HatStore")
configura o OpenTelemetry para transmitir todas as métricas coletadas pelo Medidor definido no aplicativo.AddPrometheusHttpListener
configura o OpenTelemetry para:- Exponha o ponto de extremidade de métricas do Prometheus na porta
9184
- Use o HttpListener.
- Exponha o ponto de extremidade de métricas do Prometheus na porta
Consulte a documentação do OpenTelemetry para obter mais informações sobre as opções de configuração do OpenTelemetry . A documentação do OpenTelemetry mostra opções de hospedagem para aplicativos ASP.NET.
Execute o aplicativo e deixe-o em execução para que as medições possam ser coletadas:
dotnet run
Instalar e configurar o Prometheus
Siga os primeiros passos do Prometheus para configurar um servidor Prometheus e confirme se está a funcionar.
Modifique o arquivo de configuração prometheus.yml para que o Prometheus raspe o ponto de extremidade de métricas que o aplicativo de exemplo está expondo. Adicione o seguinte texto realçado na scrape_configs
secção:
# 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 Prometheus
Recarregue a configuração ou reinicie o servidor Prometheus.
Confirme se OpenTelemetryTest está no estado UP na página Status>Targets do portal da Web Prometheus.
Na página Gráfico do portal Web Prometheus, introduza
hats
na caixa de texto da expressão e selecionehats_sold_Hats
No separador gráfico, Prometheus mostra o valor crescente do Contador "chapéus-vendidos" que está a ser emitido pela aplicação de exemplo.
Na imagem anterior, o tempo do gráfico é definido como 5m, ou seja, 5 minutos.
Se o servidor Prometheus não estiver raspando o aplicativo de exemplo por muito tempo, talvez seja necessário esperar que os dados se acumulem.
Mostrar métricas em um painel do Grafana
Siga as instruções padrão para instalar o Grafana e conectá-lo a uma fonte de dados Prometheus.
Crie um painel do Grafana clicando + no ícone na barra de ferramentas à esquerda no portal da Web do Grafana e selecione Painel. No editor de painel exibido, digite Chapéus vendidos/s na caixa de entrada Título e classifique(hats_sold[5m]) no campo de expressão PromQL:
Clique em Aplicar para salvar e exibir o novo painel.
]
Criar uma ferramenta de coleta personalizada usando a API .NET MeterListener
A API .NET MeterListener permite que você crie uma lógica personalizada no processo para observar as medições que estão sendo registradas pelo System.Diagnostics.Metrics.Meter. Para obter orientação sobre como criar lógica personalizada compatível com a instrumentação EventCounters mais antiga, consulte EventCounters.
Modifique o código de Program.cs
para usar MeterListener:
using System.Diagnostics.Metrics;
class Program
{
static Meter s_meter = new("HatCo.HatStore", "1.0.0");
static Counter<int> s_hatsSold = s_meter.CreateCounter<int>(
name: "hats-sold",
unit: "Hats",
description: "The number of hats sold in our store");
static void Main(string[] args)
{
using MeterListener meterListener = new();
meterListener.InstrumentPublished = (instrument, listener) =>
{
if (instrument.Meter.Name is "HatCo.HatStore")
{
listener.EnableMeasurementEvents(instrument);
}
};
meterListener.SetMeasurementEventCallback<int>(OnMeasurementRecorded);
// Start the meterListener, enabling InstrumentPublished callbacks.
meterListener.Start();
var rand = Random.Shared;
Console.WriteLine("Press any key to exit");
while (!Console.KeyAvailable)
{
//// Simulate hat selling transactions.
Thread.Sleep(rand.Next(100, 2500));
s_hatsSold.Add(rand.Next(0, 1000));
}
}
static void OnMeasurementRecorded<T>(
Instrument instrument,
T measurement,
ReadOnlySpan<KeyValuePair<string, object?>> tags,
object? state)
{
Console.WriteLine($"{instrument.Name} recorded measurement {measurement}");
}
}
A saída a seguir mostra a saída do aplicativo com retorno de chamada personalizado em cada medição:
> dotnet run
Press any key to exit
hats-sold recorded measurement 978
hats-sold recorded measurement 775
hats-sold recorded measurement 666
hats-sold recorded measurement 66
hats-sold recorded measurement 914
hats-sold recorded measurement 912
...
Explicando o código de exemplo
Os trechos de código nesta seção vêm do exemplo anterior.
No código destacado a seguir, uma instância do MeterListener é criada para receber medições. A using
palavra-chave faz com que Dispose
seja chamada quando o meterListener
sai do escopo.
using MeterListener meterListener = new();
meterListener.InstrumentPublished = (instrument, listener) =>
{
if (instrument.Meter.Name is "HatCo.HatStore")
{
listener.EnableMeasurementEvents(instrument);
}
};
O código realçado a seguir configura de quais instrumentos o ouvinte recebe medições. InstrumentPublished é um delegado que é invocado quando um novo instrumento é criado dentro do aplicativo.
using MeterListener meterListener = new();
meterListener.InstrumentPublished = (instrument, listener) =>
{
if (instrument.Meter.Name is "HatCo.HatStore")
{
listener.EnableMeasurementEvents(instrument);
}
};
O delegado pode examinar o instrumento para decidir se subscreve. Por exemplo, o delegado pode verificar o nome, o medidor ou qualquer outra propriedade pública. EnableMeasurementEvents Permite receber medições do instrumento especificado. Código que obtém uma referência a um instrumento por outra abordagem:
- Normalmente não é feito.
- Pode invocar
EnableMeasurementEvents()
a qualquer momento com a referência.
O delegado que é invocado quando as medições são recebidas de um instrumento é configurado chamando SetMeasurementEventCallback:
meterListener.SetMeasurementEventCallback<int>(OnMeasurementRecorded);
// Start the meterListener, enabling InstrumentPublished callbacks.
meterListener.Start();
var rand = Random.Shared;
Console.WriteLine("Press any key to exit");
while (!Console.KeyAvailable)
{
//// Simulate hat selling transactions.
Thread.Sleep(rand.Next(100, 2500));
s_hatsSold.Add(rand.Next(0, 1000));
}
}
static void OnMeasurementRecorded<T>(
Instrument instrument,
T measurement,
ReadOnlySpan<KeyValuePair<string, object?>> tags,
object? state)
{
Console.WriteLine($"{instrument.Name} recorded measurement {measurement}");
}
O parâmetro genérico controla qual tipo de dados de medição é recebido pelo retorno de chamada. Por exemplo, a Counter<int>
gera medições, Counter<double>
gera double
int
medições. Os instrumentos podem ser criados com byte
, , , , float
short
, double
int
long
e decimal
tipos. Recomendamos registrar um retorno de chamada para cada tipo de dados, a menos que você tenha conhecimento específico do cenário de que nem todos os tipos de dados são necessários. Fazer chamadas repetidas com SetMeasurementEventCallback
diferentes argumentos genéricos pode parecer um pouco incomum. A API foi projetada dessa forma para permitir que um MeterListener
receba medições com sobrecarga de baixo desempenho, normalmente apenas alguns nanossegundos.
Quando MeterListener.EnableMeasurementEvents
é chamado, um objeto pode ser fornecido como um state
dos parâmetros. O state
objeto é arbitrário. Se você fornecer um objeto de estado nessa chamada, ele será armazenado com esse instrumento e retornado a você como o state
parâmetro no retorno de chamada. Isso é pretendido tanto como uma conveniência e como uma otimização de desempenho. Muitas vezes, os ouvintes precisam:
- Crie um objeto para cada instrumento que esteja armazenando medições na memória.
- Ter código para fazer cálculos nessas medições.
Como alternativa, crie um Dictionary
que mapeie do instrumento para o objeto de armazenamento e pesquise-o em todas as medições. Usar um Dictionary
é muito mais lento do que acessá-lo a partir de state
.
meterListener.Start();
O código anterior inicia o que habilita retornos MeterListener
de chamada. O InstrumentPublished
delegado é invocado para cada Instrumento pré-existente no processo. Os objetos Instrument recém-criados também são acionados InstrumentPublished
para serem invocados.
using MeterListener meterListener = new MeterListener();
Quando o aplicativo termina de ouvir, descartar o ouvinte interrompe o fluxo de retornos de chamada e libera quaisquer referências internas ao objeto do ouvinte. A using
palavra-chave usada ao declarar meterListener
causas Dispose
a serem chamadas quando a variável sai do escopo. Observe que está apenas prometendo que Dispose
não iniciará novos retornos de chamada. Como os retornos de chamada ocorrem em threads diferentes, ainda pode haver retornos de chamada em andamento após o retorno da chamada Dispose
.
Para garantir que uma determinada região de código no retorno de chamada não esteja em execução no momento e não seja executada no futuro, a sincronização de threads deve ser adicionada. Dispose
não inclui sincronização por padrão porque:
- A sincronização adiciona sobrecarga de desempenho em cada retorno de chamada de medição.
MeterListener
é projetado como uma API altamente consciente do desempenho.