共用方式為


.NET 中的網路計量

計量是一段時間所報告的數值測量。 它們通常用來監視應用程式的健康情況,並產生警示。

從 .NET 8 開始,System.Net.HttpSystem.Net.NameResolution 元件會經過檢測以使用 .NET 的新 System.Diagnostics.Metrics API 來發佈計量。 這些計量是與 OpenTelemetry 合作設計的,以確保它們與標準一致,並與 PrometheusGrafana 等熱門工具良好地搭配運作。 它們也是多維度的,這表示測量與稱為標籤的索引鍵/值組相關聯 (亦即屬性或標籤),可讓資料進行分類以供分析。

提示

如需所有內建檢測及其屬性的完整清單,請參閱 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 中的計量

概觀

OpenTelemetry

  • 這是 Cloud Native Computing Foundation 支援的廠商中性開放原始碼專案。
  • 將產生和收集雲端原生軟體的遙測標準化。
  • 使用 .NET 計量 API 搭配 .NET 使用。
  • 獲得 Azure 監視器和許多 APM 廠商認可。

本教學課程示範使用 OSS PrometheusGrafana 專案的 OpenTelemetry 計量可用的其中一項整合。 計量資料流包含下列步驟:

  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") 會設定 OpenTelemetry,以傳輸由內建 System.Net.HttpSystem.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

  1. 重新載入設定或重新啟動 Prometheus 伺服器。

  2. 確認 OpenTelemetryTest 在 Prometheus 入口網站的 [狀態]> [目標] 頁面中處於運作中狀態。 Prometheus status

  3. 在 Prometheus 入口網站的 [Graph] 頁面上,於運算式文字方塊中輸入 http,然後選取 http_client_active_requestshttp_client_active_requests 在圖表索引標籤中,Prometheus 會顯示範例應用程式所發出的 http.client.active_requests 計數器值。 Prometheus active requests graph

在 Grafana 儀表板上顯示計量

  1. 請遵循標準指示來安裝 Grafana,並將其連線至 Prometheus 資料來源。

  2. 選取頂端工具列上的 + 圖示,然後選取 [儀表板],以建立 Grafana 儀表板。 在出現的儀表板編輯器中,於 [標題] 方塊中 輸入 [開啟 HTTP/1.1 連線],然後在 [PromQL 運算式] 欄位中輸入下列查詢:

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

Grafana HTTP/1.1 Connections

  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>

IMeterFactoryIHttpClientFactory 整合

HTTP 計量的設計考慮了隔離和可測試性。 使用 IMeterFactory 支援這些層面可讓自訂 Meter 執行個體發佈計量,以便讓計量彼此隔離。 根據預設,所有計量都會由 System.Net.Http 程式庫的全域 Meter 內部發出。 將自訂 IMeterFactory 執行個體指派給 SocketsHttpHandler.MeterFactoryHttpClientHandler.MeterFactory,即可覆寫此行為。

注意

Meter.NameSystem.Net.Http 針對 HttpClientHandlerSocketsHttpHandler 發出的所有計量。

在 NET 8+ 上使用 Microsoft.Extensions.HttpIHttpClientFactory時,預設 IHttpClientFactory 實作會自動挑選 IServiceCollection 中註冊的 IMeterFactory 執行個體,並將其指派給它在內部建立的主要處理常式。

注意

從 .NET 8 開始,AddHttpClient 方法會自動呼叫 AddMetrics 來初始化計量服務,並使用 IServiceCollection 註冊預設 IMeterFactory 實作。 預設 IMeterFactory 會依名稱快取 Meter 執行個體,這表示每個 IServiceCollection 都會有一個名稱為 System.Net.HttpMeter

測試計量

下列範例示範如何使用 xUnit、IHttpClientFactoryMetricCollector<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.HttpSystem.Net.NameResolutions 元件會使用計量進行檢測,這表示如果您需要來自 System.Net.SocketsSystem.Net.Security 等堆疊較低層級的計數器,則必須使用 EventCounters。

此外,計量與其相符的 EventCounters 之間有一些語意上的差異。 例如,使用 HttpCompletionOption.ResponseContentRead 時,current-requests EventCounter 會將要求視為作用中,直到讀取要求本文的最後一個位元組為止。 其計量對應項目 http.client.active_requests 不包含計算使用中要求時讀取回應本文所花費的時間。

需要更多計量嗎?

如果您有其他可透過計量公開之實用資訊的建議,請建立 dotnet/runtime 問題