Coleção de métricas personalizadas no .NET e no .NET Core

Os SDKs do .NET e do .NET Core do Application Insights, do Azure Monitor, têm dois métodos diferentes de coletar métricas personalizadas: TrackMetric() e GetMetric(). A principal diferença entre esses dois métodos é a agregação local. O método TrackMetric() não tem pré-agregação. Já o método GetMetric() tem. Recomendamos que você use a agregação, por isso o método TrackMetric() não é mais o indicado para coletar métricas personalizadas. Este artigo explica como usar o método GetMetric() e alguns fundamentos por trás do seu funcionamento.

Observação

A documentação a seguir depende da API clássica do Application Insights. O plano de longo prazo do Application Insights é coletar dados usando o OpenTelemetry. Para obter mais informações, confira Habilitar o OpenTelemetry do Azure Monitor para aplicativos .NET, Node.js, Python e Java.

API com pré-agregação e sem pré-agregação

O método TrackMetric() envia telemetria bruta que indica uma métrica. Não é eficiente enviar um único item de telemetria para cada valor. O método TrackMetric() também é ineficiente em termos de desempenho, porque cada TrackMetric(item) passa pelo pipeline completo do SDK de inicializadores e processadores de telemetria.

Ao contrário de TrackMetric(), GetMetric() manipula a pré-agregação local para você e envia apenas uma métrica de resumo agregada em um intervalo fixo de um minuto. Caso seja necessário monitorar de perto alguma métrica personalizada em segundos ou até mesmo milissegundos, é possível fazer isso com o custo de armazenamento e tráfego de rede do monitoramento a cada minuto. Esse comportamento também reduz bastante o risco de limitação, pois o número total de itens de telemetria que precisam ser enviados para uma métrica agregada é muito reduzido.

Em Application Insights, as métricas personalizadas coletadas por meio de TrackMetric() e GetMetric() não estão sujeitas à amostragem. A amostragem de métricas importantes pode gerar cenários em que os alertas criados para essas métricas podem não ser confiáveis. Não fazendo a amostragem das métricas personalizadas, você geralmente tem a certeza de que um alerta é acionado quando os limites de alerta são violados. Como as métricas personalizadas não são amostradas, há algumas preocupações em potencial.

O acompanhamento de tendências de uma métrica a cada segundo ou em um intervalo ainda mais detalhado pode ter os seguintes resultados:

  • Aumento dos custos de armazenamento de dados. Há um custo associado à quantidade de dados que você envia para o Azure Monitor. Quanto mais dados enviados, maior é o custo geral de monitoramento.
  • Aumento do tráfego de rede ou sobrecarga de desempenho. Em alguns cenários, essa sobrecarga pode ter um custo monetário e de desempenho do aplicativo.
  • Risco de limitação de ingestão. O Azure Monitor remove (“restringe”) os pontos de dados quando o aplicativo envia uma taxa alta de telemetria em um curto intervalo de tempo.

A limitação é preocupante, pois pode causar alertas perdidos. A condição para disparar um alerta pode ocorrer no local e ser removida no ponto de extremidade de ingestão devido ao excesso de dados enviados. Não recomendamos usar TrackMetric() para .NET e .NET Core, a menos que você tenha implementado sua própria lógica de agregação local. Se você está tentando rastrear todas as instâncias em que um evento ocorre durante um determinado período, TrackEvent() é uma opção melhor. Tenha em mente que, ao contrário das métricas personalizadas, os eventos personalizados estão sujeitos à amostragem. Você ainda pode usar TrackMetric() mesmo sem escrever sua própria pré-agregação local. No entanto, se você fizer isso, tenha cuidado com percalços.

Em resumo, recomendamos o método GetMetric() porque ele faz a pré-agregação, acumula valores de todas as chamadas Track() e envia um resumo ou agregação a cada minuto. O método GetMetric() pode reduzir significativamente o custo e a sobrecarga de desempenho enviando menos pontos de dados, mais ainda coletando todas as informações relevantes.

Observação

Somente os SDKs do .NET e do .NET Core têm um método GetMetric(). Se você usa o Java, confira Enviar métricas personalizadas usando o Micrometer. Para o JavaScript e o Node.js, você ainda usaria o método TrackMetric(), mas lembre-se das limitações descritas na seção anterior. Para o Python, você pode usar OpenCensus.stats para enviar métricas personalizadas, mas a implementação de métricas é diferente.

Introdução ao método GetMetric

Para nossos exemplos, vamos usar um aplicativo básico do serviço de trabalho do .NET Core 3.1. Se você quer replicar o ambiente de teste usado com esses exemplos, siga as etapas de 1 a 6 do artigo Monitoramento do serviço de trabalho. Essas etapas adicionam o Application Insights a um modelo básico de projeto de serviço de trabalho. Os conceitos são aplicáveis a qualquer aplicativo geral em que o SDK possa ser usado, incluindo aplicativos Web e aplicativos de console.

Enviar métrica

Substitua o conteúdo do arquivo worker.cs pelo seguinte código:

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.ApplicationInsights;

namespace WorkerService3
{
    public class Worker : BackgroundService
    {
        private readonly ILogger<Worker> _logger;
        private TelemetryClient _telemetryClient;

        public Worker(ILogger<Worker> logger, TelemetryClient tc)
        {
            _logger = logger;
            _telemetryClient = tc;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {   // The following line demonstrates usages of GetMetric API.
            // Here "computersSold", a custom metric name, is being tracked with a value of 42 every second.
            while (!stoppingToken.IsCancellationRequested)
            {
                _telemetryClient.GetMetric("ComputersSold").TrackValue(42);

                _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
                await Task.Delay(1000, stoppingToken);
            }
        }
    }
}

Ao executar o código de exemplo, aparece o loop while sendo executado várias vezes sem que a telemetria seja enviada na janela de saída do Visual Studio. Um único item de telemetria é enviado aproximadamente na marca de 60 segundos, o que no nosso teste tem a seguinte aparência:

Application Insights Telemetry: {"name":"Microsoft.ApplicationInsights.Dev.00000000-0000-0000-0000-000000000000.Metric", "time":"2019-12-28T00:54:19.0000000Z",
"ikey":"00000000-0000-0000-0000-000000000000",
"tags":{"ai.application.ver":"1.0.0.0",
"ai.cloud.roleInstance":"Test-Computer-Name",
"ai.internal.sdkVersion":"m-agg2c:2.12.0-21496",
"ai.internal.nodeName":"Test-Computer-Name"},
"data":{"baseType":"MetricData",
"baseData":{"ver":2,"metrics":[{"name":"ComputersSold",
"kind":"Aggregation",
"value":1722,
"count":41,
"min":42,
"max":42,
"stdDev":0}],
"properties":{"_MS.AggregationIntervalMs":"42000",
"DeveloperMode":"true"}}}}

Este item de telemetria único representa uma agregação de 41 medidas de métricas distintas. Como estamos enviando o mesmo valor várias vezes, temos um desvio padrão (stDev) de 0 com valores máximos (max) e mínimos (min) idênticos. A propriedade value representa uma soma de todos os valores individuais que foram agregados.

Observação

O método GetMetric é incompatível com o acompanhamento do último valor (por exemplo, gauge), tampouco com o acompanhamento de histogramas ou distribuições.

Se examinássemos nosso recurso do Application Insights na experiência Log Analytics, o item individual de telemetria se pareceria com a captura de tela abaixo.

Screenshot that shows the Log Analytics query view.

Observação

Como o item de telemetria bruta não continha um campo nem uma propriedade de soma explícita depois de ser ingerido, criamos um para você. Nesse caso, as propriedades value e valueSum representam a mesma coisa.

Você também pode acessar a telemetria das métricas personalizadas na seção Métricas do portal como uma métrica personalizada e baseada em log. A captura de tela abaixo é um exemplo de métrica baseada em log.

Screenshot that shows the Metrics explorer view.

Referência de métrica de cache para uso de alta taxa de transferência

É possível observar os valores de métrica com frequência em alguns casos. Por exemplo, um serviço de alta taxa de transferência que processa 500 solicitações por segundo pode querer emitir 20 métricas de telemetria para cada solicitação. Esse resultado significa acompanhar 10 mil valores por segundo. Nesses cenários de alta taxa de transferência, talvez os usuários precisem ajudar o SDK evitando algumas pesquisas.

O exemplo acima realizou uma pesquisa de um identificador para a métrica ComputersSold e rastreou um valor observado de 42. Em vez disso, é possível armazenar em cache o identificador em várias invocações de rastreamento:

//...

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            // This is where the cache is stored to handle faster lookup
            Metric computersSold = _telemetryClient.GetMetric("ComputersSold");
            while (!stoppingToken.IsCancellationRequested)
            {

                computersSold.TrackValue(42);

                computersSold.TrackValue(142);

                _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
                await Task.Delay(50, stoppingToken);
            }
        }

Além de armazenar em cache o identificador de métrica, o exemplo acima também reduziu Task.Delay para 50 milissegundos. Assim, o loop pôde ser executado com mais frequência. O resultado é 772 invocações TrackValue().

Métricas multidimensionais

Os exemplos na seção anterior mostram métricas de dimensionalidade zero. As métricas também podem ser multidimensionais. Atualmente, damos suporte para até 10 dimensões.

Este é um exemplo de como criar uma métrica unidimensional:

//...

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            // This is an example of a metric with a single dimension.
            // FormFactor is the name of the dimension.
            Metric computersSold= _telemetryClient.GetMetric("ComputersSold", "FormFactor");

            while (!stoppingToken.IsCancellationRequested)
            {
                // The number of arguments (dimension values)
                // must match the number of dimensions specified while GetMetric.
                // Laptop, Tablet, etc are values for the dimension "FormFactor"
                computersSold.TrackValue(42, "Laptop");
                computersSold.TrackValue(20, "Tablet");
                computersSold.TrackValue(126, "Desktop");


                _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
                await Task.Delay(50, stoppingToken);
            }
        }

Caso você execute o código de exemplo por pelo menos 60 segundos, três itens diferentes de telemetria são enviados ao Azure. Cada um representa a agregação de um dos três fatores forma. Como antes, você pode examiná-los melhor na exibição do Log Analytics.

Screenshot that shows the Log Analytics view of multidimensional metric.

No gerenciador de métricas:

Screenshot that shows Custom metrics.

Não é possível dividir a métrica pela nova dimensão personalizada nem exibir a dimensão personalizada com a exibição de métricas.

Screenshot that shows splitting support.

Por padrão, as métricas multidimensionais no gerenciador de métricas não estão ativadas nos recursos do Application Insights.

Habilitar métricas multidimensionais

Para habilitar métricas multidimensionais para um recurso do Application Insights, selecione Uso e custos estimados>Métricas personalizadas>Habilitar alertas em dimensões de métricas personalizadas>OK. Para saber mais, confira Dimensões de métricas personalizadas e pré-agregação.

Após fazer essa alteração e enviar uma nova telemetria multidimensional, você pode selecionar Aplicar divisão.

Observação

Somente as métricas enviadas recentemente depois que o recurso foi ativado no portal terão as dimensões armazenadas.

Screenshot that shows applying splitting.

Exiba suas agregações de métrica para cada dimensão FormFactor.

Screenshot that shows form factors.

Usar MetricIdentifier quando há mais de três dimensões

No momento, há suporte para dez dimensões. É necessário usar MetricIdentifier para mais de três dimensões:

// Add "using Microsoft.ApplicationInsights.Metrics;" to use MetricIdentifier
// MetricIdentifier id = new MetricIdentifier("[metricNamespace]","[metricId],"[dim1]","[dim2]","[dim3]","[dim4]","[dim5]");
MetricIdentifier id = new MetricIdentifier("CustomMetricNamespace","ComputerSold", "FormFactor", "GraphicsCard", "MemorySpeed", "BatteryCapacity", "StorageCapacity");
Metric computersSold  = _telemetryClient.GetMetric(id);
computersSold.TrackValue(110,"Laptop", "Nvidia", "DDR4", "39Wh", "1TB");

Configuração de métrica personalizada

Se você quer alterar a configuração de uma métrica, é necessário fazer isso no local em que a métrica é inicializada.

Nomes de dimensão especiais

As métricas não usam o contexto de telemetria do TelemetryClient usado para acessá-las. A melhor solução para isso é usar nomes de dimensões especiais disponíveis como constantes na classe MetricDimensionNames.

As agregações de métrica que a métrica Special Operation Request Size a seguir envia não têm Context.Operation.Name definido como Special Operation. O método TrackMetric() ou qualquer outro método TrackXXX() tem OperationName definido corretamente como Special Operation.

        //...
        TelemetryClient specialClient;
        private static int GetCurrentRequestSize()
        {
            // Do stuff
            return 1100;
        }
        int requestSize = GetCurrentRequestSize()

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                //...
                specialClient.Context.Operation.Name = "Special Operation";
                specialClient.GetMetric("Special Operation Request Size").TrackValue(requestSize);
                //...
            }
                   
        }

Nessa circunstância, use os nomes de dimensão especiais listados na classe MetricDimensionNames para especificar os valores TelemetryContext.

Por exemplo, quando a agregação de métrica resultante da próxima instrução é enviada para o ponto de extremidade na nuvem do Application Insights, o campo de dados Context.Operation.Name é definido como Special Operation:

_telemetryClient.GetMetric("Request Size", MetricDimensionNames.TelemetryContext.Operation.Name).TrackValue(requestSize, "Special Operation");

Os valores dessa dimensão especial são copiados no TelemetryContext e não são como uma dimensão normal. Se você também quiser manter uma dimensão de operação para exploração de métrica normal, você precisará criar uma dimensão separada para essa finalidade:

_telemetryClient.GetMetric("Request Size", "Operation Name", MetricDimensionNames.TelemetryContext.Operation.Name).TrackValue(requestSize, "Special Operation", "Special Operation");

Dimensão e utilização de limitação da série temporal

Para impedir que o subsistema de telemetria use acidentalmente seus recursos, você pode controlar o número máximo de série de dados por métrica. Os limites padrão não são mais do que mil séries de dados totais por métrica e não mais do que 100 valores diferentes por dimensão.

Importante

Use valores cardeais baixos nas dimensões para evitar a limitação.

No contexto de dimensão e utilização de limite da série temporal, usamos Metric.TrackValue(..) para garantir que os limites sejam cumpridos. Se os limites já foram atingidos, Metric.TrackValue(..) retorna False, e o valor não é rastreado. Caso contrário, ele retornará True. Esse comportamento será útil se os dados de uma métrica se originarem da entrada de usuário.

O construtor MetricConfiguration obtém algumas opções sobre como gerenciar diferentes séries dentro da respectiva métrica e um objeto de uma classe que implementa IMetricSeriesConfiguration que especifica o comportamento de agregação para cada série individual da métrica:

var metConfig = new MetricConfiguration(seriesCountLimit: 100, valuesPerDimensionLimit:2,
                new MetricSeriesConfigurationForMeasurement(restrictToUInt32Values: false));

Metric computersSold = _telemetryClient.GetMetric("ComputersSold", "Dimension1", "Dimension2", metConfig);

// Start tracking.
computersSold.TrackValue(100, "Dim1Value1", "Dim2Value1");
computersSold.TrackValue(100, "Dim1Value1", "Dim2Value2");

// The following call gives 3rd unique value for dimension2, which is above the limit of 2.
computersSold.TrackValue(100, "Dim1Value1", "Dim2Value3");
// The above call does not track the metric, and returns false.
  • seriesCountLimit é o número máximo de série temporal de dados que uma métrica pode ter. Quando esse limite é atingido, as chamadas para TrackValue(), que costumam ter uma nova série como resultado, retornam false.
  • valuesPerDimensionLimit limita o número de valores distintos por dimensão de maneira semelhante.
  • restrictToUInt32Values determina se apenas valores inteiros não negativos devem ser acompanhados.

Este é um exemplo de como enviar uma mensagem para saber se os limites de tampa são excedidos:

if (! computersSold.TrackValue(100, "Dim1Value1", "Dim2Value3"))
{
// Add "using Microsoft.ApplicationInsights.DataContract;" to use SeverityLevel.Error
_telemetryClient.TrackTrace("Metric value not tracked as value of one of the dimension exceeded the cap. Revisit the dimensions to ensure they are within the limits",
SeverityLevel.Error);
}

Próximas etapas