建立指標
本文適用於: ✔️ .NET Core 6 和更新版本 ✔️ .NET Framework 4.6.1 和更新版本
您可以使用 System.Diagnostics.Metrics API 來檢測 .NET 應用程式,以追蹤重要的計量。 某些計量包含在標準 .NET 連結庫中,但您可能想要新增與應用程式和連結庫相關的自定義計量。 在本教學課程中,您將新增計量,並瞭解可用的計量類型。
注意
.NET 有一些較舊的計量 API,也就是 EventCounters 和 System.Diagnostics.PerformanceCounter,此處未涵蓋。 若要深入瞭解這些替代方案,請參閱 比較計量 API。
建立自訂計量
必要條件:.NET Core 6 SDK 或更新版本
建立參考 System.Diagnostics.DiagnosticSource NuGet 套件 8 版或更新版本的新控制台應用程式。 以 .NET 8+ 為目標的應用程式預設會包含此參考。 然後,更新 Program.cs
中的程式代碼以符合:
> dotnet new console
> dotnet add package System.Diagnostics.DiagnosticSource
using System;
using System.Diagnostics.Metrics;
using System.Threading;
class Program
{
static Meter s_meter = new Meter("HatCo.Store");
static Counter<int> s_hatsSold = s_meter.CreateCounter<int>("hatco.store.hats_sold");
static void Main(string[] args)
{
Console.WriteLine("Press any key to exit");
while(!Console.KeyAvailable)
{
// Pretend our store has a transaction each second that sells 4 hats
Thread.Sleep(1000);
s_hatsSold.Add(4);
}
}
}
System.Diagnostics.Metrics.Meter 類型是函式庫用來建立具名稱的工具組的進入點。 儀器會記錄計算指標所需的數值測量。 在這裡,我們使用 CreateCounter 來建立名為「hatco.store.hats_sold」的計數器。 在每個模擬交易過程中,程式碼會呼叫 Add 來記錄已售出帽子的數量,在此案例中為 4。 “hatco.store.hats_sold” 儀器隱含定義了一些指標,這些指標可以從這些測量值中計算,例如銷售的帽子總數或每秒帽子銷量。最後,由指標收集工具決定要計算的指標以及如何執行這些計算,但每個儀器都有一些預設慣例來傳達開發人員的意圖。 針對計數器工具,慣例是收集工具會顯示計數總計和/或計數增加的速率。
Counter<int>
和 CreateCounter<int>(...)
上的泛型參數 int
定義這個計數器必須能夠儲存最多 Int32.MaxValue的值。 您可以使用任何 byte
、short
、int
、long
、float
、double
或 decimal
,視您需要儲存的數據大小,以及是否需要小數值而定。
執行應用程式,並讓它立即執行。 接下來,我們將檢視指標。
> dotnet run
Press any key to exit
最佳做法
針對未設計用於依賴注入(DI)容器的程式碼,建立一次計數器,並將其儲存在靜態變數中。 針對 DI 感知程式庫中使用靜態變數的情況,會被視為反模式,而下列 DI 範例 顯示更具代表性的方法。 每個程式庫或程式庫子元件都可以(而且通常應該)建立自己的 Meter。 如果您預期應用程式開發人員能夠輕鬆地啟用和停用計量群組,請考慮建立新的計量,而不是重複使用現有的計量。
傳遞給 Meter 建構函式的名稱應該是唯一的,才能與其他度量器區別。 我們建議遵循 OpenTelemetry 命名指導方針,其中使用點狀階層式名稱。 要檢測之程式代碼的元件名稱或命名空間名稱通常是不錯的選擇。 如果元件在第二個獨立元件中新增程式代碼的檢測,則名稱應該以定義 Meter 的元件為基礎,而不是要檢測其程式代碼的元件。
.NET 不會強制執行 Instruments 的任何命名配置,但我們建議遵循 OpenTelemetry 命名指導方針,其使用小寫點點的階層式名稱和底線('_')作為相同元素中多個單字之間的分隔符。 並非所有度量工具都會保留度量名稱作為最終度量名稱的一部分,因此讓工具名稱本身具有全球唯一性會很有幫助。
範例儀器名稱:
contoso.ticket_queue.duration
contoso.reserved_tickets
contoso.purchased_tickets
用來建立儀器和記錄測量的 API 是線程安全的。 在 .NET 連結庫中,大部分的實例方法都需要從多個線程在相同物件上叫用時進行同步處理,但在此情況下不需要。
用來記錄測量的儀器 API(此範例中使用Add),在未收集數據時,通常執行時間為 <10 ns,而當使用高效能的資料收集程式庫或工具來收集測量時,執行時間可能為數十到數百奈秒。 這可讓這些 API 在大部分情況下自由使用,但對於效能非常敏感的程式代碼需小心。
檢視新的指標
有許多選項可用來儲存和檢視計量。 本教學課程使用 dotnet-counters 工具,非常適合即席分析。 您也可以查看 計量集合教學課程,以尋求其他選項。 如果尚未安裝 dotnet-counters 工具,請使用 SDK 進行安裝:
> dotnet tool update -g dotnet-counters
You can invoke the tool using the following command: dotnet-counters
Tool 'dotnet-counters' (version '7.0.430602') was successfully installed.
當範例應用程式仍在執行時,請使用 dotnet-counters 來監視新的計數器:
> dotnet-counters monitor -n metric-demo.exe --counters HatCo.Store
Press p to pause, r to resume, q to quit.
Status: Running
[HatCo.Store]
hatco.store.hats_sold (Count / 1 sec) 4
如預期般,您可以看到 HatCo 商店正在穩步銷售每秒 4 頂帽子。
透過相依性插入取得計量
在上一個範例中,透過使用 new
來建構計量器,然後將其指派給靜態欄位。 在使用相依注入時,這樣使用靜態不是一個好的方法。 在使用 DI 的程式代碼中,例如 ASP.NET Core 或具有 一般主機的應用程式,請使用 IMeterFactory建立 Meter 物件。 從 .NET 8 開始,主機會自動在服務容器中註冊 IMeterFactory,或者您可以藉由呼叫 AddMetrics,在任何 IServiceCollection 中手動註冊類型。
計量處理站會整合計量與 DI,即使計量使用相同的名稱,仍會將計量保留在彼此隔離的不同服務集合中。 這在測試中特別有用,如此一來,多個平行執行的測試只會觀察來自相同測試案例的測量值。
若要在專為 DI 設計的型別中取得計量,請將 IMeterFactory 參數新增至建構函式,然後呼叫 Create。 此範例示範在 ASP.NET Core 應用程式中使用 IMeterFactory。
定義用來儲存儀器的類型:
public class HatCoMetrics
{
private readonly Counter<int> _hatsSold;
public HatCoMetrics(IMeterFactory meterFactory)
{
var meter = meterFactory.Create("HatCo.Store");
_hatsSold = meter.CreateCounter<int>("hatco.store.hats_sold");
}
public void HatsSold(int quantity)
{
_hatsSold.Add(quantity);
}
}
在 Program.cs
中向 DI 容器註冊類型。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<HatCoMetrics>();
視需要插入計量類型和記錄值。 由於計量類型已在 DI 中註冊,因此可以搭配 MVC 控制器、最小 API 或 DI 所建立的任何其他類型使用:
app.MapPost("/complete-sale", ([FromBody] SaleModel model, HatCoMetrics metrics) =>
{
// ... business logic such as saving the sale to a database ...
metrics.HatsSold(model.QuantitySold);
});
最佳做法
-
System.Diagnostics.Metrics.Meter 實作 IDisposable,但 IMeterFactory 會自動管理它所建立之任何
Meter
物件的存留期,在處置 DI 容器時處置它們。 不需要新增額外的代碼以在Meter
上叫用Dispose()
,並且這樣做不會有任何效果。
儀器類型
到目前為止,我們只示範了一種 Counter<T> 儀器,但有更多的儀器類型可供使用。 儀器有兩種不同的方式:
- 預設指標計算 - 收集和分析儀器測量值的工具會根據不同儀器計算不同的預設指標。
- 匯總數據的記憶體 - 最實用的計量需要從許多度量匯總數據。 其中一個選項是呼叫端會在任意時間提供每一筆量測值,而收集工具會管理聚合。 或者,呼叫端可以管理匯總度量,並在回呼中視需要提供它們。
目前可用的儀器類型:
計數器 (CreateCounter) - 此儀器會追蹤隨著時間增加的值,呼叫者使用 Add來報告增加量。 大部分工具都會計算總計和總變化率。 對於僅顯示單一內容的工具,建議使用變更率。 例如,假設呼叫端會以連續值 1、2、4、5、4、3 每秒叫用一次
Add()
。 如果收集工具每隔三秒更新一次,則三秒后的總計為 1+2+4=7,而六秒后的總計為 1+2+4+5+4+3=19。 變更率是 (current_total - previous_total),因此在三秒時,工具會報告 7-0=7,並在六秒后報告 19-7=12。UpDownCounter (CreateUpDownCounter) - 此工具追蹤隨時間可能增加或減少的值。 呼叫端會使用 Add來報告遞增和遞減。 例如,假設呼叫端每秒以連續值 1、5、-2、3、-1、-3 叫用一次
Add()
。 如果收集工具每隔三秒更新一次,則三秒后的總計為 1+5-2=4,而六秒后的總計為 1+5-2+3-1-3=3。ObservableCounter (CreateObservableCounter) - 此工具類似於 Counter,不同之處在於呼叫端現在負責維護總計。 呼叫端會在建立 ObservableCounter 時提供回呼委派,而且每當工具需要觀察目前的總計時,就會叫用回呼。 例如,如果集合工具每隔三秒更新一次,則回呼函式也會每隔三秒叫用一次。 大部分工具都會提供總計和總計變化率。 如果只能顯示一個,建議顯示變化率。 如果回呼在初始呼叫時傳回 0,在三秒後再次呼叫時傳回 7,並在六秒後呼叫時傳回 19,則工具會將這些值作為總計報告且不進行更改。 針對變更率,工具會在三秒后顯示 7-0=7,而 19-7=12 會在六秒之後顯示。
ObservableUpDownCounter (CreateObservableUpDownCounter) - 這項工具類似於 UpDownCounter,不同之處在於呼叫端現在負責維護匯總總計。 呼叫端在建立 ObservableUpDownCounter 時提供回呼代表,而且每當工具需要觀察目前總計時,就會叫用回呼。 例如,如果集合工具每隔三秒更新一次,則回呼函式也會每隔三秒叫用一次。 回呼函式所傳回的任何值將在集合工具中不變地顯示為總計。
儀表 (CreateGauge) - 這個工具允許呼叫者使用 Record 方法來設定指標的當前值。 您可以隨時叫用 方法來更新值,而計量集合工具會顯示最近設定的任何值。
ObservableGauge (CreateObservableGauge) - 此儀表允許呼叫者提供回調函數,測量值會直接作為度量傳遞。 每次收集工具更新時,都會叫用回呼,而且回呼傳回的任何值都會顯示在工具中。
直方圖 (CreateHistogram)- 此儀器會追蹤測量的分佈情況。 沒有一個標準的方式來描述一組度量,但建議使用直方圖或計算百分位數的工具。 例如,假設呼叫者在收集工具的更新間隔期間叫用 Record 來記錄這些度量:1,5,2,3,10,9,7,4,6,8。 收集工具可能會報告這些測量的第 50、90 和第 95 個百分位數分別為 5、9 和 9。
注意
如需瞭解在建立直方圖工具時如何設定建議的區間界限的詳細資訊,請參閱:使用建議來自定義直方圖工具。
選取檢測類型時的最佳做法
若要計數事物或任何其他只隨著時間增加的值,請使用 Counter 或 ObservableCounter。 根據將其新增到現有程式碼中的容易程度,在 Counter 和 ObservableCounter 之間進行選擇:選擇提供每個遞增操作的 API 呼叫,或是選擇回呼從程式碼維護的變數中讀取當前總計。 在效能很重要且在極其繁忙的程式代碼路徑中使用 Add 時,如果每秒每個線程會發出超過一百萬次呼叫,使用 ObservableCounter 可能會有更多優化的機會。
對於計時事項,通常偏好直方圖。 通常,瞭解這些分佈的尾端(第90、95、99個百分位數)而不是平均值或總計很有用。
其他常見案例,例如快取命中率或快取大小、佇列和檔案的大小,通常非常適合
UpDownCounter
或ObservableUpDownCounter
。 根據哪種方法較容易新增至現有程式碼來選擇:是為每個遞增和遞減操作進行 API 呼叫,還是使用回呼來從程式碼所維護的變數中讀取當前值。
注意
如果您使用舊版的 .NET 或不支援 UpDownCounter
和 ObservableUpDownCounter
的 DiagnosticSource NuGet 套件(在版本 7 之前),ObservableGauge
通常是很好的替代專案。
不同儀器類型的範例
停止先前啟動的範例程式,並將 Program.cs
中的範例程式代碼取代為:
using System;
using System.Diagnostics.Metrics;
using System.Threading;
class Program
{
static Meter s_meter = new Meter("HatCo.Store");
static Counter<int> s_hatsSold = s_meter.CreateCounter<int>("hatco.store.hats_sold");
static Histogram<double> s_orderProcessingTime = s_meter.CreateHistogram<double>("hatco.store.order_processing_time");
static int s_coatsSold;
static int s_ordersPending;
static Random s_rand = new Random();
static void Main(string[] args)
{
s_meter.CreateObservableCounter<int>("hatco.store.coats_sold", () => s_coatsSold);
s_meter.CreateObservableGauge<int>("hatco.store.orders_pending", () => s_ordersPending);
Console.WriteLine("Press any key to exit");
while(!Console.KeyAvailable)
{
// Pretend our store has one transaction each 100ms that each sell 4 hats
Thread.Sleep(100);
s_hatsSold.Add(4);
// Pretend we also sold 3 coats. For an ObservableCounter we track the value in our variable and report it
// on demand in the callback
s_coatsSold += 3;
// Pretend we have some queue of orders that varies over time. The callback for the orders_pending gauge will report
// this value on-demand.
s_ordersPending = s_rand.Next(0, 20);
// Last we pretend that we measured how long it took to do the transaction (for example we could time it with Stopwatch)
s_orderProcessingTime.Record(s_rand.Next(5, 15)/1000.0);
}
}
}
執行新的進程,並在第二個殼層中使用 dotnet-counters 來檢視計量:
> dotnet-counters monitor -n metric-demo.exe --counters HatCo.Store
Press p to pause, r to resume, q to quit.
Status: Running
Name Current Value
[HatCo.Store]
hatco.store.coats_sold (Count) 8,181
hatco.store.hats_sold (Count) 548
hatco.store.order_processing_time
Percentile
50 0.012
95 0.013
99 0.013
hatco.store.orders_pending 9
此範例會使用一些隨機產生的數位,因此您的值會有所不同。 Dotnet-counters 會將直方圖儀器轉譯為三個百分位數的統計數據(第 50 位、95 號和第 99 位),但其他工具可能會以不同的方式摘要分佈,或提供更多組態選項。
最佳做法
直方圖通常會將比其他計量類型更多的數據儲存在記憶體中。 不過,確切的記憶體使用量是由所使用的收集工具所決定。 如果您要定義大量的直方圖指標(>100),您可能需要提供使用者指引,告訴他們不要同時啟用所有指標,或設定其工具,以透過降低精確度來節省記憶體。 某些收集工具可能會對要監視的並行直方圖數目有硬性限制,以防止過度使用記憶體。
所有可觀察的儀器的回調會按順序依次被調用,因此任何耗時過長的回調可能會延遲或阻止收集所有度量。 偏好快速讀取快取值、不傳回任何度量或擲回例外狀況,而非進行任何可能長時間執行或封鎖的作業。
ObservableCounter、ObservableUpDownCounter 和 ObservableGauge 的回呼發生在通常不與更新值的程式碼同步的執行緒上。 您有責任同步處理記憶體存取,或接受因使用未同步存取而產生的不一致值。 同步存取的常見方法是使用鎖定或調用 Volatile.Read 或 Volatile.Write。
CreateObservableGauge 和 CreateObservableCounter 函式會傳回檢測物件,但在大多數情況下,您不需要將它儲存在變數中,因為不需要與對象進行進一步的互動。 如同我們針對其他工具所做的一樣,將它指派給靜態變數是合法的,但容易發生錯誤,因為 C# 靜態初始化是延遲的,而且通常永遠不會參考變數。 以下是問題的範例:
using System; using System.Diagnostics.Metrics; class Program { // BEWARE! Static initializers only run when code in a running method refers to a static variable. // These statics will never be initialized because none of them were referenced in Main(). // static Meter s_meter = new Meter("HatCo.Store"); static ObservableCounter<int> s_coatsSold = s_meter.CreateObservableCounter<int>("hatco.store.coats_sold", () => s_rand.Next(1,10)); static Random s_rand = new Random(); static void Main(string[] args) { Console.ReadLine(); } }
描述和單位
儀器可以指定選擇性的描述和單位。 這些值對所有計量計算不透明,但可在集合工具 UI 中顯示,以協助工程師瞭解如何解譯數據。 停止您先前啟動的範例程式,並將 Program.cs
中的範例程式代碼取代為:
using System;
using System.Diagnostics.Metrics;
using System.Threading;
class Program
{
static Meter s_meter = new Meter("HatCo.Store");
static Counter<int> s_hatsSold = s_meter.CreateCounter<int>(name: "hatco.store.hats_sold",
unit: "{hats}",
description: "The number of hats sold in our store");
static void Main(string[] args)
{
Console.WriteLine("Press any key to exit");
while(!Console.KeyAvailable)
{
// Pretend our store has a transaction each 100ms that sells 4 hats
Thread.Sleep(100);
s_hatsSold.Add(4);
}
}
}
執行新的程序,並在第二個終端中使用 dotnet-counters 來檢視指標:
Press p to pause, r to resume, q to quit.
Status: Running
Name Current Value
[HatCo.Store]
hatco.store.hats_sold ({hats}) 40
dotnet-counters 目前不會在 UI 中使用描述文字,但會在提供時顯示單位。 在此情況下,您會看到 “{hats}” 已取代先前描述中可見的泛型詞彙 “Count”。
最佳做法
.NET API 允許使用任何字串作為單位,但我們建議使用 UCUM,這是單位名稱的國際標準。 “{hats}” 周圍的大括號是 UCUM 標準的一部分,表示它是描述性註解,而不是像秒或位元組那樣具有標準化意義的單位名稱。
建構函式中指定的單位應該描述適合個別測量的單位。 這有時會與最終回報計量上的單位不同。 在此範例中,每個度量都是一些帽子,因此 “{hats}” 是傳入建構函式的適當單位。 收集工具可以計算變更率,並自行推導出計算速率的適當單位為 {hats}/秒。
記錄時間測量時,應優先將秒數記錄為浮點數或雙精度浮點數。
多維度計量
度量也可以與稱為標籤的索引鍵/值組相關聯,以允許數據分類進行分析。 例如,HatCo 可能不僅想要記錄已售出的帽子數目,還能記錄其大小和色彩。 稍後分析數據時,HatCo 工程師可以依大小、色彩或兩者的任何組合來細分總計。
在具有一個或多個 KeyValuePair
參數的 Add 和 Record 超載中,可以指定計數器和直方圖標籤。 例如:
s_hatsSold.Add(2,
new KeyValuePair<string, object?>("product.color", "red"),
new KeyValuePair<string, object?>("product.size", 12));
將 Program.cs
的程式碼替換掉,然後重新執行應用程式和 dotnet-counters,就像之前一樣。
using System;
using System.Collections.Generic;
using System.Diagnostics.Metrics;
using System.Threading;
class Program
{
static Meter s_meter = new Meter("HatCo.Store");
static Counter<int> s_hatsSold = s_meter.CreateCounter<int>("hatco.store.hats_sold");
static void Main(string[] args)
{
Console.WriteLine("Press any key to exit");
while(!Console.KeyAvailable)
{
// Pretend our store has a transaction, every 100ms, that sells two size 12 red hats, and one size 19 blue hat.
Thread.Sleep(100);
s_hatsSold.Add(2,
new KeyValuePair<string,object?>("product.color", "red"),
new KeyValuePair<string,object?>("product.size", 12));
s_hatsSold.Add(1,
new KeyValuePair<string,object?>("product.color", "blue"),
new KeyValuePair<string,object?>("product.size", 19));
}
}
}
Dotnet-counters 現在會顯示基本的分類方式:
Press p to pause, r to resume, q to quit.
Status: Running
Name Current Value
[HatCo.Store]
hatco.store.hats_sold (Count)
product.color product.size
blue 19 73
red 12 146
針對 ObservableCounter 和 ObservableGauge,標記的度量可以在傳遞至建構函式的回呼中提供:
using System;
using System.Collections.Generic;
using System.Diagnostics.Metrics;
using System.Threading;
class Program
{
static Meter s_meter = new Meter("HatCo.Store");
static void Main(string[] args)
{
s_meter.CreateObservableGauge<int>("hatco.store.orders_pending", GetOrdersPending);
Console.WriteLine("Press any key to exit");
Console.ReadLine();
}
static IEnumerable<Measurement<int>> GetOrdersPending()
{
return new Measurement<int>[]
{
// pretend these measurements were read from a real queue somewhere
new Measurement<int>(6, new KeyValuePair<string,object?>("customer.country", "Italy")),
new Measurement<int>(3, new KeyValuePair<string,object?>("customer.country", "Spain")),
new Measurement<int>(1, new KeyValuePair<string,object?>("customer.country", "Mexico")),
};
}
}
如之前使用 dotnet-counters 執行時,結果如下:
Press p to pause, r to resume, q to quit.
Status: Running
Name Current Value
[HatCo.Store]
hatco.store.orders_pending
customer.country
Italy 6
Mexico 1
Spain 3
最佳做法
雖然 API 允許使用任何對象作為標記值,但集合工具會預期數值類型和字串。 指定的收集工具可能或可能不支援其他類型。
我們建議標記名稱遵循 OpenTelemetry 命名指導方針,其使用小寫虛線階層名稱搭配 '_' 字元來分隔相同元素中的多個單字。 如果在不同的計量或其他遙測記錄中重複使用標記名稱,則標籤名稱應該在所使用的任何地方具有相同的意義和一組法律值。
範例標籤名稱:
customer.country
store.payment_method
store.purchase_result
請注意在實踐中紀錄非常大或無限制的標記值組合。 雖然 .NET API 實作可以處理它,但收集工具可能會為與每個標記組合相關聯的計量數據配置記憶體,而且這可能會變得非常大。 例如,如果 HatCo 有 10 種不同的帽子色彩和 25 種帽子尺寸,那麼最多可以追蹤 10*25=250 個銷售總數。不過,如果 HatCo 新增第三個標籤,即用於銷售的顧客身分識別碼,而他們在全球銷售給 1 億名客戶,那麼現在可能會記錄數十億種不同的標籤組合。 大部分的數據收集工具會刪除數據以保持在技術限制內,或者可能需要支出高昂的成本來涵蓋數據儲存和處理。 每個收集工具的實施將決定其限制,但對於一個儀器來說,少於1000個組合應該是安全的。 超過 1000 種組合的情況下,收集工具都需要套用篩選,或是設計成可以在大規模下運作。 直方圖實作通常會使用比其他計量更多的記憶體,因此安全限制可能會降低 10-100 倍。 如果您預期會有大量的獨特標記組合,則日誌、事務資料庫或巨量數據處理系統可能更適合以所需的規模運作。
對於具有非常大量標記組合的儀器,偏好使用較小的儲存類型來協助降低記憶體負荷。 例如,儲存
short
的Counter<short>
只會占用每個標籤組合的 2 個字節,而double
的Counter<double>
佔用了每個標籤組合的 8 個字節。建議優化收集工具,使其在每次調用中,針對相同儀器的記錄測量,皆以相同順序指定一組相同的標記名稱。 對於需要經常呼叫 Add 和 Record 的高效能程式代碼,偏好針對每個呼叫使用相同的標記名稱序列。
.NET API 已優化為對於 Add 和 Record 呼叫在個別指定三個或更少標籤的情況下實現無配置。 若要避免分配大量標記,請使用 TagList。 一般而言,這些呼叫的效能額外負荷會隨著使用更多標記而增加。
注意
OpenTelemetry 將標籤稱為「屬性」。 這些是相同功能的兩個不同的名稱。
使用建議來客製化直方圖儀表
使用直方圖時,負責收集數據的工具或庫,負責決定如何最佳地呈現所記錄數值的分佈。 使用 OpenTelemetry時,常見的策略(以及
收集直方圖數據的工具或函式庫負責定義其將使用的桶。 使用 OpenTelemetry 時的預設貯體組態為:[ 0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000 ]
。
預設值可能不會產生每個直方圖的最佳粒度。 例如,持續時間不到一秒的請求全都會落在 0
分組中。
收集直方圖數據的工具或函式庫可能會提供機制,讓使用者自定義區間配置。 例如,OpenTelemetry 會定義 View API。 不過,這需要用戶的操作,並使使用者有責任充分了解數據分佈,以選擇正確的分類。
為了提升使用體驗,9.0.0
版 System.Diagnostics.DiagnosticSource
套件引入了(InstrumentAdvice<T>)API。
檢測工具作者可以使用 InstrumentAdvice
API 來指定直方圖所建議的預設分桶邊界集合。 接著,收集直方圖數據的工具或程式庫可以在設定匯總時選擇使用這些值,進而為使用者提供更順暢的入門體驗。 自 1.10.0 版起,OpenTelemetry .NET SDK
重要
一般而言,更多的桶會使給定的直方圖數據更精確,但每個桶都需要使用記憶體來儲存匯總的詳細信息,而且在處理測量時,尋找正確的桶會增加 CPU 的成本。 在選擇要透過 InstrumentAdvice
API 推薦的桶子數目時,請務必瞭解精確性與 CPU/記憶體耗用量之間的權衡。
以下程式碼示範使用 InstrumentAdvice
API 來設定建議的預設桶。
using System;
using System.Diagnostics.Metrics;
using System.Threading;
class Program
{
static Meter s_meter = new Meter("HatCo.Store");
static Histogram<double> s_orderProcessingTime = s_meter.CreateHistogram<double>(
name: "hatco.store.order_processing_time",
unit: "s",
description: "Order processing duration",
advice: new InstrumentAdvice<double> { HistogramBucketBoundaries = [0.01, 0.05, 0.1, 0.5, 1, 5] });
static Random s_rand = new Random();
static void Main(string[] args)
{
Console.WriteLine("Press any key to exit");
while (!Console.KeyAvailable)
{
// Pretend our store has one transaction each 100ms
Thread.Sleep(100);
// Pretend that we measured how long it took to do the transaction (for example we could time it with Stopwatch)
s_orderProcessingTime.Record(s_rand.Next(5, 15) / 1000.0);
}
}
}
其他資訊
如需了解 OpenTelemetry 中明確分桶直方圖的詳細資訊,請參閱:
測試自定義計量
您可以使用 MetricCollector<T>來測試您新增的任何自訂計量。 此類型可讓您輕鬆地記錄來自特定儀器的測量,並判斷值正確無誤。
使用相依性插入進行測試
下列程式代碼示範使用相依性插入和 IMeterFactory 之程式代碼元件的範例測試案例。
public class MetricTests
{
[Fact]
public void SaleIncrementsHatsSoldCounter()
{
// Arrange
var services = CreateServiceProvider();
var metrics = services.GetRequiredService<HatCoMetrics>();
var meterFactory = services.GetRequiredService<IMeterFactory>();
var collector = new MetricCollector<int>(meterFactory, "HatCo.Store", "hatco.store.hats_sold");
// Act
metrics.HatsSold(15);
// Assert
var measurements = collector.GetMeasurementSnapshot();
Assert.Equal(1, measurements.Count);
Assert.Equal(15, measurements[0].Value);
}
// Setup a new service provider. This example creates the collection explicitly but you might leverage
// a host or some other application setup code to do this as well.
private static IServiceProvider CreateServiceProvider()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddMetrics();
serviceCollection.AddSingleton<HatCoMetrics>();
return serviceCollection.BuildServiceProvider();
}
}
每個 MetricCollector 物件都會記錄一個儀器的所有度量。 如果您需要驗證來自多個儀器的測量,請為每個儀器建立一個 MetricCollector。
測試不使用依賴注入
您也可以測試在靜態欄位中使用共用全域 Meter 物件的程式代碼,但請確定這類測試未平行執行。 由於 Meter 物件正在共用,因此某一測試中的 MetricCollector 會監測由其他任何同時執行的測試所建立的量值。
class HatCoMetricsWithGlobalMeter
{
static Meter s_meter = new Meter("HatCo.Store");
static Counter<int> s_hatsSold = s_meter.CreateCounter<int>("hatco.store.hats_sold");
public void HatsSold(int quantity)
{
s_hatsSold.Add(quantity);
}
}
public class MetricTests
{
[Fact]
public void SaleIncrementsHatsSoldCounter()
{
// Arrange
var metrics = new HatCoMetricsWithGlobalMeter();
// Be careful specifying scope=null. This binds the collector to a global Meter and tests
// that use global state should not be configured to run in parallel.
var collector = new MetricCollector<int>(null, "HatCo.Store", "hatco.store.hats_sold");
// Act
metrics.HatsSold(15);
// Assert
var measurements = collector.GetMeasurementSnapshot();
Assert.Equal(1, measurements.Count);
Assert.Equal(15, measurements[0].Value);
}
}