.NET 中的網路計量
計量是一段時間所報告的數值測量。 它們通常用來監視應用程式的健康情況,並產生警示。
從 .NET 8 開始,System.Net.Http
和 System.Net.NameResolution
元件會經過檢測以使用 .NET 的新 System.Diagnostics.Metrics API 來發佈計量。
這些計量是與 OpenTelemetry 合作設計的,以確保它們與標準一致,並與 Prometheus 和 Grafana 等熱門工具良好地搭配運作。
它們也是多維度的,這表示測量與稱為標籤的索引鍵/值組相關聯 (亦即屬性或標籤),可讓資料進行分類以供分析。
提示
如需所有內建檢測及其屬性的完整清單,請參閱 System.Net 計量。
收集 System.Net 計量
在 .NET 應用程式中使用計量有兩個部分:
- 檢測:.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
是跨平台效能監視工具,適用於臨機操作的狀況監控和第一層級的效能調查。
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 監視器和許多 APM 廠商認可。
本教學課程示範使用 OSS Prometheus 和 Grafana 專案的 OpenTelemetry 計量可用的其中一項整合。 計量資料流包含下列步驟:
.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")
會設定 OpenTelemetry,以傳輸由內建System.Net.Http
和System.Net.NameResolution
計量所收集的所有計量。AddPrometheusHttpListener
會設定 OpenTelemetry,以在連接埠9184
上公開 Prometheus 的計量 HTTP 端點。
注意
此組態與 ASP.NET Core 應用程式不同,其中計量會以 OpenTelemetry.Exporter.Prometheus.AspNetCore
匯出,而不是 HttpListener
。 請參閱相關的 ASP.NET Core 範例。
執行應用程式,讓應用程式保持在執行狀態以便收集度量:
dotnet run
設定 Prometheus
遵循 Prometheus first steps (英文) 來設定 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 伺服器。
確認 OpenTelemetryTest 在 Prometheus 入口網站的 [狀態]> [目標] 頁面中處於運作中狀態。
在 Prometheus 入口網站的 [Graph] 頁面上,於運算式文字方塊中輸入
http
,然後選取http_client_active_requests
。 在圖表索引標籤中,Prometheus 會顯示範例應用程式所發出的http.client.active_requests
計數器值。
在 Grafana 儀表板上顯示計量
請遵循標準指示來安裝 Grafana,並將其連線至 Prometheus 資料來源。
選取頂端工具列上的 + 圖示,然後選取 [儀表板],以建立 Grafana 儀表板。 在出現的儀表板編輯器中,於 [標題] 方塊中 輸入 [開啟 HTTP/1.1 連線],然後在 [PromQL 運算式] 欄位中輸入下列查詢:
sum by(http_connection_state) (http_client_open_connections{network_protocol_version="1.1"})
- 選取 [套用],以儲存並檢視新儀表板。 它會顯示集區中作用中與閒置 HTTP/1.1 連線的數目。
擴充
擴充是將自訂標籤 (也就是屬性或標籤) 新增至計量。 如果應用程式想要將自訂分類新增至使用計量建置的儀表板或警示,這會很有用。
http.client.request.duration
檢測支援擴充,方法是向 HttpMetricsEnrichmentContext 註冊回呼。
請注意,這是低階 API,而且每個 HttpRequestMessage
都需要個別的回呼註冊。
在單一位置執行回呼註冊的簡單方法是實作自訂 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
,則可以使用 AddHttpMessageHandler 來註冊 EnrichmentHandler
:
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");
注意
基於效能因素,只有在啟用 http.client.request.duration
檢測時才會叫用擴充回呼,這表示應該有某種程序來收集計量。
這可以是 dotnet-monitor
、Prometheus 匯出工具、MeterListener
、或 MetricCollector<T>
。
IMeterFactory
和 IHttpClientFactory
整合
HTTP 計量的設計考慮了隔離和可測試性。 使用 IMeterFactory 支援這些層面可讓自訂 Meter 執行個體發佈計量,以便讓計量彼此隔離。
根據預設,所有計量都會由 System.Net.Http
程式庫的全域 Meter 內部發出。 將自訂 IMeterFactory 執行個體指派給 SocketsHttpHandler.MeterFactory 或 HttpClientHandler.MeterFactory,即可覆寫此行為。
注意
Meter.Name 是 System.Net.Http
針對 HttpClientHandler
和 SocketsHttpHandler
發出的所有計量。
在 NET 8+ 上使用 Microsoft.Extensions.Http
和 IHttpClientFactory
時,預設 IHttpClientFactory
實作會自動挑選 IServiceCollection 中註冊的 IMeterFactory
執行個體,並將其指派給它在內部建立的主要處理常式。
注意
從 .NET 8 開始,AddHttpClient 方法會自動呼叫 AddMetrics 來初始化計量服務,並使用 IServiceCollection 註冊預設 IMeterFactory 實作。 預設 IMeterFactory 會依名稱快取 Meter 執行個體,這表示每個 IServiceCollection 都會有一個名稱為 System.Net.Http
的 Meter。
測試計量
下列範例示範如何使用 xUnit、IHttpClientFactory
和 MetricCollector<T>
,從 Microsoft.Extensions.Diagnostics.Testing
NuGet 套件驗證單元測試中的內建計量:
[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"]);
});
}
計量與EventCounters
計量比 EventCounters 更為功能豐富,特別是因為其多維度本質。 此多維度可讓您在 Prometheus 之類的工具中建立複雜的查詢,並取得 EventCounters 無法達到的深入解析。
不過,從 .NET 8 起,只有 System.Net.Http
和 System.Net.NameResolutions
元件會使用計量進行檢測,這表示如果您需要來自 System.Net.Sockets
或 System.Net.Security
等堆疊較低層級的計數器,則必須使用 EventCounters。
此外,計量與其相符的 EventCounters 之間有一些語意上的差異。
例如,使用 HttpCompletionOption.ResponseContentRead
時,current-requests
EventCounter 會將要求視為作用中,直到讀取要求本文的最後一個位元組為止。
其計量對應項目 http.client.active_requests
不包含計算使用中要求時讀取回應本文所花費的時間。
需要更多計量嗎?
如果您有其他可透過計量公開之實用資訊的建議,請建立 dotnet/runtime 問題。