Сбор метрик
Эта статья относится к : ✔️ .NET Core 3.1 и более поздних версий платформа .NET Framework 4.6.1 и более поздних ✔️ версий.
Инструментированный код может записывать числовые измерения, однако чтобы создать полезные метрики для мониторинга эти измерения обычно необходимо агрегировать, передавать и хранить. Процесс агрегирования, передачи и хранения данных называется коллекцией. В этом руководстве показано несколько примеров сбора метрик:
- Заполнение метрик в Grafana с помощью OpenTelemetry и Prometheus.
- Просмотр метрик в режиме реального времени с помощью
dotnet-counters
- Создание пользовательского инструмента сбора с помощью базового API .NET MeterListener.
Дополнительные сведения о пользовательском инструментировании метрик и параметрах см. в разделе "Сравнение API метрик".
Необходимые компоненты
- Пакет SDK для .NET Core 3.1 или более поздней версии
Создание примера приложения
Прежде чем собирать метрики, необходимо производить измерения. В этом руководстве создается приложение с базовым инструментированием метрик. Среда выполнения .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.
Обзор
- Является проектом с открытым исходным кодом, нейтральным поставщиком, поддерживаемым Cloud Native Computing Foundation.
- Стандартизирует создание и сбор данных телеметрии для облачного программного обеспечения.
- Работает с .NET с помощью API метрик .NET.
- Поддерживается Azure Monitor и многими поставщиками APM.
В этом руководстве показана одна из интеграции, доступная для метрик OpenTelemetry с помощью проектов OSS Prometheus и Grafana . Поток данных метрик:
Api метрик .NET записывают измерения из примера приложения.
Библиотека OpenTelemetry, запущенная в приложении, объединяет измерения.
Библиотека программы экспорта Prometheus предоставляет доступ к агрегированным данным через конечную точку метрик HTTP. Программа экспорта — это библиотеки OpenTelemetry, передающие телеметрию в серверные части конкретных поставщиков.
Сервер Prometheus:
- Опрос конечной точки метрик
- Считывает данные
- Сохраняет данные в базе данных для долгосрочного сохранения. Prometheus относится к чтению и хранению данных в качестве очистки конечной точки.
- Может работать на другом компьютере
Сервер 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.
- Предоставление конечной точки метрик Prometheus на порту
Дополнительные сведения о параметрах конфигурации 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
Перезагрузите конфигурацию или перезапустите сервер Prometheus.
Убедитесь, что OpenTelemetryTest находится в состоянии UP на странице "Целевые показатели состояния>" веб-портала Prometheus.
На странице Graph веб-портала Prometheus введите
hats
текстовое поле выражения и выберитеhats_sold_Hats
На вкладке графа Prometheus отображает увеличение значения счетчика "hats-sold", который создается примером приложения.
На предыдущем изображении для графа задано значение 5 м, что составляет 5 минут.
Если сервер Prometheus не сломывает пример приложения в течение длительного времени, может потребоваться ждать, пока данные будут накапливаться.
Отображение метрик на панели мониторинга Grafana
Следуйте стандартным инструкциям по установке решения Grafana и его подключению к источнику данных Prometheus.
Создайте панель мониторинга Grafana, щелкнув значок + на панели инструментов слева на веб-портале Grafana, а затем выберите Панель мониторинга. В появившемся редакторе панели мониторинга введите Hats Sold/Sec в поле ввода заголовка и rate(hats_sold[5m]) в поле выражения PromQL:
Нажмите кнопку " Применить" , чтобы сохранить и просмотреть новую панель мониторинга.
]
Создание пользовательского средства сбора с помощью 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
вызывается для каждого существующего инструмента в процессе. Недавно созданные объекты инструментирования также активируются InstrumentPublished
для вызова.
using MeterListener meterListener = new MeterListener();
Когда приложение будет выполнено прослушивание, удаление прослушивателя останавливает поток обратных вызовов и освобождает все внутренние ссылки на объект прослушивателя. Ключевое слово, используемый при объявлении meterListener
причин Dispose
вызова, когда переменная using
выходит из область. Обратите внимание, что это только обещает, Dispose
что он не будет инициировать новые обратные вызовы. Поскольку обратные вызовы происходят в разных потоках, после возврата вызова Dispose
все еще могут присутствовать выполняющиеся обратные вызовы.
Чтобы гарантировать, что определенный регион кода в обратном вызове не выполняется в настоящее время и не будет выполняться в будущем, необходимо добавить синхронизацию потоков. Dispose
Не включает синхронизацию по умолчанию, так как:
- Синхронизация добавляет затраты на производительность при каждом обратном вызове измерения.
MeterListener
разработан как высокопроизводительный сознательный API.