Сбор пользовательских метрик в .NET и .NET Core

В пакетах SDK для .NET и .NET Core в Application Insights для Azure Monitor применяются два разных метода сбора пользовательских метрик: TrackMetric() и GetMetric(). Основное различие между этими двумя методами заключается в локальном агрегировании. В TrackMetric() отсутствует предварительное агрегирование, тогда, как в GetMetric() оно есть. Рекомендуемый подход заключается в использовании агрегирования, поэтому TrackMetric() больше не является предпочтительным методом сбора пользовательских метрик. В этой статье приведены пошаговые инструкции по использованию метода GetMetric() и некоторые принципы его работы.

API с предварительным агрегированием и без предварительного агрегирования

TrackMetric() отправляет необработанные данные телеметрии, обозначающие метрику. Отправка одного элемента телеметрии для каждого значения является неэффективной. TrackMetric() также является неэффективным в плане производительности, поскольку каждый TrackMetric(item) проходит через весь конвейер инициализаторов и процессоров телеметрии. В отличие от TrackMetric(), GetMetric() обрабатывает локальное предварительное агрегирование для вас, а затем отправляет только агрегированную сводную метрику с фиксированным интервалом в одну минуту. Таким образом, если вам необходимо тщательно отслеживать некоторые пользовательские метрики на уровне секунд или даже миллисекунд, вы можете сделать это, неся при этом затраты только на хранение и сетевой трафик, связанные только с ежеминутным мониторингом. Это также значительно снижает риск возникновения регулирования, поскольку общее количество элементов телеметрии, которые необходимо отправить для агрегированной метрики, значительно уменьшается.

В Application Insights пользовательские метрики, собранные с помощью TrackMetric() и GetMetric(), не подлежат выборке. Выборка важной метрики может привести к сценариям, при которых, оповещение, созданное на основе этой метрики, может стать ненадежным. Если вы никогда не делали выборку пользовательских метрик, можете быть уверены, что при нарушении порога оповещения, сработает оповещение. Но поскольку пользовательские метрики не являются выборочными, существуют некоторые потенциальные проблемы.

Если нужно отслеживать тенденции в метрике каждую секунду или с еще более детальным интервалом, это может привести к следующим последствиям.

  • Увеличение затрат на хранение данных. Увеличение объема данных, отправляемых в Azure Monitor, связано с большими затратами. (Чем больше данных вы отправляете, тем выше общая стоимость мониторинга).
  • Увеличение сетевого трафика и потери производительности. (В некоторых сценариях это может стать причиной как денежных затрат, так и затрат, связанных с производительностью приложения).
  • Риск регулирования приема данных. (Служба Azure Monitor удаляет ("регулирует") точки данных, когда приложение слишком часто отправляет данные телеметрии за короткий интервал времени.)

Регулирования вызывает особую озабоченность, поскольку, как и выборка, регулирование может привести к пропуску оповещений, так как условие для запуска оповещения может возникнуть локально, а затем быть отменено в конечной точке приема данных из-за слишком большого объема отправляемых данных. Вот почему для .NET и .NET Core мы не рекомендуем использовать TrackMetric(), если вы не реализовали собственную логику локального агрегирования. Если вы пытаетесь отследить каждый экземпляр наступления события за определенный период времени, лучше всего вам может подойти TrackEvent(). Однако следует помнить, что в отличие от пользовательских метрик, пользовательские события подлежат выборке. Вы по-прежнему можете использовать TrackMetric() даже без написания собственного локального предварительно агрегирования, однако при использовании не забывайте о возможных проблемах.

В целом, GetMetric() является рекомендуемым подходом, поскольку он позволяет выполнить предварительное агрегирование, накапливает значения от всех вызовов Track() и отправляет сводку или агрегирование раз в минуту. Этот подход может значительно снизить затраты и потери производительности благодаря отправке меньшего числа точек данных. При этом собирается вся важная информация.

Примечание

Только пакеты SDK для .NET и .NET Core содержат метод GetMetric(). Если вы используете Java, см. как отправить пользовательские метрики с помощью micrometer. Для JavaScript и Node.js вы по-прежнему будете использовать TrackMetric(), однако помните о предостережениях, которые были изложены в предыдущем разделе. При использовании Python можно применить OpenCensus.stats для отправки пользовательских метрик, однако реализация метрик будет отличатся.

Начало работы с GetMetric

В нашем примере мы будем использовать базовое приложение службы рабочей роли .NET Core 3.1. При желании можно точно реплицировать тестовую среду, которая использовалась в этих примерах, выполнив шаги 1-6, приведенные в статье о мониторинге службы рабочей роли, чтобы добавить Application Insights в базовый шаблон проекта службы рабочей роли. Эти концепции применимы к любому общему приложению, в котором можно использовать пакет SDK, в том числе веб-приложения и консольные приложения.

Отправка метрик

Замените содержимое файла worker.cs следующим кодом:

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

Если запустить приведенный выше код и проследить за отправкой телеметрии через окно вывода Visual Studio или с помощью такого инструмента, как Telerik Fiddler, вы увидите, что цикл while многократно выполняется без отправки телеметрии, а затем, примерно на 60-й секунде, будет отправлен один элемент телеметрии, который в случае нашего теста выглядит следующим образом:

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"}}}}

Этот один элемент телеметрии представляет собой совокупность 41 отдельного метрического измерения. Поскольку мы отправляли одно и то же значение снова и снова, мы имеем стандартное отклонение (stDev) , равное 0, с идентичными максимальным (max) и минимальным (min) значениями. Свойство value представляет собой сумму всех отдельных значений, которые были агрегированы.

Примечание

GetMetric не поддерживает отслеживание последнего значения (т. е. "датчика") либо отслеживание гистограмм или распределений.

При рассмотрении ресурса Application Insights в разделе "Журналы (Analytics)", этот отдельный элемент телеметрии будет выглядеть следующим образом:

Экран "Представление запросов Log Analytics"

Примечание

Хотя необработанный элемент телеметрии не содержит явного свойства или поля суммы, после его получения, мы создадим его. В этом случае оба свойства value и valueSum представляют одно и то же.

Также можно получить доступ к телеметрии пользовательских метрик в разделе портала Метрики. В качестве метрики на основе журнала и пользовательской метрики. (На следующем снимке экрана показан пример метрики на основе журнала.) Экран "Представление обозревателя метрик"

Справочник по кэшированию метрики для использования высокой пропускной способности

В некоторых случаях значения метрики наблюдаются очень часто. Например, службе с высокой пропускной способностью, обрабатывающей 500 запросов в секунду, может потребоваться передать 20 метрик телеметрии для каждого запроса. Это означает отслеживание 10 000 значений в секунду. В таких сценариях с высокой пропускной способностью, пользователям может потребоваться помощь пакета SDK, во избежание выполнения поисков.

Например, в приведенном выше примере выполняется поиск дескриптора для метрики "ComputersSold", а затем отслеживается наблюдаемое значение 42. Вместо этого дескриптор может быть кэширован для вызовов нескольких отслеживаний:

//...

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

В дополнение к кэшированию дескриптора метрики, в приведенном примере также уменьшена задержка Task.Delay до 50 миллисекунд, таким образом цикл будет выполнятся чаще. Это приведет к 772 вызовам TrackValue().

Многомерные метрики

В примерах из предыдущего раздела показаны метрики нулевого измерения. Метрики также могут быть многомерными. В настоящее время поддерживается до 10 измерений.

Ниже приведен пример создания одномерной метрики:

//...

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

Выполнение этого кода в течение как минимум 60 секунд приведет к отправке в Azure трех отдельных элементов телеметрии, каждый из которых представляет собой агрегат одной из трех форм-факторов. Как и прежде, вы сможете просмотреть их в представлении "Журналы (Analytics)":

Экран "Представление анализа журналов многомерной метрики"

Также их можно просмотреть в интерфейсе обозревателя метрик:

Настраиваемые метрики

Однако в последующем вы заметите, что нельзя разделить метрику по новому пользовательскому измерению или просмотреть пользовательское измерение в представлении метрик:

Экран "Поддержка разделения"

По умолчанию многомерные метрики в интерфейсе обозревателя метрик не включены в ресурсах Application Insights.

Включение многомерных метрик

Чтобы включить многомерные метрики для ресурса Application Insights, выберите Использование и ожидаемые затраты > Пользовательские метрики > Активация оповещений по измерениям пользовательских метрик > ОК. Дополнительные сведения см. здесь.

После внесения этого изменения и отправки новой многомерной телеметрии можно будет Применить разделение.

Примечание

Только вновь отправленные метрики после включения этой функции на портале будут иметь сохраненные измерения.

Применение разделения

И просмотреть агрегированные метрики для каждого измерения FormFactor:

Экран "Форм-факторы"

Использование MetricIdentifier при наличии более трех измерений

В настоящее время поддерживается 10 измерений, однако при наличии более трех измерений необходимо использовать MetricIdentifier:

// 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");

Конфигурация пользовательской метрики

Если вы хотите изменить конфигурацию метрики, это необходимо сделать в том месте, где была инициализирована метрика.

Специальные имена измерений

Метрики не используют контекст телеметрии клиента TelemetryClient, применяемого для доступа к ним. Специальные имена измерений, доступные как константы в классе MetricDimensionNames, являются лучшим обходным решением для этого ограничения.

Агрегаты метрик, отправленные с помощью нижеприведенной метрики "Special Operation Request Size", не будут иметь присвоенное параметру Context.Operation.Name значение "Special Operation". В то время как TrackMetric() или любой другой метод TrackXXX() будет иметь правильно присвоенное параметру OperationName значение "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);
                //...
            }
                   
        }

В этом случае используйте специальные имена измерений, перечисленные в классе MetricDimensionNames, чтобы указать значения TelemetryContext.

Например, когда агрегат метрик, полученный в результате следующей инструкции, отправляется на облачную конечную точку Application Insights, его полю данных Context.Operation.Name будет задано значение "Special Operation":

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

Значения этого специального измерения будут скопированы в TelemetryContext и не будут использоваться в качестве "обычного" измерения. Если требуется также сохранить измерение операции для просмотра обычной метрики, вам нужно создать отдельное измерение для этой цели:

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

Ограничение измерений и временных рядов

Чтобы подсистема телеметрии случайно не использовала ваши ресурсы, можно управлять максимальным количеством рядов данных для каждой метрики. Ограничения по умолчанию — не более 1000 рядов данных итого для каждой метрики и не более 100 различных значений на измерение.

В контексте ограничения измерения и временного ряда мы используем Metric.TrackValue(..), чтобы убедиться, что ограничения соблюдаются. Если ограничения уже достигнуты, Metric.TrackValue(..) вернет значение "False", и значение не будет отслеживаться. В противном случае будет возвращено значение "True". Это может понадобится, если данные для метрики поступают от пользователя.

Конструктор MetricConfiguration принимает некоторые параметры по управлению различными рядами внутри соответствующей метрики и объектом класса, реализующем IMetricSeriesConfiguration, который определяет поведение агрегата для каждого отдельного ряда метрики:

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 — максимальное количество временных рядов данных, которые может содержать метрика. После достижения этого ограничения, вызовы TrackValue(), которые обычно приводят к появлению нового ряда, будут возвращать значение "false".
  • valuesPerDimensionLimit — аналогичным образом ограничивает количество отдельных значений для каждого измерения.
  • restrictToUInt32Values — определяет, следует ли отслеживать только неотрицательные целочисленные значения.

Ниже приведен пример того, как отправить сообщение, чтобы узнать о превышении лимитов ограничения:

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

Дальнейшие действия