次の方法で共有


.NET のネットワーク メトリック

メトリックは、経時的に報告される数値の測定値です。 これは通常、アプリの正常性を監視し、アラートを生成するために使用します。

.NET 8 以降では、System.Net.HttpSystem.Net.NameResolution コンポーネントがインストルメント化され、.NET の新しい System.Diagnostics.Metrics APIを使用してメトリックが公開されます。 これらのメトリックは、標準と一致し、PrometheusGrafana などの一般的なツールとうまく連携するように、OpenTelemetry と協力して設計されました。 これらは多次元でもあります。つまり、測定は、分析用にデータを分類できるタグ (別名: 属性またはラベル) と呼ばれるキーと値のペアに関連付けられています。

ヒント

すべての組み込みインストルメントとその属性の包括的な一覧については、「System.Net メトリック」を参照してください。

System.Net メトリックを収集する

.NET アプリでメトリックを使用するには、2 つの手順が必要です。

  • インストルメンテーション: .NET ライブラリのコードによって、測定値が取得され、これらの測定値がメトリック名に関連付けられます。 .NET と ASP.NET Core には、多くの組み込みメトリックが含まれています。
  • コレクション: .NET アプリで、外部での保管と分析のためにアプリから送信される名前付きメトリックを構成します。 ツールによっては、構成ファイルまたは UI ツールを使用して、アプリの外部で構成を実行する場合があります。

このセクションでは、System.Net メトリックを収集して表示するさまざまな方法について説明します。

サンプル アプリ

このチュートリアルでは、HTTP 要求をさまざまなエンドポイントに並列で送信する単純なアプリを作成します。

dotnet new console -o HelloBuiltinMetrics
cd ..\HelloBuiltinMetrics

Program.cs の内容を次のサンプル コードに置き換えます。

using System.Net;

string[] uris = ["http://example.com", "http://httpbin.org/get", "https://example.com", "https://httpbin.org/get"];
using HttpClient client = new()
{
    DefaultRequestVersion = HttpVersion.Version20
};

Console.WriteLine("Press any key to start.");
Console.ReadKey();

while (!Console.KeyAvailable)
{
    await Parallel.ForAsync(0, Random.Shared.Next(20), async (_, ct) =>
    {
        string uri = uris[Random.Shared.Next(uris.Length)];
        byte[] bytes = await client.GetByteArrayAsync(uri, ct);
        await Console.Out.WriteLineAsync($"{uri} - received {bytes.Length} bytes.");
    });
}

dotnet-counters を使ってメトリックを表示する

dotnet-counters は、アドホックな正常性監視と第 1 レベルのパフォーマンス調査のためのクロスプラットフォーム パフォーマンス監視ツールです。

dotnet tool install --global dotnet-counters

.NET 8 以降のプロセスに対して実行する場合は、dotnet-counters を使用して、--counters 引数によって定義されたインストルメントを有効にし、測定を表示します。 これにより、コンソールが最新の数値で継続的に更新されます。

dotnet-counters monitor --counters System.Net.Http,System.Net.NameResolution -n HelloBuiltinMetrics

OpenTelemetry と Prometheus を使って Grafana のメトリックを表示する

概要

OpenTelemetry:

  • Cloud Native Computing Foundation によってサポートされているベンダーに依存しないオープンソース プロジェクトです。
  • クラウドネイティブ ソフトウェアのテレメトリの生成と収集を標準化します。
  • .NET メトリック API を使用して .NET で動作します。
  • Azure Monitor と多くの APM ベンダーによって承認されています。

このチュートリアルでは、OSS Prometheus プロジェクトと Grafana プロジェクトを使用して OpenTelemetry メトリックで使用できる統合の 1 つを示します。 メトリック データ フローは、次の手順で構成されます。

  1. .NET メトリック API は、サンプル アプリからの測定値を記録します。

  2. アプリで実行されている OpenTelemetry ライブラリは、測定値を集計します。

  3. Prometheus エクスポーター ライブラリにより、集計されたデータを HTTP メトリック エンドポイント経由で使うことができるようになります。 'エクスポーター' とは、ベンダー固有のバックエンドにテレメトリを送信するライブラリに OpenTelemetry が付けた名称です。

  4. Prometheus サーバー:

    • メトリック エンドポイントをポーリングします。
    • データを読み取ります。
    • 長期的な永続化のためにデータベースにデータを保存します。 Prometheus では、データの読み取りと保存をエンドポイントの "スクレイピング" と言います。
    • 別のマシンで実行できます。
  5. Grafana サーバー:

    • Prometheus に保存されているデータのクエリを実行し、それを Web ベースの監視ダッシュボードに表示します。
    • 別のコンピューターで実行できます。

OpenTelemetry の Prometheus エクスポーターを使用するようにサンプル アプリを構成する

OpenTelemetry Prometheus エクスポーターへの参照をサンプル アプリンに追加します。

dotnet add package OpenTelemetry.Exporter.Prometheus.HttpListener --prerelease

注意

このチュートリアルでは、この記事の作成時点で利用可能な OpenTelemetry の Prometheus サポートのプレリリース ビルドを使用しています。

OpenTelemetry 構成を使用した Program.cs の更新 :

using OpenTelemetry.Metrics;
using OpenTelemetry;
using System.Net;

using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder()
    .AddMeter("System.Net.Http", "System.Net.NameResolution")
    .AddPrometheusHttpListener(options => options.UriPrefixes = new string[] { "http://localhost:9184/" })
    .Build();

string[] uris = ["http://example.com", "http://httpbin.org/get", "https://example.com", "https://httpbin.org/get"];
using HttpClient client = new()
{
    DefaultRequestVersion = HttpVersion.Version20
};

while (!Console.KeyAvailable)
{
    await Parallel.ForAsync(0, Random.Shared.Next(20), async (_, ct) =>
    {
        string uri = uris[Random.Shared.Next(uris.Length)];
        byte[] bytes = await client.GetByteArrayAsync(uri, ct);
        await Console.Out.WriteLineAsync($"{uri} - received {bytes.Length} bytes.");
    });
}

上のコードでは以下の操作が行われます。

  • AddMeter("System.Net.Http", "System.Net.NameResolution") を使って、組み込みの System.Net.Http および System.Net.NameResolution 測定によって収集されたすべてのメトリックが送信されるように OpenTelemetry を構成します。
  • AddPrometheusHttpListener を使用して、Prometheus のメトリック HTTP エンドポイントがポート 9184 で公開されるように OpenTelemetry を構成します。

Note

この構成は、メトリックが HttpListener ではなく OpenTelemetry.Exporter.Prometheus.AspNetCore を使用してエクスポートされる ASP.NET Core アプリでは異なります。 関連する ASP.NET Core の例を参照してください。

アプリを実行し、測定値を収集できるようにアプリを実行したままにします。

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 を起動する

  1. 構成を再読み込みするか、Prometheus サーバーを再起動します。

  2. Prometheus Web ポータルの [状態]>[ターゲット] ページで OpenTelemetryTest が UP 状態であることを確認します。 Prometheus status

  3. Prometheus Web ポータルの [Graph] (グラフ) ページで式テキスト ボックスに「http」と入力し、http_client_active_requests を選びます。 http_client_active_requests Prometheus の[Graph] (グラフ) タブには、サンプル アプリによって出力される http.client.active_requests カウンターの値が表示されます。 Prometheus active requests graph

Grafana ダッシュボードにメトリックを表示する

  1. 標準の手順に従って Grafana をインストールし、Prometheus データソースに接続します。

  2. 上部のツール バーの + アイコンを選択し、[Dashboard] (ダッシュボード) を選択して、Grafana ダッシュボードを作成します。 表示されるダッシュボード エディターで [Title] (タイトル) ボックスに「OPEN HTTP/1.1 Connections」と入力し、[PromQL expression ] (PromQL 式) フィールドに次のクエリを入力します。

sum by(http_connection_state) (http_client_open_connections{network_protocol_version="1.1"})

Grafana HTTP/1.1 Connections

  1. [Apply] (適用) をクリックして、新しいダッシュボードを保存して表示します。 プール内のアクティブおよびアイドル状態の HTTP/1.1 接続の数が表示されます。

エンリッチメント

"エンリッチメント" とは、カスタム タグ (別名: 属性またはラベル) をメトリックに追加することをいいます。 この機能は、アプリで、メトリックを使用して構築されたダッシュボードまたはアラートにカスタムの分類を加える場合に便利です。 http.client.request.duration インストルメントは、HttpMetricsEnrichmentContext を使用してコールバックを登録することにより、エンリッチメントをサポートします。 これは低レベルの API であり、それぞれの HttpRequestMessage に個別のコールバック登録が必要であることに注意してください。

コールバック登録を 1 か所で行う簡単な方法は、カスタム DelegatingHandler を実装することです。 これにより、要求が内部ハンドラーに転送されてサーバーに送信される前に、それらをインターセプトして変更できます。

using System.Net.Http.Metrics;

using HttpClient client = new(new EnrichmentHandler() { InnerHandler = new HttpClientHandler() });

await client.GetStringAsync("https://httpbin.org/response-headers?Enrichment-Value=A");
await client.GetStringAsync("https://httpbin.org/response-headers?Enrichment-Value=B");

sealed class EnrichmentHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        HttpMetricsEnrichmentContext.AddCallback(request, static context =>
        {
            if (context.Response is not null) // Response is null when an exception occurs.
            {
                // Use any information available on the request or the response to emit custom tags.
                string? value = context.Response.Headers.GetValues("Enrichment-Value").FirstOrDefault();
                if (value != null)
                {
                    context.AddCustomTag("enrichment_value", value);
                }
            }
        });
        return base.SendAsync(request, cancellationToken);
    }
}

IHttpClientFactory を使用している場合は、EnrichmentHandler を登録するために AddHttpMessageHandler を使用できます。

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System.Net.Http.Metrics;

ServiceCollection services = new();
services.AddHttpClient(Options.DefaultName).AddHttpMessageHandler(() => new EnrichmentHandler());

ServiceProvider serviceProvider = services.BuildServiceProvider();
HttpClient client = serviceProvider.GetRequiredService<HttpClient>();

await client.GetStringAsync("https://httpbin.org/response-headers?Enrichment-Value=A");
await client.GetStringAsync("https://httpbin.org/response-headers?Enrichment-Value=B");

Note

パフォーマンス上の理由から、エンリッチメント コールバックは、http.client.request.duration インストルメントが有効になっている場合にのみ呼び出されます。つまり、何かによってメトリックを収集する必要があるということです。 これには、dotnet-monitor、Prometheus エクスポーター、MeterListener、または MetricCollector<T> を指定することができます。

IMeterFactoryIHttpClientFactory の統合

HTTP メトリックは、分離性とテスト可能性を考慮して設計されています。 これらの側面は、IMeterFactory を使用してサポートされています。これにより、測定の相互の分離を維持するための、カスタム Meter インスタンスによるメトリックの発行が可能になります。 既定では、すべてのメトリックは、System.Net.Http ライブラリ内部のグローバル Meter によって出力されます。 この動作は、カスタム IMeterFactory インスタンスを SocketsHttpHandler.MeterFactory または HttpClientHandler.MeterFactory に割り当てることでオーバーライドできます。

Note

HttpClientHandlerSocketsHttpHandler によって出力されるすべてのメトリックの Meter.NameSystem.Net.Http となります。

.NET 8 以降で Microsoft.Extensions.HttpIHttpClientFactory を使用する場合、既定の IHttpClientFactory の実装では、IServiceCollection に登録されている IMeterFactory インスタンスが自動的に選択され、内部で作成されるプライマリ ハンドラーに割り当てられます。

Note

.NET 8 以降では、この AddHttpClient メソッドは自動的に AddMetrics を呼び出してメトリック サービスを初期化し、既定の IMeterFactory の実装を IServiceCollection を使用して登録します。 IMeterFactory では既定で、名前によって Meter インスタンスがキャッシュされます。つまり、IServiceCollection ごとに System.Net.Http という名前が付いた Meter が 1 つ存在することになります。

メトリックをテストする

次の例では、Microsoft.Extensions.Diagnostics.Testing NuGet パッケージから xUnit、IHttpClientFactoryMetricCollector<T> を使用して、単体テストで組み込みのメトリックを検証する方法を示します。

[Fact]
public async Task RequestDurationTest()
{
    // Arrange
    ServiceCollection services = new();
    services.AddHttpClient();
    ServiceProvider serviceProvider = services.BuildServiceProvider();
    var meterFactory = serviceProvider.GetService<IMeterFactory>();
    var collector = new MetricCollector<double>(meterFactory,
        "System.Net.Http", "http.client.request.duration");
    var client = serviceProvider.GetRequiredService<HttpClient>();

    // Act
    await client.GetStringAsync("http://example.com");

    // Assert
    await collector.WaitForMeasurementsAsync(minCount: 1).WaitAsync(TimeSpan.FromSeconds(5));
    Assert.Collection(collector.GetMeasurementSnapshot(),
        measurement =>
        {
            Assert.Equal("http", measurement.Tags["url.scheme"]);
            Assert.Equal("GET", measurement.Tags["http.request.method"]);
        });
}

Metrics とEventCounters

Metrics には、EventCounters と比較してより充実した機能が備わっています。これは特に、その多次元性が理由です。 この多次元性により、Prometheus などのツールで高度なクエリを作成し、EventCounters では不可能なレベルで分析情報を取得できます。

ただし、.NET 8 の時点では、Metrics を使用してインストルメント化されるのは System.Net.Http および System.Net.NameResolutions コンポーネントのみです。つまり、System.Net.SocketsSystem.Net.Security など、スタックの下位レベルのカウンターが必要な場合は、EventCounters を使用する必要があります。

さらに、Metrics とそれらの一致する EventCounters には、いくつかのセマンティックな違いがあります。 たとえば、HttpCompletionOption.ResponseContentRead を使用する場合、current-requests EventCounter は要求本文の最後のバイトが読み取られるまで、要求がアクティブであると見なします。 対応するメトリック http.client.active_requests には、アクティブな要求をカウントするときに応答本文の読み取りに費やされた時間は含まれません。

さらにメトリックが必要な場合

メトリックを介して公開される可能性のある他の有用な情報についての提案がある場合は、dotnet/runtime の問題を作成します。