共用方式為


收集計量

本文適用於:✔️.NET Core 3.1 和更新版本 ✔️ .NET Framework 4.6.1 和更新版本

檢測的程式碼可以記錄數值度量,但度量通常需要經過彙總、傳輸和儲存,以建立實用的監視計量。 彙總、傳輸和儲存資料的此程序稱為收集。 本教學課程示範數個收集計量的範例:

如需自訂計量檢測和選項的詳細資訊,請參閱比較計量 API

必要條件

建立範例應用程式

必須先產生一些度量,才能收集計量。 本教學課程會建立具有基本計量檢測的應用程式。 .NET 執行階段也內建各種計量。 如需使用 System.Diagnostics.Metrics.Meter API 建立新計量的詳細資訊,請參閱檢測教學課程

dotnet new console -o metric-instr
cd metric-instr
dotnet add package System.Diagnostics.DiagnosticSource

以下列程式碼取代 Program.cs 的內容:

using System.Diagnostics.Metrics;

class Program
{
    static Meter s_meter = new("HatCo.HatStore", "1.0.0");
    static Counter<int> s_hatsSold = s_meter.CreateCounter<int>("hats-sold");

    static void Main(string[] args)
    {
        var rand = Random.Shared;
        Console.WriteLine("Press any key to exit");
        while (!Console.KeyAvailable)
        {
            //// Simulate hat selling transactions.
            Thread.Sleep(rand.Next(100, 2500));
            s_hatsSold.Add(rand.Next(0, 1000));
        }
    }
}

上述程式碼會以隨機間隔和隨機時間模擬銷售帽子。

使用 dotnet-counters 檢視計量

dotnet-counters 是命令列工具,可隨需檢視 .NET Core 應用程式的即時計量。 它不需要設定,因此適合臨機操作調查,或驗證計量檢測可正常運作。 其適用於 System.Diagnostics.Metrics 式 API 和 EventCounters

如果未安裝 dotnet-counters 工具,請執行下列命令:

dotnet tool update -g dotnet-counters

範例應用程式執行中時,啟動 dotnet-counters。 下列命令顯示的 dotnet-counters 範例會監視來自 HatCo.HatStore 計量的所有計量。 計量 (meter) 名稱會區分大小寫。 我們的範例應用程式為 metric-instr.exe,請將其替換為您的範例應用程式名稱。

dotnet-counters monitor -n metric-instr HatCo.HatStore

會顯示類似下列的輸出:

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

[HatCo.HatStore]
    hats-sold (Count / 1 sec)                          4

dotnet-counters 可以使用一組不同的計量來執行,以查看 .NET 執行階段的一些內建檢測:

dotnet-counters monitor -n metric-instr

會顯示類似下列的輸出:

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

[System.Runtime]
    % Time in GC since last GC (%)                                 0
    Allocation Rate (B / 1 sec)                                8,168
    CPU Usage (%)                                                  0
    Exception Count (Count / 1 sec)                                0
    GC Heap Size (MB)                                              2
    Gen 0 GC Count (Count / 1 sec)                                 0
    Gen 0 Size (B)                                         2,216,256
    Gen 1 GC Count (Count / 1 sec)                                 0
    Gen 1 Size (B)                                           423,392
    Gen 2 GC Count (Count / 1 sec)                                 0
    Gen 2 Size (B)                                           203,248
    LOH Size (B)                                             933,216
    Monitor Lock Contention Count (Count / 1 sec)                  0
    Number of Active Timers                                        1
    Number of Assemblies Loaded                                   39
    ThreadPool Completed Work Item Count (Count / 1 sec)           0
    ThreadPool Queue Length                                        0
    ThreadPool Thread Count                                        3
    Working Set (MB)                                              30

如需詳細資訊,請參閱 dotnet-counters。 如需深入了解 .NET 中的計量,請參閱內建計量

使用 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;
using OpenTelemetry.Metrics;
using System.Diagnostics.Metrics;

class Program
{
    static Meter s_meter = new("HatCo.HatStore", "1.0.0");
    static Counter<int> s_hatsSold = s_meter.CreateCounter<int>(
        name: "hats-sold",
        unit: "Hats",
        description: "The number of hats sold in our store");

    static void Main(string[] args)
    {
        using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder()
                .AddMeter("HatCo.HatStore")
                .AddPrometheusHttpListener(options => options.UriPrefixes = new string[] { "http://localhost:9184/" })
                .Build();

        var rand = Random.Shared;
        Console.WriteLine("Press any key to exit");
        while (!Console.KeyAvailable)
        {
            //// Simulate hat selling transactions.
            Thread.Sleep(rand.Next(100, 2500));
            s_hatsSold.Add(rand.Next(0,1000));
        }
    }
}

在上述程式碼中:

  • AddMeter("HatCo.HatStore") 會設定 OpenTelemetry,以傳輸應用程式中所定義計量 (meter) 收集的所有計量。
  • AddPrometheusHttpListener 將 OpenTelemetry 設定為:
    • 在連接埠 9184 上公開 Prometheus 的計量端點
    • 使用 HttpListener。

如需 OpenTelemetry 組態選項的詳細資訊,請參閱 OpenTelemetry 文件。 OpenTelemetry 文件顯示 ASP.NET 應用程式的裝載選項。

執行應用程式,讓應用程式保持在執行狀態以便收集度量:

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 Web 入口網站的 [圖表] 頁面上,於運算式文字方塊中輸入 hats,然後在 [圖表] 索引標籤中選取 hats_sold_Hatshat,Prometheus 會顯示範例應用程式發出的「hats-sold」計數器值不斷增加。 Prometheus hats sold graph

在上圖中,圖表時間會設定為 5m,也就是5分鐘。

如果 Prometheus 伺服器抓取範例應用程式的時間尚短,則您可能需要等候以累積資料。

在 Grafana 儀表板上顯示計量

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

  2. 請按一下 Grafana 入口網站左側工具列上的 + 圖示,然後選取 [Dashboard],以建立 Grafana 儀表板。 在隨即出現的儀表板編輯器中,在 [Title] 輸入方塊中輸入 Hats Sold/Sec,並在 PromQL 運算式欄位中輸入 rate(hats_sold[5m])

    Hats sold Grafana dashboard editor

  3. 按一下 [Apply],以儲存並檢視新儀表板。

    Hats sold Grafana dashboard]

使用 .NET MeterListener API 建立自訂收集工具

.NET MeterListener API 可讓您建立自訂同處理序邏輯,以觀察 System.Diagnostics.Metrics.Meter 所記錄的度量。 如需建立與舊版 EventCounters 檢測相容的自訂邏輯指引,請參閱 EventCounters

修改 Program.cs 的程式碼以使用 MeterListener

using System.Diagnostics.Metrics;

class Program
{
    static Meter s_meter = new("HatCo.HatStore", "1.0.0");
    static Counter<int> s_hatsSold = s_meter.CreateCounter<int>(
        name: "hats-sold",
        unit: "Hats",
        description: "The number of hats sold in our store");

    static void Main(string[] args)
    {
        using MeterListener meterListener = new();
        meterListener.InstrumentPublished = (instrument, listener) =>
        {
            if (instrument.Meter.Name is "HatCo.HatStore")
            {
                listener.EnableMeasurementEvents(instrument);
            }
        };

        meterListener.SetMeasurementEventCallback<int>(OnMeasurementRecorded);
        // Start the meterListener, enabling InstrumentPublished callbacks.
        meterListener.Start();

        var rand = Random.Shared;
        Console.WriteLine("Press any key to exit");
        while (!Console.KeyAvailable)
        {
            //// Simulate hat selling transactions.
            Thread.Sleep(rand.Next(100, 2500));
            s_hatsSold.Add(rand.Next(0, 1000));
        }
    }

    static void OnMeasurementRecorded<T>(
        Instrument instrument,
        T measurement,
        ReadOnlySpan<KeyValuePair<string, object?>> tags,
        object? state)
    {
        Console.WriteLine($"{instrument.Name} recorded measurement {measurement}");
    }
}

下列輸出會顯示應用程式輸出,並針對每個度量使用自訂回呼:

> dotnet run
Press any key to exit
hats-sold recorded measurement 978
hats-sold recorded measurement 775
hats-sold recorded measurement 666
hats-sold recorded measurement 66
hats-sold recorded measurement 914
hats-sold recorded measurement 912
...

說明程式碼範例

本節中的程式碼片段來自上述範例。

在下列醒目提示的程式碼中,建立了 MeterListener 執行個體來接收度量。 using 關鍵字會在 meterListener 超出範圍時呼叫 Dispose

using MeterListener meterListener = new();
meterListener.InstrumentPublished = (instrument, listener) =>
{
    if (instrument.Meter.Name is "HatCo.HatStore")
    {
        listener.EnableMeasurementEvents(instrument);
    }
};

下列醒目提示的程式碼會設定接聽程式從哪些檢測接收度量。 InstrumentPublished 是一項委派,會在應用程式內建立新檢測時叫用。

using MeterListener meterListener = new();
meterListener.InstrumentPublished = (instrument, listener) =>
{
    if (instrument.Meter.Name is "HatCo.HatStore")
    {
        listener.EnableMeasurementEvents(instrument);
    }
};

該委派可以檢查檢測,以決定是否要訂閱。 例如,委派可以檢查名稱、計量或任何其他公用屬性。 EnableMeasurementEvents 可接收來自指定檢測的度量。 透過另一種方法取得檢測參考的程式碼:

  • 這並不常用。
  • 隨時都可以使用參考來叫用 EnableMeasurementEvents()

藉由呼叫 SetMeasurementEventCallback 來設定從檢測接收度量時叫用的委派:

    meterListener.SetMeasurementEventCallback<int>(OnMeasurementRecorded);
    // Start the meterListener, enabling InstrumentPublished callbacks.
    meterListener.Start();

    var rand = Random.Shared;
    Console.WriteLine("Press any key to exit");
    while (!Console.KeyAvailable)
    {
        //// Simulate hat selling transactions.
        Thread.Sleep(rand.Next(100, 2500));
        s_hatsSold.Add(rand.Next(0, 1000));
    }
}

static void OnMeasurementRecorded<T>(
    Instrument instrument,
    T measurement,
    ReadOnlySpan<KeyValuePair<string, object?>> tags,
    object? state)
{
    Console.WriteLine($"{instrument.Name} recorded measurement {measurement}");
}

泛型參數會控制回呼接收哪些資料類型的度量。 例如,Counter<int> 會產生 int 度量,而 Counter<double> 會產生 double 度量。 您可以使用 byteshortintlongfloatdoubledecimal 類型來建立檢測。 除非您有案例特定的知識,否則建議您為每個資料類型註冊回呼,但並非所有資料類型都需要。 對具有不同泛型引數的 SetMeasurementEventCallback 進行重複呼叫,可能會顯得有點不尋常。 該 API 的設計方式是為了讓 MeterListener 能夠接收效能負載低的度量,其通常只有幾奈秒。

呼叫 MeterListener.EnableMeasurementEvents 時,可以提供 state 物件做為其中一個參數。 state 物件是任意的。 如果您在該呼叫中提供狀態物件,則會使用該檢測來儲存它,並將其當作回呼中的 state 參數傳回給您。 這既是為了方便,也是為了效能最佳化。 接聽程式通常需要:

  • 為每個在記憶體中儲存度量的檢測建立物件。
  • 讓程式碼對這些度量執行計算。

或者,建立從檢測對應到儲存物件的 Dictionary,並在每次度量時進行查找。 使用 Dictionary 的速度比從 state 存取慢得多。

meterListener.Start();

上述程式碼會啟動啟用回呼的 MeterListener。 程序中每個預先存在的檢測都會叫用 InstrumentPublished 委派。 新建立的檢測物件也會觸發 InstrumentPublished 被叫用。

using MeterListener meterListener = new MeterListener();

當應用程式完成接聽之後,處置接聽程式會停止回呼的流程,並釋放接聽程式物件的任何內部參考。 宣告 meterListener 時所使用的 using 關鍵字會造成變數超出範圍時呼叫 Dispose。 請注意,Dispose 只保證不會起始新的回呼。 因為回呼發生在不同的執行緒上,所以在對 Dispose 的呼叫傳回之後,可能仍有回呼正在進行。

為了保證回呼中某個區域的程式碼目前未執行,且未來也不會執行,則必須新增執行緒同步處理。 依預設,Dispose 不會包含同步處理,因為:

  • 同步處理會在每個度量回呼中增加效能負載。
  • MeterListener 設計為具有高效能意識的 API。