Compartilhar via


Coletar métricas

Este artigo se aplica a: ✔️ .NET Core 3.1 e versões posteriores ✔️ .NET Framework 4.6.1 e versões posteriores

O código instrumentado pode registrar medidas numéricas, mas as medidas 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:

Para obter mais informações sobre instrumentação de métrica personalizada e opções, confira Comparar APIs de métrica.

Pré-requisitos

Criar um aplicativo de exemplo

Antes que as métricas possam ser coletadas, as medidas devem ser produzidas. Este tutorial cria um aplicativo que tem instrumentação de métrica básica. O runtime do .NET também tem várias métricas internas. Para obter mais informações sobre como criar métricas usando a System.Diagnostics.Metrics.Meter API, confira o tutorial de instrumentação.

dotnet new console -o metric-instr
cd metric-instr
dotnet add package System.Diagnostics.DiagnosticSource

Substitua o conteúdo de 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 horários aleatórios.

Exibir métricas com dotnet-counters

dotnet-counters é uma ferramenta de linha de comando simples que pode exibir métricas dinâmicas para qualquer aplicativo .NET Core sob demanda. Ela não requer configuração, tornando-a útil para investigações ad hoc ou verificando se a instrumentação de métrica está funcionando. Ele funciona com APIs baseadas em System.Diagnostics.Metrics 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 dotnet-counters. O comando a seguir mostra um exemplo de dotnet-counters monitoramento de todas as métricas do medidor HatCo.HatStore. O nome do medidor diferencia maiúsculas de minúsculas. Nosso aplicativo de exemplo foi metric-instr.exe, substitua-o pelo nome do seu aplicativo de exemplo.

dotnet-counters monitor -n metric-instr HatCo.HatStore

Uma saída semelhante à apresentada a seguir será exibida:

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 runtime do .NET:

dotnet-counters monitor -n metric-instr

Uma saída semelhante à apresentada a seguir será exibida:

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, confira dotnet-counters. Para saber mais sobre as métricas no .NET, confira métricas internas.

Preencher métricas no Grafana com OpenTelemetry e Prometheus.

Visão geral

OpenTelemetry:

  • É um projeto de software livre neutro do fornecedor compatível com o 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 de métricas:

  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;
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 pelo aplicativo.
  • AddPrometheusHttpListener configura o OpenTelemetry para:
    • Expor o ponto de extremidade de métricas do Prometheus na porta 9184
    • Use o HttpListener.

Confira 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 as opções de hospedagem para aplicativos ASP.NET.

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 do Graph do portal da Web do Prometheus, insira hats na caixa de texto de expressão e selecione hats_sold_Hatshat na guia grafo, o Prometheus mostra o valor crescente do Contador "hats-sold" que está sendo emitido pelo aplicativo de exemplo. Prometheus hats sold graph

Na imagem anterior, o tempo do grafo é definido como 5m, que é de 5 minutos.

Se o servidor Prometheus não tiver extraído o aplicativo de exemplo por muito tempo, talvez seja necessário aguardar o acúmulo de dados.

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 clicando no ícone + da barra de ferramentas à esquerda no portal da Web do Grafana e selecione Painel. No editor do painel exibido, insira Hats Sold/Sec como o Título e rate(hats_sold[5m]) no campo de expressão PromQL:

    Hats sold Grafana dashboard editor

  3. Clique em Aplicar para salvar e exibir o novo painel.

    Hats sold Grafana dashboard]

Criar uma ferramenta de coleção personalizada usando a API .NET MeterListener

A API MeterListener do .NET permite a criação de uma logica personalizada no processo para observar as medidas que estão sendo registradas pelo System.Diagnostics.Metrics.Meter. Para obter diretrizes sobre a criação de uma logica personalizada compatível com a instrumentação antiga do EventCounters, confira 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 medida:

> 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 snippets de código nesta seção vêm do exemplo anterior.

No código realçado a seguir, uma instância do MeterListener é criada para receber medidas. A palavra-chave using faz com que Dispose sejam chamados quando o meterListener sair 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 medidas. InstrumentPublished é um delegado que é invocado quando um novo instrumento for 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 deseja assinar. Por exemplo, o delegado pode verificar o nome, o Medidor ou qualquer outra propriedade pública. EnableMeasurementEvents permite o recebimento de medidas do instrumento especificado. Código que obtém uma referência a um instrumento por outra abordagem:

  • Normalmente, isso não é feito.
  • Pode invocar EnableMeasurementEvents() a qualquer momento com a referência.

O delegado que é invocado quando as medidas 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 medida é recebido pelo retorno de chamada. Por exemplo, Counter<int> gera medidas int, enquanto Counter<double> gera medidas double. Os instrumentos podem ser criados com os tipos byte, short, int, long, float, double e decimal. 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 para SetMeasurementEventCallback com argumentos genéricos diferentes pode parecer um pouco incomum. A API foi projetada dessa forma para permitir que um MeterListener receba medidas com sobrecarga de baixo desempenho, normalmente apenas alguns nanossegundos.

Quando MeterListener.EnableMeasurementEvents é chamado, um state objeto pode ser fornecido como um dos parâmetros. O objeto state é arbitrário. Se você fornecer um objeto de estado nessa chamada, ele será armazenado com esse instrumento e retornado a você como o parâmetro state no retorno de chamada. Isso se destina tanto como uma conveniência quanto como uma otimização de desempenho. Muitas vezes, os ouvintes precisam:

  • Crie um objeto para cada instrumento que está armazenando medidas na memória.
  • Tenha código para fazer cálculos nessas medidas.

Como alternativa, crie um Dictionary mapeado do instrumento para o objeto de armazenamento e pesquise-o em todas as medidas. Usar um Dictionary é muito mais lento do que acessá-lo de state.

meterListener.Start();

O código anterior inicia o MeterListener que habilita retornos de chamada. O delegado InstrumentPublished é invocado para cada Instrumento pré-existente no processo. Objetos instrumentais recém-criados também disparam InstrumentPublished a serem invocados.

using MeterListener meterListener = new MeterListener();

Quando o aplicativo terminar de escutar, descartar o ouvinte interrompe o fluxo de retornos de chamada e libera todas as referências internas ao objeto ouvinte. A palavra-chave using usada ao declarar meterListener faz com que Dispose sejam chamados quando a variável sai do escopo. Lembre-se de que Dispose só promete que ele 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 a chamada para Dispose ser retornada.

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 conversa deve ser adicionada. Dispose não inclui a sincronização por padrão porque:

  • A sincronização adiciona sobrecarga de desempenho em cada retorno de chamada de medida.
  • MeterListener foi projetado como uma API consciente de alto desempenho.