Поделиться через


Сбор метрик

Эта статья относится к ✔️ .NET 6.0 и более поздним версиям .NET Framework 4.6.1 и более поздних ✔️ версий.

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

  • Заполнение метрик в Grafana с помощью OpenTelemetry и Prometheus.
  • Просмотр метрик в режиме реального времени с помощью dotnet-counters
  • Создание пользовательского инструмента сбора данных с помощью основного API .NET MeterListener.

Дополнительные сведения о пользовательском инструментировании метрик и параметрах см. в разделе "Сравнение API метрик".

Предпосылки

Создание примера приложения

Прежде чем собирать метрики, необходимо производить измерения. В этом руководстве создается приложение с базовым инструментированием метрик. Среда выполнения .NET также имеет различные метрики. Дополнительные сведения о создании новых метрик с помощью System.Diagnostics.Metrics.Meter API см. в руководстве по инструментированию.

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

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

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

Предыдущий код имитирует продажу шляп с случайными интервалами и случайным временем.

Просмотр метрик с помощью dotnet-counters

dotnet-counters — это средство командной строки, которое может просматривать динамические метрики для приложений .NET Core по запросу. Не требуется настройка, что делает его полезным для разовых исследований или проверки работы инструментирования метрик. Она работает с API-интерфейсами на основе System.Diagnostics.Metrics и счетчиками событий.

Если средство dotnet-counters не установлено, выполните следующую команду:

dotnet tool update -g dotnet-counters

Пока выполняется примерное приложение, запустите dotnet-counters. В следующей команде показан пример dotnet-counters мониторинга всех метрик из счетчика HatCo.HatStore . Имя счетчика чувствительно к регистру символов. Наш пример приложения был metric-instr.exe, замените его именем примера приложения.

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

Выходные данные должны выглядеть примерно так:

Press p to pause, r to resume, q to quit.
    Status: Running

[HatCo.HatStore]
    hats-sold (Count / 1 sec)                          4

dotnet-counters можно запустить с другим набором метрик, чтобы увидеть некоторые встроенные инструменты для среды выполнения .NET.

dotnet-counters monitor -n metric-instr

Выходные данные должны выглядеть примерно так:

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

Дополнительные сведения см. в разделе dotnet-counters. Дополнительные сведения о метриках в .NET см. о встроенных метриках.

Просмотр метрик в Grafana с OpenTelemetry и Prometheus

Обзор

OpenTelemetry:

  • Является независимым от поставщиков проектом с открытым исходным кодом, поддерживаемым Cloud Native Computing Foundation.
  • Стандартизирует создание и сбор данных телеметрии для облачного программного обеспечения.
  • Работает с .NET с помощью API метрик .NET.
  • Поддерживается Azure Monitor и многими поставщиками APM.

В этом руководстве показана одна из интеграции, доступная для метрик OpenTelemetry с помощью проектов OSS Prometheus и Grafana . Поток данных метрик:

  1. API метрик .NET записывают измерения из этого примера приложения.

  2. Библиотека OpenTelemetry, запущенная в приложении, объединяет измерения.

  3. Библиотека программы экспорта Prometheus предоставляет доступ к агрегированным данным через конечную точку метрик HTTP. Термином «экспортер» OpenTelemetry называет библиотеки, которые передают телеметрию в серверные части, специфичные для поставщиков.

  4. Сервер Prometheus:

    • Опрос конечной точки метрик
    • Считывает данные
    • Сохраняет данные в базе данных для долгосрочного сохранения. Prometheus относится к чтению и хранению данных как к извлечению данных с конечной точки.
    • Может работать на другом компьютере
  5. Сервер Grafana:

    • Запрашивает данные, хранящиеся в Prometheus, и отображает его на веб-панели мониторинга.
    • Может работать на другом компьютере.

Настройка примера приложения для использования экспортера Prometheus в OpenTelemetry

Добавьте ссылку на экспортер Prometheus OpenTelemetry в пример приложения:

dotnet add package OpenTelemetry.Exporter.Prometheus.HttpListener --prerelease

Примечание.

В этом руководстве используется предварительная сборка поддержки Prometheus в OpenTelemetry, доступная на момент написания.

Обновление Program.cs с помощью конфигурации 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));
        }
    }
}

В предыдущем коде:

  • AddMeter("HatCo.HatStore") настраивает OpenTelemetry для передачи всех метрик, собранных счетчиком, определенным в приложении.
  • AddPrometheusHttpListener конфигурирует OpenTelemetry для:
    • Открытие доступа к конечной точке метрик Prometheus на порту 9184
    • Используйте HttpListener.

Дополнительные сведения о параметрах конфигурации OpenTelemetry см. в документации по OpenTelemetry . В документации по OpenTelemetry показаны параметры размещения для приложений ASP.NET.

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

dotnet run

Установка и настройка Prometheus

Выполните первые действия Prometheus, чтобы настроить сервер Prometheus и подтвердить его работу.

Измените файл конфигурации prometheus.yml, чтобы Prometheus собирал данные с конечной точки метрик, которую предоставляет пример приложения. Добавьте следующий выделенный текст в 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']

Запуск Prometheus

  1. Перезагрузите конфигурацию или перезапустите сервер Prometheus.

  2. Убедитесь, что OpenTelemetryTest находится в состоянии UP на странице Состояние>Цели веб-портала Prometheus. Состояние Prometheus

  3. На странице Graph веб-портала Prometheus введите hats в текстовое поле выражения и выберите hats_sold_HatsHat на вкладке графа. Prometheus показывает увеличение значения счетчика "hats-sold", который эмитируется примерным приложением. Граф проданных шляп Prometheus

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

Если сервер Prometheus не собирает данные с примерного приложения достаточно долго, вам, возможно, придется подождать, пока данные накопятся.

Отображение метрик на панели мониторинга Grafana

  1. Следуйте стандартным инструкциям по установке Grafana и подключению его к источнику данных Prometheus.

  2. Создайте панель мониторинга Grafana, щелкнув + значок на левой панели инструментов на веб-портале Grafana, а затем выберите панель мониторинга. В появившемся редакторе панели мониторинга введите Hats Sold/Sec в поле ввода заголовка и rate(hats_sold[5m]) в поле выражения PromQL:

    Продажи шляп в редакторе панели Grafana

  3. Нажмите кнопку "Применить" , чтобы сохранить и просмотреть новую панель мониторинга.

    Панель мониторинга продаж шляп в Grafana ]

Создание пользовательского средства сбора с помощью API .NET MeterListener

API .NET MeterListener позволяет разрабатывать пользовательскую внутрипроцессную логику для наблюдения за процессом записи измерений System.Diagnostics.Metrics.Meter. Рекомендации по созданию пользовательской логики, совместимой с более старой инструментацией EventCounters, см. в разделе EventCounters.

Измените код Program.cs, чтобы использовать 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}");
    }
}

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

> 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
...

Объяснение примера кода

Фрагменты кода в этом разделе приведены из предыдущего примера.

В следующем выделенном коде создается экземпляр MeterListener объекта для получения измерений. Ключевое слово using вызывает Dispose при выходе meterListener из зоны видимости.

using MeterListener meterListener = new();
meterListener.InstrumentPublished = (instrument, listener) =>
{
    if (instrument.Meter.Name is "HatCo.HatStore")
    {
        listener.EnableMeasurementEvents(instrument);
    }
};

Следующий выделенный код настраивает инструмент, из которого прослушиватель получает измерения. InstrumentPublished — это делегат, который вызывается при создании нового инструмента в приложении.

using MeterListener meterListener = new();
meterListener.InstrumentPublished = (instrument, listener) =>
{
    if (instrument.Meter.Name is "HatCo.HatStore")
    {
        listener.EnableMeasurementEvents(instrument);
    }
};

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

  • Обычно не выполняется.
  • Можно вызвать EnableMeasurementEvents() в любое время, используя ссылку.

Делегат, вызываемый при получении измерений от инструмента, настраивается путем вызова 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}");
}

Универсальный параметр управляет типом данных измерения, получаемым обратным вызовом. Например, Counter<int> создаёт int измерения, а Counter<double> создаёт double измерения. Инструменты можно создавать с помощью byte, short, int, long, float, double и decimal типов. Мы рекомендуем зарегистрировать обратный вызов для каждого типа данных, если у вас есть знания, относящиеся к конкретным сценариям, что не все типы данных необходимы. Выполнение повторяющихся вызовов SetMeasurementEventCallback с различными универсальными аргументами может показаться немного необычным. API был разработан таким образом, чтобы позволить MeterListener получать измерения с низкой нагрузкой на производительность, что занимает, как правило, всего несколько наносекунд.

Когда вызывается MeterListener.EnableMeasurementEvents, объект state можно предоставить как один из параметров. Объект state является произвольным. Если вы предоставляете объект состояния в этом вызове, он хранится с этим инструментом и возвращается в качестве state параметра в обратном вызове. Это предназначено как для удобства, так и в качестве оптимизации производительности. Часто слушатели должны:

  • Создайте объект для каждого инструмента, в котором хранятся измерения в памяти.
  • Имеется код для выполнения вычислений по этим измерениям.

В качестве альтернативы создайте объект Dictionary, который сопоставляет инструмент с объектом хранения, и проверяйте его при каждом измерении. Использование Dictionary гораздо медленнее, чем доступ к нему из state.

meterListener.Start();

Предыдущий код запускает MeterListener, что позволяет использовать обратные вызовы. Делегат InstrumentPublished вызывается для каждого ранее существующего инструмента в процессе. Недавно созданные объекты Instrument также вызывают активацию InstrumentPublished.

using MeterListener meterListener = new MeterListener();

Когда приложение завершит прослушивание, удаление прослушивателя останавливает процесс обратных вызовов и освобождает все внутренние ссылки на объект прослушивателя. Ключевое слово using, используемое при объявлении meterListener, вызывает Dispose, когда переменная выходит из области видимости. Обратите внимание, что Dispose обещает только не инициировать новые обратные вызовы. Так как обратные вызовы происходят в разных потоках, после возврата вызова могут по-прежнему выполняться обратные вызовы Dispose .

Чтобы гарантировать, что определенный регион кода в обратном вызове не выполняется в настоящее время и не будет выполняться в будущем, необходимо добавить синхронизацию потоков. Dispose Не включает синхронизацию по умолчанию, так как:

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