Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Эта статья относится к ✔️ .NET 6.0 и более поздним версиям .NET Framework 4.6.1 и более поздних ✔️ версий.
Инструментированный код может записывать числовые измерения, но измерения обычно необходимо агрегировать, передавать и хранить для создания полезных метрик для мониторинга. Процесс агрегирования, передачи и хранения данных называется коллекцией. В этом руководстве показано несколько примеров сбора метрик:
- Заполнение метрик в Grafana с помощью OpenTelemetry и Prometheus.
- Просмотр метрик в режиме реального времени с помощью
dotnet-counters
- Создание пользовательского инструмента сбора данных с помощью основного API .NET MeterListener.
Дополнительные сведения о пользовательском инструментировании метрик и параметрах см. в разделе "Сравнение API метрик".
Предпосылки
- Пакет SDK для .NET 6.0 или более поздней версии
Создание примера приложения
Прежде чем собирать метрики, необходимо производить измерения. В этом руководстве создается приложение с базовым инструментированием метрик. Среда выполнения .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
вызывается для каждого ранее существующего инструмента в процессе. Недавно созданные объекты Instrument также вызывают активацию InstrumentPublished
.
using MeterListener meterListener = new MeterListener();
Когда приложение завершит прослушивание, удаление прослушивателя останавливает процесс обратных вызовов и освобождает все внутренние ссылки на объект прослушивателя. Ключевое слово using
, используемое при объявлении meterListener
, вызывает Dispose
, когда переменная выходит из области видимости. Обратите внимание, что Dispose
обещает только не инициировать новые обратные вызовы. Так как обратные вызовы происходят в разных потоках, после возврата вызова могут по-прежнему выполняться обратные вызовы Dispose
.
Чтобы гарантировать, что определенный регион кода в обратном вызове не выполняется в настоящее время и не будет выполняться в будущем, необходимо добавить синхронизацию потоков.
Dispose
Не включает синхронизацию по умолчанию, так как:
- Синхронизация добавляет затраты ресурсов при каждом вызове функции измерения.
-
MeterListener
разработан как API с высокой производительностью и учетом эффективности.