メトリックの作成
この記事の対象: ✔️ .NET Core 6 以降のバージョン ✔️ .NET Framework 4.6.1 以降のバージョン
.NET アプリケーションは、System.Diagnostics.Metrics API を使用してインストルメント化して、重要なメトリックを追跡できます。 一部のメトリックは標準の .NET ライブラリに含まれていますが、アプリケーションとライブラリに関連する新しいカスタム メトリックを追加することもできます。 このチュートリアルでは、新しいメトリックを追加し、使用できるメトリックの種類を理解します。
手記
.NET には、EventCounters と System.Diagnostics.PerformanceCounterなど、いくつかの古いメトリック API があります。ここでは説明しません。 これらの代替方法の詳細については、「メトリック APIの比較」を参照してください。
カスタム メトリックを作成する
前提条件: .NET Core 6 SDK またはその後のバージョンのいずれか
バージョン 8 以上の System.Diagnostics.DiagnosticSource NuGet パッケージ 参照する新しいコンソール アプリケーションを作成します。 .NET 8 以降を対象とするアプリケーションには、既定でこの参照が含まれています。 次に、Program.cs
のコードを一致するように更新します。
> dotnet new console
> dotnet add package System.Diagnostics.DiagnosticSource
using System;
using System.Diagnostics.Metrics;
using System.Threading;
class Program
{
static Meter s_meter = new Meter("HatCo.Store");
static Counter<int> s_hatsSold = s_meter.CreateCounter<int>("hatco.store.hats_sold");
static void Main(string[] args)
{
Console.WriteLine("Press any key to exit");
while(!Console.KeyAvailable)
{
// Pretend our store has a transaction each second that sells 4 hats
Thread.Sleep(1000);
s_hatsSold.Add(4);
}
}
}
System.Diagnostics.Metrics.Meter タイプは、ライブラリが名前付きインストルメントのグループを作成するためのエントリ ポイントです。 インストルメントは、メトリックの計算に必要な数値の測定値を記録します。 ここでは、CreateCounter を使用して、"hatco.store.hats_sold" という名前のカウンター インストルメントを作成しました。 各模擬的な取引の際に、コードは販売された帽子を記録するために Add を呼び出します(この場合、販売数は 4 です)。 "hatco.store.hats_sold" インストルメントは、これらの測定値から計算できるメトリック (販売された帽子の合計数や 1 秒当りの帽子など) を暗黙的に定義します。最終的には、計算するメトリックとその計算の実行方法を決定するのはメトリック 収集ツールにかかっていますが、各インストルメントには、開発者の意図を伝える既定の規則がいくつかあります。 カウンター インストルメントの場合、コレクション ツールには、カウントの合計やカウントが増加しているレートが表示されます。
Counter<int>
と CreateCounter<int>(...)
に int
ジェネリック パラメーターは、このカウンターが最大 Int32.MaxValue値を格納できる必要があることを定義します。 格納する必要があるデータのサイズと小数部の値が必要かどうかに応じて、byte
、short
、int
、long
、float
、double
、または decimal
のいずれかを使用できます。
アプリを実行し、今のところ実行したままにします。 メトリックは次に表示されます。
> dotnet run
Press any key to exit
ベスト プラクティス
依存関係挿入 (DI) コンテナーで使用するように設計されていないコードの場合は、Meter を 1 回作成し、静的変数に格納します。 DI 対応ライブラリで使用する場合、静的変数はアンチパターンと見なされ、次の
DI の例は、より慣用的なアプローチを示しています。 各ライブラリまたはライブラリサブコンポーネントは、独自の Meterを作成できます (多くの場合、作成する必要があります)。 アプリ開発者がメトリックのグループを個別に簡単に有効または無効にできることを期待する場合は、既存の測定を再利用するのではなく、新しいメーターを作成することを検討してください。 Meter コンストラクターに渡される名前は、他の Meters と区別するために一意である必要があります。 ドット階層名を使用する OpenTelemetry の名前付けガイドラインをお勧めします。 インストルメント化されるコードのアセンブリ名または名前空間名は、通常、適切な選択肢です。 アセンブリが 2 番目の独立したアセンブリ内のコードのインストルメンテーションを追加する場合、名前は、コードがインストルメント化されるアセンブリではなく、Meter を定義するアセンブリに基づいている必要があります。
.NET では、Instruments の名前付けスキームは適用されませんが、OpenTelemetry の名前付けガイドラインに従うことをお勧めします。このガイドラインでは、小文字の点線の階層名とアンダースコア('_')を同じ要素内の複数の単語間の区切り記号として使用します。 すべてのメトリック ツールで最終的なメトリック名の一部として測定名が保持されるわけではないため、インストルメント名を単独でグローバルに一意にすることが有益です。
インストルメント名の例:
contoso.ticket_queue.duration
contoso.reserved_tickets
contoso.purchased_tickets
インストルメントを作成し、測定を記録する API はスレッド セーフです。 .NET ライブラリでは、ほとんどのインスタンス メソッドは、複数のスレッドから同じオブジェクトで呼び出されたときに同期を必要としますが、この場合は必要ありません。
測定を記録するインストルメント API (この例ではAdd) は、通常、データが収集されていない場合は <10 ns で実行され、高パフォーマンスのコレクション ライブラリまたはツールによって測定が収集される場合は数十から数百ナノ秒で実行されます。 これにより、ほとんどの場合、これらの API を自由に使用できますが、パフォーマンスが非常に重要なコードには注意してください。
新しいメトリックを表示する
メトリックの格納と表示には、多くのオプションがあります。 このチュートリアルでは、アドホック分析に役立つ dotnet-counters ツールを使用します。 他の代替手段については、メトリック収集のチュートリアル も参照してください。 dotnet-counters ツールがまだインストールされていない場合は、SDK を使用してインストールします。
> dotnet tool update -g dotnet-counters
You can invoke the tool using the following command: dotnet-counters
Tool 'dotnet-counters' (version '7.0.430602') was successfully installed.
サンプル アプリがまだ実行されている間は、dotnet カウンターを使用して新しいカウンターを監視します。
> dotnet-counters monitor -n metric-demo.exe --counters HatCo.Store
Press p to pause, r to resume, q to quit.
Status: Running
[HatCo.Store]
hatco.store.hats_sold (Count / 1 sec) 4
予想通り、HatCo ストアでは 1 秒ごとに 4 個の帽子が着実に販売されていることがわかります。
依存関係の挿入を使用してメーターを取得する
前の例では、meter は、new
を使用して構築し、静的フィールドに割り当てることによって取得されました。 依存関係挿入 (DI) を使用する場合、この方法で静的を使用することは適切なアプローチではありません。 ASP.NET Core や汎用ホストを使用するアプリなど、DI を使用するコードでは、IMeterFactoryを使用して Meter オブジェクトを作成します。 .NET 8 以降では、ホストはサービス コンテナーに IMeterFactory を自動的に登録するか、AddMetricsを呼び出して任意の IServiceCollection に型を手動で登録できます。
メーター ファクトリはメトリックを DI と統合し、同じ名前を使用する場合でも、メーターを異なるサービス コレクションに分離します。 これは、並列で実行されている複数のテストが同じテスト ケース内から生成された測定値のみを観察するようにテストする場合に特に便利です。
DI 用に設計された型で Meter を取得するには、コンストラクターに IMeterFactory パラメーターを追加し、Createを呼び出します。 この例では、ASP.NET Core アプリでの IMeterFactory の使用を示します。
インストルメントを保持する型を定義します。
public class HatCoMetrics
{
private readonly Counter<int> _hatsSold;
public HatCoMetrics(IMeterFactory meterFactory)
{
var meter = meterFactory.Create("HatCo.Store");
_hatsSold = meter.CreateCounter<int>("hatco.store.hats_sold");
}
public void HatsSold(int quantity)
{
_hatsSold.Add(quantity);
}
}
Program.cs
の DI コンテナーに型を登録します。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<HatCoMetrics>();
必要に応じ、メトリックの種類とレコード値を挿入します。 メトリックの種類は DI に登録されているため、MVC コントローラー、最小限の API、または DI によって作成されるその他の種類で使用できます。
app.MapPost("/complete-sale", ([FromBody] SaleModel model, HatCoMetrics metrics) =>
{
// ... business logic such as saving the sale to a database ...
metrics.HatsSold(model.QuantitySold);
});
ベスト プラクティス
- System.Diagnostics.Metrics.Meter は IDisposable を実装しますが、IMeterFactory は、作成するすべての
Meter
オブジェクトの有効期間を自動的に管理し、DI コンテナーが破棄されたときに破棄します。Meter
でDispose()
を呼び出すコードを追加する必要はありません。効果はありません。
インストルメントの種類
ここまでは、Counter<T> の楽器のみを紹介してきましたが、使用できる楽器の種類は他にもあります。 楽器は次の 2 つの方法で異なります。
- 既定のメトリック計算 - インストルメントの測定値を収集して分析するツールは、インストルメントに応じて異なる既定のメトリックを計算します。
- 集計データのストレージ - 最も有用なメトリックは、多くの測定値からデータを集計する必要があります。 1 つのオプションは、呼び出し元が任意の時間に個別の測定値を提供し、コレクション ツールが集計を管理することです。 または、呼び出し元が集計測定を管理し、コールバックでオンデマンドで提供することもできます。
現在利用可能なインストルメントの種類:
カウンター (CreateCounter) - このインストルメントは、時間の経過に伴って増加する値を追跡し、呼び出し元は Addを使用して増分を報告します。 ほとんどのツールでは、合計と合計の変化率が計算されます。 1つの対象のみを表示するツールについては、変化率を推奨します。 たとえば、呼び出し元が、連続する値 1、2、4、5、4、3 で 1 秒ごとに 1 回、
Add()
を呼び出すとします。 コレクション ツールが 3 秒ごとに更新される場合、3 秒後の合計は 1+2+4=7、6 秒後の合計は 1+2+4+5+4+3=19 になります。 変化率は (current_total - previous_total) であるため、ツールは3秒で 7-0=7 を報告し、6秒後に19-7=12を報告します。UpDownCounter (CreateUpDownCounter) - このインストルメントは、時間の経過と同時に増減する可能性のある値を追跡します。 呼び出し元は、Addを使用してインクリメントとデクリメントを通知します。 たとえば、呼び出し元が、連続する値 1、5、-2、3、-1、-3 で 1 秒ごとに 1 回、
Add()
を呼び出すとします。 コレクション ツールが 3 秒ごとに更新される場合、3 秒後の合計は 1+5-2=4、6 秒後の合計は 1+5-2+3-1-3=3 になります。ObservableCounter (CreateObservableCounter) - このインストルメントは Counter に似ていますが、呼び出し元が集計された合計を維持する役割を担うようになった点が異なります。 呼び出し元は、ObservableCounter が作成されたときにコールバック デリゲートを提供し、ツールが現在の合計を監視する必要がある場合は常にコールバックが呼び出されます。 たとえば、コレクション ツールが 3 秒ごとに更新される場合、コールバック関数も 3 秒ごとに呼び出されます。 ほとんどのツールは、使用可能な合計と変更率の両方を持ちます。 表示できる値が1つしかない場合は、変化率をお勧めします。 コールバックが最初の呼び出しで 0、3 秒後に再度呼び出されたときに 7、6 秒後に呼び出されたときに 19 を返す場合、ツールはそれらの値を合計として変更せずに報告します。 変更率の場合、ツールは 3 秒後に 7-0=7、6 秒後に 19-7=12 と表示されます。
ObservableUpDownCounter (CreateObservableUpDownCounter) - このインストルメントは UpDownCounter に似ていますが、呼び出し元が集計された合計を維持する役割を担うようになった点が異なります。 呼び出し元は、ObservableUpDownCounter が作成されたときにコールバック デリゲートを提供し、ツールが現在の合計を確認する必要がある場合は常にコールバックが呼び出されます。 たとえば、コレクション ツールが 3 秒ごとに更新される場合、コールバック関数も 3 秒ごとに呼び出されます。 コールバックによって返された値は、コレクション ツールに合計として変更されずに表示されます。
ゲージ (CreateGauge) - このインストルメントにより、呼び出し元は、Record メソッドを使用してメトリックの現在の値を設定できます。 この値は、メソッドを再度呼び出すことによっていつでも更新でき、メトリック コレクション ツールには、最近設定された値が表示されます。
ObservableGauge (CreateObservableGauge) - このインストルメントにより、呼び出し元は、測定値がメトリックとして直接渡されるコールバックを提供できます。 コレクション ツールが更新されるたびにコールバックが呼び出され、コールバックによって返される値がツールに表示されます。
ヒストグラム (CreateHistogram) - この装置は、測定値の分布を追跡します。 一連の測定値を記述する標準的な方法は 1 つではありませんが、ヒストグラムまたは計算されたパーセンタイルを使用するツールをお勧めします。 たとえば、呼び出し元が収集ツールの更新間隔 (1,5,2,3,10,9,7,4,6,8) の間にこれらの測定値を記録するために Record を呼び出したとします。 コレクション ツールでは、これらの測定値の 50 番目、90 番目、95 番目のパーセンタイルがそれぞれ 5、9、9 であると報告される場合があります。
手記
ヒストグラムインストルメントの作成時に推奨されるバケット境界を設定する方法の詳細については、「アドバイスを使用してヒストグラムのをカスタマイズする
」を参照してください。
インストルメントの種類を選択するときのベスト プラクティス
数える対象や時間の経過と共に増える他の値の場合は、Counter または ObservableCounter を使用します。 既存のコードに追加しやすいもの (インクリメント操作ごとに API 呼び出しを行うか、コードが保持する変数から現在の合計を読み取るコールバック) に応じて、Counter と ObservableCounter のどちらかを選択します。 パフォーマンスが重要であり、Add を使用するとスレッドあたり 1 秒あたり 100 万回を超える呼び出しが作成される非常にホットなコード パスでは、ObservableCounter を使用すると、最適化の機会が増える可能性があります。
タイミングについては、通常、ヒストグラムが推奨されます。 多くの場合、平均値や合計ではなく、これらの分布の末尾 (90、95、99 パーセンタイル) を理解すると便利です。
キャッシュ ヒット 率やキャッシュ、キュー、ファイルのサイズなど、その他の一般的なケースは、通常、
UpDownCounter
またはObservableUpDownCounter
に適しています。 既存のコードに追加する方が簡単であるか (インクリメントとデクリメントの各操作の API 呼び出しか、コードが保持する変数から現在の値を読み取るコールバック) に応じて、それらを選択します。
手記
以前のバージョンの .NET または UpDownCounter
と ObservableUpDownCounter
(バージョン 7 より前) をサポートしていない DiagnosticSource NuGet パッケージを使用している場合は、多くの場合、ObservableGauge
が代わりに適しています。
異なる楽器の種類の例
前に開始したサンプル プロセスを停止し、Program.cs
のコード例を次のように置き換えます。
using System;
using System.Diagnostics.Metrics;
using System.Threading;
class Program
{
static Meter s_meter = new Meter("HatCo.Store");
static Counter<int> s_hatsSold = s_meter.CreateCounter<int>("hatco.store.hats_sold");
static Histogram<double> s_orderProcessingTime = s_meter.CreateHistogram<double>("hatco.store.order_processing_time");
static int s_coatsSold;
static int s_ordersPending;
static Random s_rand = new Random();
static void Main(string[] args)
{
s_meter.CreateObservableCounter<int>("hatco.store.coats_sold", () => s_coatsSold);
s_meter.CreateObservableGauge<int>("hatco.store.orders_pending", () => s_ordersPending);
Console.WriteLine("Press any key to exit");
while(!Console.KeyAvailable)
{
// Pretend our store has one transaction each 100ms that each sell 4 hats
Thread.Sleep(100);
s_hatsSold.Add(4);
// Pretend we also sold 3 coats. For an ObservableCounter we track the value in our variable and report it
// on demand in the callback
s_coatsSold += 3;
// Pretend we have some queue of orders that varies over time. The callback for the orders_pending gauge will report
// this value on-demand.
s_ordersPending = s_rand.Next(0, 20);
// Last we pretend that we measured how long it took to do the transaction (for example we could time it with Stopwatch)
s_orderProcessingTime.Record(s_rand.Next(5, 15)/1000.0);
}
}
}
新しいプロセスを実行し、2 番目のシェルで前と同様に dotnet-counters を使用してメトリックを表示します。
> dotnet-counters monitor -n metric-demo.exe --counters HatCo.Store
Press p to pause, r to resume, q to quit.
Status: Running
Name Current Value
[HatCo.Store]
hatco.store.coats_sold (Count) 8,181
hatco.store.hats_sold (Count) 548
hatco.store.order_processing_time
Percentile
50 0.012
95 0.013
99 0.013
hatco.store.orders_pending 9
この例では、ランダムに生成された数値をいくつか使用するため、値は少し異なります。 Dotnet カウンターでは、ヒストグラム のインストルメントは 3 パーセンタイル統計 (50 番目、95 番目、99 番目) としてレンダリングされますが、他のツールでは分布の概要が異なる場合や、より多くの構成オプションが提供される場合があります。
ベスト プラクティス
ヒストグラムは、他のメトリックの種類よりも多くのデータをメモリに格納する傾向があります。 ただし、正確なメモリ使用量は、使用されているコレクション ツールによって決まります。 ヒストグラム メトリックの数が多い (>100) 場合は、それらを同時に有効にしたり、精度を下げることでメモリを節約するようにツールを構成したりしないようにユーザーにガイダンスを提供することが必要になる場合があります。 一部のコレクション ツールでは、過剰なメモリ使用を防ぐために監視する同時ヒストグラムの数にハード制限がある場合があります。
すべての監視可能なインストルメントのコールバックは順番に呼び出されるため、時間がかかるコールバックは遅延したり、すべてのメトリックが収集されないようにしたりできます。 キャッシュされた値をすばやく読み取ることや、測定値を返さないこと、または例外をスローすることを優先し、長時間実行される可能性のある操作やブロック操作は避けてください。
ObservableCounter、ObservableUpDownCounter、および ObservableGauge コールバックは、通常は値を更新するコードと同期されていないスレッドで発生します。 メモリ アクセスを同期するか、同期されていないアクセスを使用した結果として生じる可能性のある一貫性のない値を受け入れる必要があります。 アクセスを同期する一般的な方法は、ロックまたは呼び出し Volatile.Read と Volatile.Writeを使用する方法です。
CreateObservableGauge 関数と CreateObservableCounter 関数は、インストルメント オブジェクトを返しますが、ほとんどの場合、オブジェクトとのそれ以上の対話は必要ないため、変数に保存する必要はありません。 他のインストルメントと同様に静的変数に割り当てることは有効ですが、エラーが発生しやすくなります。C# の静的初期化は遅延であり、変数は通常参照されないためです。 問題の例を次に示します。
using System; using System.Diagnostics.Metrics; class Program { // BEWARE! Static initializers only run when code in a running method refers to a static variable. // These statics will never be initialized because none of them were referenced in Main(). // static Meter s_meter = new Meter("HatCo.Store"); static ObservableCounter<int> s_coatsSold = s_meter.CreateObservableCounter<int>("hatco.store.coats_sold", () => s_rand.Next(1,10)); static Random s_rand = new Random(); static void Main(string[] args) { Console.ReadLine(); } }
説明と単位
インストルメントでは、オプションの説明と単位を指定できます。 これらの値はすべてのメトリック計算に対して不透明ですが、エンジニアがデータを解釈する方法を理解するのに役立つ収集ツール UI に表示できます。 前に開始したサンプル プロセスを停止し、Program.cs
のコード例を次のように置き換えます。
using System;
using System.Diagnostics.Metrics;
using System.Threading;
class Program
{
static Meter s_meter = new Meter("HatCo.Store");
static Counter<int> s_hatsSold = s_meter.CreateCounter<int>(name: "hatco.store.hats_sold",
unit: "{hats}",
description: "The number of hats sold in our store");
static void Main(string[] args)
{
Console.WriteLine("Press any key to exit");
while(!Console.KeyAvailable)
{
// Pretend our store has a transaction each 100ms that sells 4 hats
Thread.Sleep(100);
s_hatsSold.Add(4);
}
}
}
新しいプロセスを実行し、2 番目のシェルで前と同様に dotnet-counters を使用してメトリックを表示します。
Press p to pause, r to resume, q to quit.
Status: Running
Name Current Value
[HatCo.Store]
hatco.store.hats_sold ({hats}) 40
dotnet-counters は現在、UI で説明テキストを使用していませんが、ユニットが指定されると表示されます。 この場合は、"{hats}" が、前の説明に表示されている一般用語 "Count" に置き換えられています。
ベスト プラクティス
.NET API では、任意の文字列を単位として使用できますが、単位名の国際標準 UCUMを使用することをお勧めします。 "{hats}" の周りの中かっこは UCUM 標準の一部であり、秒やバイトなどの標準化された意味を持つ単位名ではなく、説明的な注釈であることを示しています。
コンストラクターで指定する単位は、個々の測定に適した単位を記述する必要があります。 これは、最終的に報告されたメトリックの単位と異なる場合があります。 この例では、各測定値は多数の帽子であるため、コンストラクターで渡す適切な単位は "{hats}" です。 収集ツールは、変化率を計算し、計算されたレート メトリックの適切な単位が {hats}/sec であることを独自に導き出すことができました。
時間の測定値を記録する場合は、浮動小数点または倍精度浮動小数点値として記録された秒数を優先します。
多次元メトリック
測定は、分析のためにデータを分類できるようにするタグと呼ばれるキーと値のペアに関連付けることもできます。 たとえば、HatCo では、販売された帽子の数だけでなく、そのサイズと色も記録したい場合があります。 後でデータを分析する場合、HatCo のエンジニアは、サイズ、色、またはその両方の任意の組み合わせによって合計を分割できます。
カウンタータグとヒストグラムタグは、1 つ以上の KeyValuePair
引数を受け取る Add および Record のオーバーロードで指定できます。 例えば:
s_hatsSold.Add(2,
new KeyValuePair<string, object?>("product.color", "red"),
new KeyValuePair<string, object?>("product.size", 12));
Program.cs
のコードを置き換え、前と同様にアプリと dotnet-counters を再実行します。
using System;
using System.Collections.Generic;
using System.Diagnostics.Metrics;
using System.Threading;
class Program
{
static Meter s_meter = new Meter("HatCo.Store");
static Counter<int> s_hatsSold = s_meter.CreateCounter<int>("hatco.store.hats_sold");
static void Main(string[] args)
{
Console.WriteLine("Press any key to exit");
while(!Console.KeyAvailable)
{
// Pretend our store has a transaction, every 100ms, that sells two size 12 red hats, and one size 19 blue hat.
Thread.Sleep(100);
s_hatsSold.Add(2,
new KeyValuePair<string,object?>("product.color", "red"),
new KeyValuePair<string,object?>("product.size", 12));
s_hatsSold.Add(1,
new KeyValuePair<string,object?>("product.color", "blue"),
new KeyValuePair<string,object?>("product.size", 19));
}
}
}
Dotnet カウンターに基本的な分類が表示されるようになりました。
Press p to pause, r to resume, q to quit.
Status: Running
Name Current Value
[HatCo.Store]
hatco.store.hats_sold (Count)
product.color product.size
blue 19 73
red 12 146
ObservableCounter と ObservableGauge の場合、コンストラクターに渡されるコールバックでタグ付けされた測定値を指定できます。
using System;
using System.Collections.Generic;
using System.Diagnostics.Metrics;
using System.Threading;
class Program
{
static Meter s_meter = new Meter("HatCo.Store");
static void Main(string[] args)
{
s_meter.CreateObservableGauge<int>("hatco.store.orders_pending", GetOrdersPending);
Console.WriteLine("Press any key to exit");
Console.ReadLine();
}
static IEnumerable<Measurement<int>> GetOrdersPending()
{
return new Measurement<int>[]
{
// pretend these measurements were read from a real queue somewhere
new Measurement<int>(6, new KeyValuePair<string,object?>("customer.country", "Italy")),
new Measurement<int>(3, new KeyValuePair<string,object?>("customer.country", "Spain")),
new Measurement<int>(1, new KeyValuePair<string,object?>("customer.country", "Mexico")),
};
}
}
前と同様に dotnet-counters を使用して実行すると、結果は次のようになります。
Press p to pause, r to resume, q to quit.
Status: Running
Name Current Value
[HatCo.Store]
hatco.store.orders_pending
customer.country
Italy 6
Mexico 1
Spain 3
ベスト プラクティス
API では任意のオブジェクトをタグ値として使用できますが、数値型と文字列はコレクション ツールによって予測されます。 他の型は、特定のコレクション ツールでサポートされている場合とサポートされていない場合があります。
タグ名は、OpenTelemetry の名前付けガイドラインに従うことをお勧めします。このガイドラインでは、小文字の点線の階層名と '_' 文字を使用して、同じ要素内の複数の単語を区切ります。 タグ名が異なるメトリックまたはその他のテレメトリ レコードで再利用される場合、タグ名は、使用されるすべての場所で同じ意味と一連の有効な値を持つ必要があります。
タグ名の例:
customer.country
store.payment_method
store.purchase_result
実際に記録されるタグ値の非常に大きな組み合わせまたは無制限の組み合わせを持つことに注意してください。 .NET API の実装ではそれを処理できますが、コレクション ツールは各タグの組み合わせに関連付けられているメトリック データにストレージを割り当てる可能性があり、これは非常に大きくなる可能性があります。 たとえば、HatCo に 10 種類の帽子の色と 25 個の帽子サイズがあり、最大 10*25=250 の売上合計が追跡できる場合は問題ありません。しかし、HatCo が販売用の CustomerID である 3 つ目のタグを追加し、世界中の 1 億人の顧客に販売している場合、数十億もの異なるタグの組み合わせが記録される可能性が高くなります。 ほとんどのメトリック収集ツールは、技術的な制限内に収まるようにデータをドロップするか、データのストレージと処理をカバーするために大きな金銭的コストが発生する可能性があります。 各コレクション ツールの実装によってその制限が決まりますが、1 つのインストルメントの組み合わせが 1,000 未満の場合は安全です。 1,000 を超える組み合わせでは、コレクション ツールでフィルター処理を適用するか、大規模に動作するように設計する必要があります。 ヒストグラムの実装では、他のメトリックよりもはるかに多くのメモリを使用する傾向があるため、安全な制限は 10 ~ 100 倍低くなる可能性があります。 多数の一意のタグの組み合わせが予想される場合は、ログ、トランザクション データベース、またはビッグ データ処理システムが、必要なスケールで動作するためのより適切なソリューションになる可能性があります。
タグの組み合わせが非常に多いインストルメントの場合は、メモリのオーバーヘッドを減らすために、ストレージの種類を小さくすることを好みます。 たとえば、
Counter<short>
のshort
を格納する場合、タグの組み合わせごとに 2 バイトしか占有されますが、Counter<double>
のdouble
はタグの組み合わせごとに 8 バイトを占有します。コレクション ツールは、同じインストルメントで測定値を記録する呼び出しごとに同じ順序で同じタグ名のセットを指定するコードに最適化することをお勧めします。 Add を頻繁に呼び出して Record する必要がある高パフォーマンス コードの場合は、呼び出しごとに同じ一連のタグ名を使用することをお勧めします。
.NET API は、3 つ以下のタグを個別に指定した Add および Record 呼び出し用に割り当て不要に最適化されています。 タグの数が多い割り当てを回避するには、TagListを使用します。 一般に、これらの呼び出しのパフォーマンス オーバーヘッドは、使用されるタグが増えるにつれて増加します。
手記
OpenTelemetry はタグを "属性" と参照します。 これらは、同じ機能の 2 つの異なる名前です。
アドバイスを使用してヒストグラムのインストルメントをカスタマイズする
ヒストグラムを使用する場合は、ツールまたはライブラリがデータを収集して、記録された値の分布を最も適切に表す方法を決定する必要があります。 一般的な戦略 (および OpenTelemetryを使用する場合の既定のモード
ヒストグラム データを収集するツールまたはライブラリは、使用するバケットを定義する役割を担います。 OpenTelemetry を使用する場合の既定のバケット構成は、[ 0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000 ]
です。
既定値は、すべてのヒストグラムに最適な細分性につながるとは限りません。 たとえば、1秒未満のリクエスト時間はすべて、0
バケットに入ります。
ヒストグラム データを収集するツールまたはライブラリには、ユーザーがバケット構成をカスタマイズできるようにするメカニズムが用意されている場合があります。 たとえば、OpenTelemetry では、View APIを定義します。 ただし、これにはエンド ユーザーのアクションが必要であり、適切なバケットを選択するのに十分な量のデータ分散を理解する必要があります。
エクスペリエンスを向上させるために、System.Diagnostics.DiagnosticSource
パッケージの 9.0.0
バージョンで (InstrumentAdvice<T>) API が導入されました。
InstrumentAdvice
API は、インストルメンテーション作成者が、特定のヒストグラムに推奨される既定のバケット境界のセットを指定するために使用できます。 ヒストグラム データを収集するツールまたはライブラリは、集計を構成するときにこれらの値を使用して、ユーザーにとってよりシームレスなオンボード エクスペリエンスを実現できます。 これは、バージョン 1.10.0以降の
重要
一般に、バケットが多いほど、特定のヒストグラムのより正確なデータが得られますが、各バケットには集計された詳細を格納するためのメモリが必要であり、測定を処理するときに適切なバケットを見つけるための CPU コストが発生します。 InstrumentAdvice
API を使用して推奨するバケットの数を選択するときは、精度と CPU/メモリ消費量のトレードオフを理解することが重要です。
次のコードは、InstrumentAdvice
API を使用して推奨される既定のバケットを設定する例を示しています。
using System;
using System.Diagnostics.Metrics;
using System.Threading;
class Program
{
static Meter s_meter = new Meter("HatCo.Store");
static Histogram<double> s_orderProcessingTime = s_meter.CreateHistogram<double>(
name: "hatco.store.order_processing_time",
unit: "s",
description: "Order processing duration",
advice: new InstrumentAdvice<double> { HistogramBucketBoundaries = [0.01, 0.05, 0.1, 0.5, 1, 5] });
static Random s_rand = new Random();
static void Main(string[] args)
{
Console.WriteLine("Press any key to exit");
while (!Console.KeyAvailable)
{
// Pretend our store has one transaction each 100ms
Thread.Sleep(100);
// Pretend that we measured how long it took to do the transaction (for example we could time it with Stopwatch)
s_orderProcessingTime.Record(s_rand.Next(5, 15) / 1000.0);
}
}
}
追加情報
OpenTelemetry の明示的なバケット ヒストグラムの詳細については、次を参照してください。
カスタム メトリックをテストする
MetricCollector<T>を使用して追加するカスタム メトリックをテストできます。 このタイプは特定の器械からの測定を記録し、値が正しいことを主張することを容易にする。
依存関係の挿入を使用したテスト
次のコードは、依存関係の挿入と IMeterFactory を使用するコード コンポーネントのテスト ケースの例を示しています。
public class MetricTests
{
[Fact]
public void SaleIncrementsHatsSoldCounter()
{
// Arrange
var services = CreateServiceProvider();
var metrics = services.GetRequiredService<HatCoMetrics>();
var meterFactory = services.GetRequiredService<IMeterFactory>();
var collector = new MetricCollector<int>(meterFactory, "HatCo.Store", "hatco.store.hats_sold");
// Act
metrics.HatsSold(15);
// Assert
var measurements = collector.GetMeasurementSnapshot();
Assert.Equal(1, measurements.Count);
Assert.Equal(15, measurements[0].Value);
}
// Setup a new service provider. This example creates the collection explicitly but you might leverage
// a host or some other application setup code to do this as well.
private static IServiceProvider CreateServiceProvider()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddMetrics();
serviceCollection.AddSingleton<HatCoMetrics>();
return serviceCollection.BuildServiceProvider();
}
}
各 MetricCollector オブジェクトは、1 つの Instrument のすべての測定値を記録します。 複数のインストルメントからの測定値を確認する必要がある場合は、それぞれに 1 つの MetricCollector を作成します。
依存関係の挿入なしでテストする
静的フィールドで共有グローバル Meter オブジェクトを使用するコードをテストすることもできますが、そのようなテストが並列で実行されないように構成されていることを確認します。 Meter オブジェクトは共有されているため、1 つのテストの MetricCollector は、並列で実行されている他のテストから作成された測定値を観察します。
class HatCoMetricsWithGlobalMeter
{
static Meter s_meter = new Meter("HatCo.Store");
static Counter<int> s_hatsSold = s_meter.CreateCounter<int>("hatco.store.hats_sold");
public void HatsSold(int quantity)
{
s_hatsSold.Add(quantity);
}
}
public class MetricTests
{
[Fact]
public void SaleIncrementsHatsSoldCounter()
{
// Arrange
var metrics = new HatCoMetricsWithGlobalMeter();
// Be careful specifying scope=null. This binds the collector to a global Meter and tests
// that use global state should not be configured to run in parallel.
var collector = new MetricCollector<int>(null, "HatCo.Store", "hatco.store.hats_sold");
// Act
metrics.HatsSold(15);
// Assert
var measurements = collector.GetMeasurementSnapshot();
Assert.Equal(1, measurements.Count);
Assert.Equal(15, measurements[0].Value);
}
}
.NET