メトリックを収集する
この記事の対象: ✔️ .NET Core 3.1 以降 ✔️ .NET Framework 4.6.1 以降
インストルメント化されたコードを使うと、数値の測定値を記録できますが、監視に役立つメトリックを作成するには、通常、測定値を集計し、送信し、格納する必要があります。 データの集計、送信、格納のプロセスは収集と呼ばれます。 このチュートリアルでは、メトリック収集のいくつかの例を示します。
- OpenTelemetry と Prometheus を使用した Grafana のメトリック設定。
dotnet-counters
を使用してメトリックをリアルタイムで表示する。- 基となる .NET MeterListener API を使ってカスタム収集ツールを作成する。
カスタム メトリック インストルメンテーションとオプションの詳細については、「メトリック 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 アプリのライブ メトリックをオンデマンドで表示できるコマンドライン ツールです。 これにはセットアップは必要ありません。これはアドホック調査や、メトリック インストルメンテーションが機能していることを確認するのに好都合です。 System.Diagnostics.Metrics ベースの API と EventCounters の両方で機能します。
dotnet-counters ツールがインストールされていない場合は、次のコマンドを実行します。
dotnet tool update -g dotnet-counters
サンプル アプリの実行中に、 dotnet-counters を起動します。 次のコマンドは、HatCo.HatStore
メーターからすべてのメトリックを監視する dotnet-counters
の例を示しています。 測定名は、大文字と小文字が区別されます。 サンプル アプリは 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 のメトリックの詳細については、「組み込みのメトリック」を参照してください。
OpenTelemetry と Prometheus を使って Grafana のメトリックを表示する
概要
- Cloud Native Computing Foundation によってサポートされているベンダーに依存しないオープンソース プロジェクトです。
- クラウドネイティブ ソフトウェアのテレメトリの生成と収集を標準化します。
- .NET メトリック API を使用して .NET で動作します。
- Azure Monitor と多くの APM ベンダーによって承認されています。
このチュートリアルでは、OSS Prometheus プロジェクトと Grafana プロジェクトを使用して OpenTelemetry メトリックで使用できる統合の 1 つを示します。 メトリック データ フロー:
.NET メトリック API は、サンプル アプリからの測定値を記録します。
アプリで実行されている OpenTelemetry ライブラリは、測定値を集計します。
Prometheus エクスポーター ライブラリにより、集計されたデータを HTTP メトリック エンドポイント経由で使うことができるようになります。 'エクスポーター' とは、ベンダー固有のバックエンドにテレメトリを送信するライブラリに OpenTelemetry が付けた名称です。
Prometheus サーバー:
- メトリック エンドポイントをポーリングします
- データを読み取ります
- 長期的な永続化のためにデータベースにデータを保存します。 Prometheus では、データの読み取りと保存をエンドポイントの "スクレイピング" と言います。
- 別のマシンで実行できます
Grafana サーバー:
- Prometheus に保存されているデータのクエリを実行し、それを Web ベースの監視ダッシュボードに表示します。
- 別のコンピューターで実行できます。
OpenTelemetry の Prometheus エクスポーターを使用するようにサンプル アプリを構成する
OpenTelemetry Prometheus エクスポーターへの参照をサンプル アプリンに追加します。
dotnet add package OpenTelemetry.Exporter.Prometheus.HttpListener --prerelease
注意
このチュートリアルでは、この記事の作成時点で利用可能な OpenTelemetry の Prometheus サポートのプレリリース ビルドを使用しています。
OpenTelemetry 構成を使用した Program.cs
の更新 :
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")
は、アプリで定義されている Meter によって収集されたすべてのメトリックを送信するように 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 サーバーを再起動します。
Prometheus Web ポータルの [状態]>[ターゲット] ページで OpenTelemetryTest が UP 状態であることを確認します。
Prometheus Web ポータルの Graph ページで、式テキスト ボックスに
hats
と入力し、hats_sold_Hats
を選択します。サンプル アプリによって出力されている "hats-sold" Counter の値が増加する様子を Prometheus が [Graph] タブに示します。
上の画像でグラフの時間は 5m (5 分) に設定されています。
Prometheus サーバーがサンプル アプリを長時間スクレイピングしていない場合は、データが蓄積されるまで待つ必要があります。
Grafana ダッシュボードにメトリックを表示する
標準の手順に従って Grafana をインストールし、Prometheus データソースに接続します。
Grafana Web ポータルの左側のツール バーにある + アイコンをクリックし、[Dashboard](ダッシュボード) を選んで Grafana ダッシュボードを作成します。 表示されるダッシュボード エディターで、[Title] 入力ボックスに Hats Sold/Sec と入力し、PromQL 式フィールドに rate(hats_sold[5m]) と入力します。
[Apply] をクリックして、新しいダッシュボードを保存して表示します。
]
.NET MeterListener API を使ってカスタム コレクション ツールを作成する
.NET MeterListener API を使うと、System.Diagnostics.Metrics.Meter によって記録される測定値を観察するためのカスタム インプロセス ロジックを作成できます。 以前の EventCounters インストルメンテーションと互換性のあるカスタム ロジックを作成する方法のガイダンスについては、EventCounters に関する記事を参照してください。
MeterListener を使用するように 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>(
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
キーワードにより、 meterListener
がスコープから外れると Dispose
が呼び出されます。
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);
}
};
デリゲートは、インストルメントを調べて、サブスクライブするかどうかを決定できます。 たとえば、デリゲートは、名前、Meter、またはその他のパブリック プロパティをチェックできます。 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
オブジェクトをパラメーターの 1 つとして指定できます。 state
オブジェクトは任意です。 その呼び出しで状態オブジェクトを指定すると、状態オブジェクトはそのインストルメントと共に格納され、コールバックの state
パラメーターとして返されます。 これは利便性とパフォーマンスの最適化を目的としています。 多くの場合、リスナーは次を行う必要があります。
- 測定値をメモリに格納している各インストルメントのオブジェクトを作成します。
- それらの測定値に対して計算を行うコードを作成します。
または、インストルメントからストレージ オブジェクトにマッピングする Dictionary
を作成し、すべての測定でそれを調べます。 Dictionary
を使用する方が state
からアクセスするよりもはるかに低速です。
meterListener.Start();
上記のコードは、 MeterListener
を開始し、 コールバックを有効にします。 InstrumentPublished
デリゲートは、プロセス内のすべての既存の Instrument に対して呼び出されます。 新しく作成された Instrument オブジェクトも、InstrumentPublished
の呼び出しをトリガーします。
using MeterListener meterListener = new MeterListener();
アプリのリスニングが完了したら、リスナーを破棄してコールバックのフローを停止させ、リスナー オブジェクトへのすべての内部参照を解放します。 meterListener
を宣言するときに使用した using
キーワードにより、変数がスコープから外れると Dispose
が呼び出されます。 Dispose
は、新しいコールバックを開始しないことを約束しているだけである点に注意してください。 コールバックは異なるスレッドで発生するため、Dispose
の呼び出しが戻った後もコールバックが進行中である可能性があります。
コールバック内の特定のコード領域が現在実行中でなく、今後も実行されないようにするには、スレッド同期を追加する必要があります。 既定では Dispose
に同期は含まれません。理由は次のとおりです。
- 同期により、すべての測定コールバックでパフォーマンスのオーバーヘッドが増加します。
MeterListener
は、高パフォーマンスを意識した API として設計されています。
.NET