この記事の対象: ✔️ .NET 6.0 以降 ✔️ の .NET Framework 4.6.1 以降
インストルメント化されたコードでは数値の測定値を記録できますが、通常、測定を集計、送信、保存して、監視に役立つメトリックを作成する必要があります。 このようなデータの集計、送信、格納のプロセスは収集と呼ばれます。 このチュートリアルでは、メトリックを収集するいくつかの例を示します。
- OpenTelemetry と Prometheus を使って Grafana のメトリックを設定します。
dotnet-counters
を使用してリアルタイムでメトリックを表示する- 基になる .NET MeterListener API を使用してカスタム コレクション ツールを作成する。
カスタム メトリックのインストルメンテーションとオプションの詳細については、「 メトリック API の比較」を参照してください。
前提条件
- .NET 6.0 SDK 以降
サンプル アプリを作成する
メトリックを収集する前に、測定を生成する必要があります。 このチュートリアルでは、基本的なメトリック インストルメンテーションを含むアプリを作成します。 .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 がサポートする vendor-neutral open-source プロジェクトです。
- クラウドネイティブ ソフトウェアのテレメトリの生成と収集を標準化します。
- .NET metric 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")
は、アプリで定義されているメーターによって収集されたすべてのメトリックを送信するように OpenTelemetry を構成します。AddPrometheusHttpListener
OpenTelemetry を次のように構成します。- Prometheus のメトリック エンドポイントをポートで公開する
9184
- HttpListener を使用します。
- Prometheus のメトリック エンドポイントをポートで公開する
OpenTelemetry 構成オプションの詳細については、OpenTelemetry のドキュメントを参照してください。 OpenTelemetry ドキュメントには、ASP.NET アプリのホスティング オプションが示されています。
測定値を収集できるよう、アプリを実行し、実行した状態にします。
dotnet run
Prometheus の設定と構成
Prometheus の最初の手順に従って Prometheus サーバーを設定し、動作していることを確認します。
サンプル アプリの公開するメトリック エンドポイントが Prometheus によってスクレイピングされるように Prometheus.yml 構成ファイルを変更します。 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 が、Prometheus Web ポータルの [状態]>、[ターゲット] ページの UP 状態になっているかを確認します。
Prometheus Web ポータルの [グラフ] ページで、式テキスト ボックスに「
hats
」と入力し、[グラフ] タブでhats_sold_Hats
を選択すると、Prometheus には、サンプル アプリによって出力される "hats-sold" カウンターの増加値が表示されます。
上の図では、グラフの時間は 5m (5 分) に設定されています。
Prometheus サーバーがサンプル アプリを長時間スクレイピングしていない場合は、データが蓄積されるのを待つ必要があります。
Grafana ダッシュボードにメトリックを表示する
標準の 手順 に従って Grafana をインストールし、Prometheus データ ソースに接続します。
Grafana Web ポータルの左側のツール バーにある + アイコンをクリックして Grafana ダッシュボードを作成し、[ダッシュボード] を選択 します。 表示されるダッシュボード エディターで、[タイトル] 入力ボックスに「Hats Sold/Sec」と入力し、[PromQL 式] フィールドに rate(hats_sold[5m]) と入力します。
[ 適用 ] をクリックして保存し、新しいダッシュボードを表示します。
]
.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
が呼び出されると、パラメーターの 1 つとしてstate
オブジェクトを指定できます。 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