共用方式為


收集指標

本文適用於:✔️ .NET 6.0 和更新版本 ✔️ .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 計量器的所有指標範例。 計量名稱會區分大小寫。 我們的範例應用程式名為 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 進行操作。
  • Azure 監視器 和許多 APM(應用程式效能管理)廠商支持。

本教程展示如何使用 OSS PrometheusGrafana 專案來整合 OpenTelemetry 度量。 計量數據流:

  1. .NET 計量 API 會記錄來自範例應用程式的度量。

  2. 在應用程式中執行的 OpenTelemetry 連結庫會匯總度量。

  3. Prometheus 匯出工具連結庫會透過 HTTP 計量端點提供匯總的數據。 'Exporter' 是 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 第一個步驟來設定 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 入口網站的 [ 狀態>目標 ] 頁面中處於 UP 狀態。 Prometheus 狀態

  3. 在 Prometheus 入口網站的 [圖形] 頁面上,於表達式文本框中輸入 hats,然後在 [圖表] 索引標籤中選取 hats_sold_Hatshat,Prometheus 會顯示範例應用程式所發出之「hats-sold」計數器的遞增變化。 Prometheus 帽子銷售圖表

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

如果 Prometheus 伺服器尚未長時間擷取範例應用程式,您可能需要等候數據累積。

在 Grafana 儀錶板上顯示計量

  1. 請遵循 標準指示 來安裝 Grafana,並將其連線到 Prometheus 數據源。

  2. 點擊 Grafana 網頁入口左側工具列上的 + 圖示,然後選擇 儀錶板,以建立 Grafana 儀錶板。 在出現的儀錶板編輯器中,於 標題 輸入框中輸入 Hats Sold/Sec,並在 PromQL 表達式欄位中輸入 rate(hats_sold[5m])

    Hats 銷售 Grafana 儀錶板編輯器

  3. 按兩下 [套用 ] 以儲存並檢視新的儀錶板。

    Hats 銷售 Grafana 儀錶板 ]

使用 .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會在Dispose超出範圍時呼叫meterListener

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,從而啟用回呼功能。 程序中的每個預存 Instrument 都會調用委派 InstrumentPublished。 新建立的 Instrument 物件也會觸發 InstrumentPublished 被調用。

using MeterListener meterListener = new MeterListener();

當應用程式完成接聽時,釋放接聽器會停止回呼流程,並釋放對接聽器物件的任何內部參照。 宣告using時使用的meterListener關鍵詞會在該變數Dispose超出作用域時被呼叫。 請注意,Dispose 僅承諾不會啟動新的回呼。 因為回呼發生在不同的執行緒上,因此即使在呼叫 Dispose 傳回之後,仍然可能有回呼正在進行中。

若要保證回呼中的特定程式代碼區域目前未執行,且未來不會執行,則必須新增線程同步處理。 Dispose 預設不會包含同步處理,因為:

  • 同步處理會在每個度量回呼中增加額外的效能負擔。
  • MeterListener 設計為注重效能的 API。