.NET のネットワーク メトリック
メトリックは、経時的に報告される数値の測定値です。 これは通常、アプリの正常性を監視し、アラートを生成するために使用します。
.NET 8 以降では、System.Net.Http
と System.Net.NameResolution
コンポーネントがインストルメント化され、.NET の新しい System.Diagnostics.Metrics APIを使用してメトリックが公開されます。
これらのメトリックは、標準と一致し、Prometheus や Grafana などの一般的なツールとうまく連携するように、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 のメトリックを表示する
概要
- 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.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 を起動する
構成を再読み込みするか、Prometheus サーバーを再起動します。
Prometheus Web ポータルの [状態]>[ターゲット] ページで OpenTelemetryTest が UP 状態であることを確認します。
Prometheus Web ポータルの [Graph] (グラフ) ページで式テキスト ボックスに「
http
」と入力し、http_client_active_requests
を選びます。 Prometheus の[Graph] (グラフ) タブには、サンプル アプリによって出力されるhttp.client.active_requests
カウンターの値が表示されます。
Grafana ダッシュボードにメトリックを表示する
標準の手順に従って Grafana をインストールし、Prometheus データソースに接続します。
上部のツール バーの + アイコンを選択し、[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"})
- [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>
を指定することができます。
IMeterFactory
と IHttpClientFactory
の統合
HTTP メトリックは、分離性とテスト可能性を考慮して設計されています。 これらの側面は、IMeterFactory を使用してサポートされています。これにより、測定の相互の分離を維持するための、カスタム Meter インスタンスによるメトリックの発行が可能になります。
既定では、すべてのメトリックは、System.Net.Http
ライブラリ内部のグローバル Meter によって出力されます。 この動作は、カスタム IMeterFactory インスタンスを SocketsHttpHandler.MeterFactory または HttpClientHandler.MeterFactory に割り当てることでオーバーライドできます。
Note
HttpClientHandler
と SocketsHttpHandler
によって出力されるすべてのメトリックの Meter.Name は System.Net.Http
となります。
.NET 8 以降で Microsoft.Extensions.Http
と IHttpClientFactory
を使用する場合、既定の IHttpClientFactory
の実装では、IServiceCollection に登録されている IMeterFactory
インスタンスが自動的に選択され、内部で作成されるプライマリ ハンドラーに割り当てられます。
Note
.NET 8 以降では、この AddHttpClient メソッドは自動的に AddMetrics を呼び出してメトリック サービスを初期化し、既定の IMeterFactory の実装を IServiceCollection を使用して登録します。 IMeterFactory では既定で、名前によって Meter インスタンスがキャッシュされます。つまり、IServiceCollection ごとに System.Net.Http
という名前が付いた Meter が 1 つ存在することになります。
メトリックをテストする
次の例では、Microsoft.Extensions.Diagnostics.Testing
NuGet パッケージから xUnit、IHttpClientFactory
、MetricCollector<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.Sockets
や System.Net.Security
など、スタックの下位レベルのカウンターが必要な場合は、EventCounters を使用する必要があります。
さらに、Metrics とそれらの一致する EventCounters には、いくつかのセマンティックな違いがあります。
たとえば、HttpCompletionOption.ResponseContentRead
を使用する場合、current-requests
EventCounter は要求本文の最後のバイトが読み取られるまで、要求がアクティブであると見なします。
対応するメトリック http.client.active_requests
には、アクティブな要求をカウントするときに応答本文の読み取りに費やされた時間は含まれません。
さらにメトリックが必要な場合
メトリックを介して公開される可能性のある他の有用な情報についての提案がある場合は、dotnet/runtime の問題を作成します。
.NET