メトリックの作成

この記事の対象: ✔️ .NET Core 6 以降のバージョン ✔️ .NET Framework 4.6.1 以降のバージョン

System.Diagnostics.Metrics API を使用して .NET アプリケーションをインストルメント化し、重要なメトリックを追跡することができます。 標準の .NET ライブラリにはいくつかのメトリックが含まれていますが、アプリケーションやライブラリに関連する新しいカスタム メトリックを追加することもできます。 このチュートリアルでは、新しいメトリックを追加して、使用可能なメトリックの種類を理解します。

注意

.NET には、ここでは説明されていない、いくつかの古いメトリック API (EventCountersSystem.Diagnostics.PerformanceCounter) があります。 これらの代替の詳細については、メトリック API の比較に関するページを参照してください。

カスタム メトリックを作成する

前提条件: .NET Core 6 SDK 以降のバージョン

System.Diagnostics.DiagnosticSource NuGet パッケージ バージョン 8 以降を参照する新しいコンソール アプリケーションを作成します。 .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" という名前の Counter インストルメントを作成しました。 偽の各トランザクションの実行中、コードは Add を呼び出して、販売された帽子の測定値を記録します。この場合は 4 です。 "hatco.store.hats_sold" のインストルメントによって、これらの測定値から計算されるいくつかのメトリック (たとえば、販売された帽子の総数や、1 秒あたりに販売された帽子の数など) が暗黙的に定義されます。最終的には、計算するメトリックとその計算の実行方法を決定するのはメトリック コレクション ツール次第ですが、各インストルメントには、開発者の意図を伝える既定の規約がいくつかあります。 Counter インストルメントの場合、規約では、コレクション ツールによって合計数、または数が増加する割合が表示されます。

Counter<int> および CreateCounter<int>(...) のジェネリック パラメーター int は、このカウンターが Int32.MaxValue までの値を格納できる必要があることを定義します。 格納する必要のあるデータのサイズと、小数部の値が必要かどうかによって、byteshortintlongfloatdouble、または decimal のいずれかを使用できます。

アプリを実行し、ここでは実行したままにします。 次にメトリックを表示します。

> dotnet run
Press any key to exit

ベスト プラクティス

  • 依存関係の挿入 (DI) コンテナーで使用するように設計されていないコードの場合は、Meter を一度作成し、静的変数に格納します。 DI 対応ライブラリで使用する場合、静的変数はアンチパターンと見なされます。以下の DI の例では、より慣用的なアプローチを示しています。 各ライブラリまたはライブラリのサブコンポーネントでは、独自の Meter を作成することができます (多くの場合、そうする必要があります)。 メトリックのグループを個別に簡単に有効化または無効化できることをアプリ開発者が希望する場合は、既存の Meter を再利用するのではなく、新しい Meter を作成することを検討してください。

  • Meter コンストラクターに渡される名前は、他の Meter と区別するために一意である必要があります。 ドット形式の階層名を使用する OpenTelemetry の名前付けガイドラインをお勧めします。 通常は、インストルメント化されるコードのアセンブリ名または名前空間の名前が適切です。 アセンブリで 2 番目の独立したアセンブリにコードのインストルメンテーションを追加する場合、その名前は、コードがインストルメント化されるアセンブリではなく、Meter を定義するアセンブリに基づいている必要があります。

  • .NET では、インストルメントに対して名前付けスキームは適用されませんが、OpenTelemetry の名前付けガイドラインに従うことをお勧めします。このガイドラインでは、小文字のドット形式の階層名、および同じ要素内の複数の単語間の区切り記号にアンダースコア ('_') を使用します。 すべてのメトリック ツールで、最終的なメトリック名の一部として Meter 名が保持されるわけではないため、インストルメント名を単独でグローバルに一意にすると便利です。

    インストルメント名の例:

    • contoso.ticket_queue.duration
    • contoso.reserved_tickets
    • contoso.purchased_tickets
  • インストルメントの作成と測定値の記録のための API はスレッドセーフです。 .NET ライブラリでは、ほとんどのインスタンス メソッドは、複数のスレッドから同じオブジェクトに対して呼び出しを行うときに同期を必要としますが、この場合は必要ありません。

  • 測定値を記録するインストルメント API (この例では Add) は、通常、データが収集されない場合は <10 ナノ秒で実行され、高パフォーマンスのコレクション ライブラリまたはツールによって測定値が収集される場合は数十から数百ナノ秒で実行されます。 これにより、ほとんどの場合、これらの 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-counters を使用して新しいカウンターを監視します。

> 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 を取得する

前の例では、Meter は new を使用して構築し、静的フィールドに割り当てることで取得されました。 依存関係の挿入 (DI) を使用する場合、この方法で静的フィールドを使用することは適切ではありません。 ASP.NET Core や汎用ホストを使用したアプリなど、DI を使用するコードでは、IMeterFactory を使用して Meter オブジェクトを作成します。 .NET 8 以降では、ホストにより IMeterFactory がサービス コンテナーに自動的に登録されます。または、ユーザーが AddMetrics を呼び出して任意の IServiceCollection を手動で登録することもできます。 メーター ファクトリはメトリックを DI と統合し、異なるサービス コレクションの Meter については、同じ名前を使用する場合でも、互いに隔離した状態を保ちます。 これは、並行して実行されている複数のテストで、同じテスト ケース内から生成された測定値のみを観察するようにテストを行なう場合、特に役に立ちます。

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 コンテナーが破棄されたときにそれらのオブジェクトを破棄します。 MeterDispose() を呼び出すために余計なコードを追加する必要はありません。また呼び出しても効果はありません。

インストルメントの種類

今までは 1 つの Counter<T> インストルメントを示しただけですが、使用可能なインストルメントの種類は他にもあります。 インストルメントは、次の 2 つの点で異なります。

  • 既定のメトリックの計算 - インストルメントの測定値を収集して分析するツールでは、インストルメントに応じて異なる既定のメトリックが計算されます。
  • 集計データのストレージ - 最も役に立つメトリックでは、多くの測定値からデータを集計する必要があります。 1 つのオプションとして、呼び出し元は任意のタイミングで個別の測定値を提供し、コレクション ツールは集計を管理します。 また、呼び出し元は集計の測定値を管理し、コールバックで要求に応じて提供することもできます。

現在使用可能なインストルメントの種類:

  • Counter (CreateCounter) - このインストルメントでは、時間の経過と共に増加する値を追跡し、呼び出し元が Add を使用してインクリメントを報告します。 ほとんどのツールでは、合計と合計の変化率が計算されます。 1 つだけ表示されるツールの場合は、変化率をお勧めします。 たとえば、呼び出し元が 1 秒に 1 回、Add() で連続する値 1、2、4、5、4、3 を呼び出すとします。 コレクション ツールが 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 秒に 1 回、Add() で連続する値 1、5、-2、3、-1、-3 を呼び出すとします。 コレクション ツールが 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 秒ごとに呼び出されます。 コールバックによって返される値がどのようなものであっても、コレクション ツールに合計として変更されずに表示されます。

  • ObservableGauge (CreateObservableGauge) - インストルメントによって、呼び出し元は測定値がメトリックとして直接渡されるコールバックを提供できます。 コレクション ツールが更新されるたびにコールバックが呼び出され、コールバックによって返された値がツールに表示されます。

  • Histogram (CreateHistogram) - インストルメントによって測定値の分布が追跡されます。 一連の測定値を記述するための標準的な方法は 1 つではありませんが、ヒストグラムまたは計算されたパーセンタイルをツールで使用することをお勧めします。 たとえば、コレクション ツールの更新間隔 (1、5、2、3、10、9、7、4、6、8) でこれらの測定値を記録するために呼び出し元が Record を呼び出したとします。 コレクション ツールは、これらの測定値の50、90、95 パーセンタイルがそれぞれ 5、9、9 であることを報告する場合があります。

インストルメントの種類を選択する際のベスト プラクティス

  • 時間の経過と共にのみ増加するその他の値をカウントするには、Counter または ObservableCounter を使用します。 既存のコードに追加しやすいのはどちらかに応じて、Counter と ObservableCounter から選択します。これは、インクリメント操作ごとの API 呼び出し、またはコードが保持する変数から現在の合計を読み取るコールバックのいずれかになります。 パフォーマンスが重要で、Add を使用すると、スレッドあたり 1 秒あたり 100 万を超える呼び出しが作成される非常にホットなコード パスでは、ObservableCounter を使用すると、最適化の機会が増える可能性があります。

  • タイミングについては、通常、Histogram を使用することをお勧めします。 多くの場合、平均や合計ではなく、これらの分布 (90、95、99 パーセンタイル) の裾を理解すると便利です。

  • キャッシュ ヒット率やキャッシュ、キュー、ファイルのサイズなど、その他の一般的なケースは、通常 UpDownCounterObservableUpDownCounter に適しています。 既存のコードに追加しやすいのはどちらかに応じて、それらを選択します。これは、インクリメントとデクリメント操作ごとの API 呼び出し、またはコードで保持される変数から現在の値を読み取るコールバックのいずれかになります。

Note

以前のバージョンの .NET または UpDownCounterObservableUpDownCounter をサポートしない DiagnosticSource NuGet パッケージ (バージョン 7 より前) を使用するのであれば、多くの場合、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(0.005, 0.015));
        }
    }
}

新しいプロセスを実行し、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

[HatCo.Store]
    hatco.store.coats_sold (Count / 1 sec)                                27
    hatco.store.hats_sold (Count / 1 sec)                                 36
    hatco.store.order_processing_time
        Percentile=50                                                      0.012
        Percentile=95                                                      0.014
        Percentile=99                                                      0.014
    hatco.store.orders_pending                                             5

この例では、ランダムに生成された数値を使用して、値が少し変化するようにしています。 hatco.store.hats_sold(Counter) と hatco.store.coats_sold (ObservableCounter) の両方が率として表示されていることがわかります。 ObservableGauge、hatco.store.orders_pending は、絶対値として表示されます。 Dotnet-counters では、Histogram インストルメントが 3 つのパーセンタイル統計 (50、95、99) として表示されますが、他のツールでは異なる方法で分布を集計したり、さらに多くの構成オプションが表示される場合があります。

ベスト プラクティス

  • ヒストグラムには、他のメトリックの種類よりも多くのデータがメモリに格納される傾向があります。 ただし、正確なメモリ使用量は、使用されているコレクション ツールによって決定されます。 Histogram メトリックの大きい数値 (>100) を定義する場合は、それらすべてを同時に有効にしないようにするか、有効桁数を減らすことでメモリを節約するようにツールを構成するようにユーザーに指示する必要があります。 コレクション ツールによっては、メモリ使用量が過剰にならないように、監視する同時実行ヒストグラムの数にハード制限が設定されている場合があります。

  • すべての監視可能なインストルメントのコールバックが順番に呼び出されるため、長い時間がかかるコールバックは、すべてのメトリックが収集されるのを遅らせたり妨げたりする可能性があります。 キャッシュされた値をすばやく読み取り、長時間実行される、またはブロックする可能性のある操作に対して測定値を返さないようにするか、または例外をスローすることをお勧めします。

  • ObservableCounter、ObservableUpDownCounter、ObservableGauge コールバックは、値を更新するコードとは通常同期されないスレッドで発生します。 メモリ アクセスを同期するか、同期されていないアクセスを使用した結果として発生する可能性がある一貫性のない値を受け取るかは自己責任となります。 アクセスを同期する一般的な手法はロックを使用するか、Volatile.ReadVolatile.Write を呼び出すことです。

  • CreateObservableGaugeCreateObservableCounter 関数は、インストルメント オブジェクトを返しますが、ほとんどの場合、オブジェクトとのやり取りは必要ないため、変数に保存する必要はありません。 他のインストルメントに対して行ったように静的変数に割り当てることは有効ですが、エラーが発生しやすくなります。これは、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

[HatCo.Store]
    hatco.store.hats_sold ({hats} / 1 sec)                                40

dotnet-counters は現在、UI の説明テキストを使用していませんが、指定されたときに単位を表示します。 この場合は、"{hats}" が、前の説明に表示されている一般用語 "Count" に置き換えられています。

ベスト プラクティス

  • .NET API では、単位として任意の文字列使用できますが、単位名の国際標準である UCUM を使用することをお勧めします。 中かっこで囲まれた "{hats}" は UCUM 標準の一部であり、秒やバイトのように標準化された意味を持つ単位名ではなく、説明的な注釈であることを示しています。

  • コンストラクターで指定された単位では、個々の測定に適した単位を示す必要があります。 これは、最終的なメトリックの単位とは異なる場合があります。 この例では、各測定値は帽子の数であるため、"{hats}" はコンストラクターで渡される適切な単位です。 コレクション ツールにより、率が計算され、計算されたメトリックの適切な単位が {hats}/sec であることが自動的に導出されました。

  • 時間の測定値を記録する場合、浮動小数点または倍精度浮動小数点値として記録された秒数が優先されます。

多次元メトリック

測定値は、データを分析用に分類できるようにする、タグと呼ばれるキーと値のペアに関連付けることもできます。 たとえば、HatCo では、販売された帽子の数だけでなく、サイズや色も記録することができます。 後でデータを分析する場合、HatCo エンジニアは、サイズ、色、またはその両方の組み合わせで合計を分割できます。

Counter タグと Histogram タグは、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-counters に基本的な分類が表示されるようになりました。

Press p to pause, r to resume, q to quit.
    Status: Running

[HatCo.Store]
    hatco.store.hats_sold (Count / 1 sec)
        product.color=blue,product.size=19                                 9
        product.color=red,product.size=12                                 18

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

[HatCo.Store]
    hatco.store.orders_pending
        customer.country=Italy                                             6
        customer.country=Mexico                                            1
        customer.country=Spain                                             3

ベスト プラクティス

  • API では、任意のオブジェクトをタグ値として使用できますが、数値型と文字列はコレクション ツールによって予測されます。 その他の型は、特定のコレクション ツールでサポートされている場合とサポートされていない場合があります。

  • タグ名は OpenTelemetryの名前付けガイドラインに従い、設定することを推奨します。このガイドラインでは、小文字のドット付き階層名を使用し、同じ要素内の複数の単語を区切る際には '_' 文字を使用します。 タグ名が異なるメトリックまたはその他のテレメトリ レコードで再利用される場合、そのタグ名は、使用されるすべての場所で同じ意味と有効な値のセットを持つ必要があります。

    タグ名の例:

    • customer.country
    • store.payment_method
    • store.purchase_result
  • 実際に記録されているタグ値の組み合わせが非常に大きい、または無制限であることに注意してください。 .NET API の実装では処理できますが、コレクション ツールでは、各タグの組み合わせに関連付けられたメトリック データ用にストレージが割り当てられる可能性があり、これは非常に大きくなる可能性があります。 たとえば、追跡する最大 10*25=250 個の販売合計について、HatCo の帽子の色が 10 種類、帽子サイズが 25 種類の場合は問題ありません。ただし、HatCo が販売用に 3 つ目のタグ (CustomerID) を追加し、世界中の 1 億人の顧客に販売する場合は、数十億個の異なるタグの組み合わせが記録される可能性が高くなります。 ほとんどのメトリック コレクション ツールでは、技術的な制限内に収まるようにデータを削除します。そうしないと、データの保存と処理に関する多額の金銭的コストが発生する可能性があります。 各コレクション ツールの実装によって制限が決定されますが、1 つのインストルメントで 1000 未満の組み合わせが安全である可能性があります。 組み合わせが 1000 を超える場合は、コレクション ツールでフィルターを適用したり、大規模に運用できるように設計する必要があります。 Histogram の実装では、他のメトリックよりもはるかに多くのメモリを使用する傾向があるため、安全な制限は 10 から 100 分の 1 になる可能性があります。 一意のタグの組み合わせが多数あることが予想される場合は、ログ、トランザクション データベース、またはビッグ データ処理システムの方が、必要な規模で運用するためのより適切なソリューションである可能性があります。

  • タグの組み合わせが非常に多いインストルメントでは、より小さいストレージの種類を使用して、メモリのオーバーヘッドを減らすようにしてください。 たとえば、Counter<short>short を格納すると、タグの組み合わせごとに 2 バイトしか使用されません。一方、Counter<double>double は、タグの組み合わせごとに 8 バイト使用します。

  • コレクション ツールは、同じインストルメントの測定値を記録するために呼び出しを行うたびに、同じ順序で同じタグ名のセットを指定するコードに対して最適化することをお勧めします。 AddRecord の呼び出しを頻繁に行う必要がある高パフォーマンス コードの場合は、呼び出しごとに同じ順序のタグ名を使用することをお勧めします。

  • .NET API は、Add および Record 呼び出しでの割り当てを必要としないように最適化されており、個別に指定されるタグは 3 個未満です。 多数のタグが割り当てられないようにするには、TagList を使用します。 一般に、これらの呼び出しのパフォーマンス オーバーヘッドは、使用するタグが増えるにつれて増加します。

注意

OpenTelemetry はタグを ' attributes' として参照します。 これらは、同じ機能に対して 2 つの異なる名前です。

カスタム メトリックをテストする

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 つのインストルメントのすべての測定値を記録します。 複数のインストルメントの測定値を確認する必要がある場合は、インストルメントごとに 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);
    }
}