Erstellen von Metriken
Dieser Artikel gilt für: ✔️ .NET Core 6 und neuere Versionen ✔️ .NET Framework 4.6.1 und höher
.NET-Anwendungen können mithilfe der System.Diagnostics.Metrics-APIs instrumentiert werden, um wichtige Metriken nachzuverfolgen. Einige Metriken sind in .NET-Standardbibliotheken enthalten, aber Möglicherweise möchten Sie neue benutzerdefinierte Metriken hinzufügen, die für Ihre Anwendungen und Bibliotheken relevant sind. In diesem Lernprogramm fügen Sie neue Metriken hinzu und verstehen, welche Arten von Metriken verfügbar sind.
Anmerkung
.NET verfügt über einige ältere Metrik-APIs, nämlich EventCounters und System.Diagnostics.PerformanceCounter, die hier nicht behandelt werden. Weitere Informationen zu diesen Alternativen finden Sie unter Vergleichen von Metrik-APIs.
Erstellen einer benutzerdefinierten Metrik
Voraussetzungen: .NET Core 6 SDK oder eine höhere Version
Erstellen Sie eine neue Konsolenanwendung, die auf das System.Diagnostics.DiagnosticSource NuGet-Paket Version 8 oder höher verweist. Anwendungen, die auf .NET 8+ abzielen, enthalten diese Referenz standardmäßig. Aktualisieren Sie dann den Code in Program.cs
, sodass folgendes erfüllt ist:
> 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);
}
}
}
Der System.Diagnostics.Metrics.Meter-Typ ist der Einstiegspunkt für eine Bibliothek zum Erstellen einer benannten Gruppe von Instrumenten. Instrumente zeichnen die numerischen Messungen auf, die zum Berechnen von Metriken erforderlich sind. Hier haben wir CreateCounter genutzt, um ein Zähler-Instrument mit der Bezeichnung „hatco.store.hats_sold“ zu erstellen. Während jeder angenommenen Transaktion ruft der Code Add auf, um die Messung der verkauften Mützen aufzuzeichnen, in diesem Fall 4. Das Instrument "hatco.store.hats_sold" definiert implizit einige Metriken, die aus diesen Messungen berechnet werden können, wie die Gesamtzahl der verkauften Hüte oder verkaufte Hüte pro Sekunde. Letztendlich liegt es an den Metrik-Sammlungstools, zu bestimmen, welche Metriken berechnet werden sollen und wie diese Berechnungen ausgeführt werden sollen, aber jedes Instrument verfügt über einige Standardkonventionen, die die Absicht des Entwicklers klar darstellen. Bei Zählerinstrumenten ist es üblich, dass Erfassungswerkzeuge die Gesamtanzahl und/oder die Rate anzeigen, mit der die Anzahl zunimmt.
Der generische Parameter int
für Counter<int>
und CreateCounter<int>(...)
definiert, dass dieser Zähler Werte bis zu Int32.MaxValuespeichern kann. Sie können abhängig von der Größe der Daten, die Sie speichern müssen, und davon, ob Bruchwerte erforderlich sind, byte
, short
, int
, long
, float
, double
oder decimal
verwenden.
Führen Sie die App aus und lassen Sie sie für den Moment laufen. Als Nächstes betrachten wir die Metriken.
> dotnet run
Press any key to exit
Bewährte Methoden
Erstellen Sie für Code, der nicht für die Verwendung in einem DI-Container (Dependency Injection, Abhängigkeitseinschleusung) vorgesehen ist, die Verbrauchseinheit einmal, und speichern Sie sie in einer statischen Variablen. Für die Verwendung in DI-fähigen Bibliotheken gelten statische Variablen als Antimuster, und im folgenden DI-Beispiel wird ein idiomatischerer Ansatz gezeigt. Jede Bibliothek oder Bibliotheksunterkomponente kann (und sollte häufig) eine eigene Meter erstellen. Erwägen Sie, einen neuen Zähler zu erstellen, anstatt einen vorhandenen Zähler wiederzuverwenden, wenn Sie davon ausgehen, dass App-Entwickler es schätzen würden, die Gruppen von Metriken problemlos separat aktivieren und deaktivieren zu können.
Der an den Meter-Konstruktor übergebene Name sollte eindeutig sein, um ihn von anderen Verbrauchseinheiten zu unterscheiden. Es wird empfohlen, OpenTelemetry-Benennungsrichtlinien zu verwenden, die gepunktete hierarchische Namen verwenden. Assemblynamen oder Namespacenamen für instrumentierten Code sind in der Regel eine gute Wahl. Wenn eine Assembly eine Instrumentierung für Code in einer zweiten unabhängigen Assembly hinzufügt, sollte der Name auf der Assembly basieren, die die Verbrauchseinheit definiert, nicht auf der Assembly, deren Code instrumentiert wird.
.NET erzwingt kein Benennungsschema für Instrumente, es wird jedoch empfohlen, OpenTelemetry-Benennungsrichtlinienzu befolgen, die gepunktete hierarchische Kleinbuchstaben und einen Unterstrich ('_') als Trennzeichen zwischen mehreren Wörtern im selben Element verwenden. Nicht alle Metriktools behalten den Namen der Verbrauchseinheit als Teil des endgültigen Metriknamens bei, daher ist es vorteilhaft, einen global eindeutigen Instrumentnamen festzulegen.
Beispielinstrumentnamen:
contoso.ticket_queue.duration
contoso.reserved_tickets
contoso.purchased_tickets
Die APIs zum Erstellen von Instrumenten und zum Aufzeichnen von Messungen sind threadsicher. In .NET-Bibliotheken erfordern die meisten Instanzmethoden eine Synchronisierung, wenn sie von mehreren Threads auf dasselbe Objekt aufgerufen werden, aber in diesem Fall ist das nicht nötig.
Die Instrumenten-APIs zum Aufzeichnen von Messungen (in diesem Beispiel Add) werden in der Regel in <10 ns ausgeführt, wenn keine Daten erfasst werden, oder in zehn bis einigen hundert Nanosekunden, wenn Messungen von einer extrem leistungsstarken Sammlungsbibliothek oder einem extrem leistungsstarken Sammlungstool erfasst werden. Dadurch können diese APIs in den meisten Fällen liberal verwendet werden, achten aber auf Code, der extrem leistungsempfindlich ist.
Zeige die neue Metrik an
Es gibt viele Optionen zum Speichern und Anzeigen von Metriken. In diesem Tutorial wird das Tool dotnet-counters verwendet, das für Ad-hoc-Analysen nützlich ist. Weitere Alternativen finden Sie auch im Tutorial zur Metriksammlung. Wenn das dotnet-counters Tool noch nicht installiert ist, verwenden Sie das SDK, um es zu installieren.
> 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.
Während die Beispiel-App noch ausgeführt wird, verwenden Sie „dotnet-counters“, um den neuen Zähler zu überwachen:
> 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
Wie erwartet, können Sie sehen, dass HatCo Store ständig 4 Hüte pro Sekunde verkauft.
Abrufen einer Verbrauchseinheit über die Abhängigkeitseinschleusung
Im vorherigen Beispiel wurde die Verbrauchseinheit durch Konstruieren mit new
und Zuweisen zu einem statischen Feld abgerufen. Eine derartige Nutzung von statischen Elementen ist bei Verwendung von Abhängigkeitseinschleusung (Dependency Injection, DI) kein guter Ansatz. Erstellen Sie in Code, der DI verwendet ( z. B. ASP.NET Core oder Apps mit generischem Host), das Objekt für die Verbrauchseinheit mithilfe von IMeterFactory. Ab .NET 8 registrieren Hosts IMeterFactory automatisch im Dienstcontainer, oder Sie können den Typ durch Aufrufen von AddMetrics manuell in einem beliebigen IServiceCollection-Element registrieren.
Die Verbrauchseinheitenfactory integriert Metriken in DI, wobei Verbrauchseinheiten in unterschiedlichen Dienstsammlungen voneinander isoliert bleiben, auch wenn sie einen identischen Namen verwenden. Dies ist besonders hilfreich für Tests, sodass mehrere parallel ausgeführte Tests nur Messungen erfassen, die innerhalb desselben Testfalls gemacht wurden.
Um eine Verbrauchseinheit in einem für DI entwickelten Typ abzurufen, fügen Sie dem Konstruktor einen IMeterFactory-Parameter hinzu, und rufen Sie dann Create auf. Dieses Beispiel zeigt die Verwendung von IMeterFactory in einer ASP.NET Core-App.
Definieren Sie einen Typ für die Instrumente:
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);
}
}
Registrieren Sie den Typ beim DI-Container in Program.cs
.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<HatCoMetrics>();
Fügen Sie den Metriktyp ein, und erfassen Sie Werte nach Bedarf. Da der Metriktyp in DI registriert ist, kann er mit MVC-Controllern, minimalen APIs oder einem anderen Typ verwendet werden, der von DI erstellt wird:
app.MapPost("/complete-sale", ([FromBody] SaleModel model, HatCoMetrics metrics) =>
{
// ... business logic such as saving the sale to a database ...
metrics.HatsSold(model.QuantitySold);
});
Bewährte Methoden
- System.Diagnostics.Metrics.Meter implementiert IDisposable, aber IMeterFactory verwaltet automatisch die Lebensdauer aller davon erstellten
Meter
-Objekte und entfernt sie, wenn der DI-Container verworfen wird. Es ist unnötig, zusätzlichen Code hinzuzufügen, umDispose()
fürMeter
aufzurufen, und er hat keine Auswirkung.
Arten von Instrumenten
Bisher haben wir nur ein Counter<T> Instrument demonstriert, aber es gibt weitere Gerätetypen. Instrumente unterscheiden sich auf zwei Arten:
- Standardmetrikberechnungen - Tools, die die Instrumentmessungen sammeln und analysieren, berechnen je nach Instrument unterschiedliche Standardmetriken.
- Speicher mit aggregierten Daten: Für Metriken, die vielseitig nutzbar sind, müssen Daten aus vielen Messungen aggregiert werden. Eine Option ist, dass der Aufrufer einzelne Messungen zu beliebigen Zeiten bereitstellt und das Sammlungstool die Aggregation verwaltet. Alternativ kann der Aufrufer die aggregierten Messungen verwalten und bei Bedarf in einem Rückruf bereitstellen.
Zurzeit verfügbare Instrumentetypen:
Zähler (CreateCounter): Dieses Instrument verfolgt einen Wert nach, der im Laufe der Zeit zunimmt, und der Aufrufer meldet die Inkremente mithilfe von Add. Die meisten Tools berechnen das Gesamtergebnis und die Änderungsrate des Gesamtergebnisses. Für Tools, die nur ein Element zeigen, wird die Änderungsrate empfohlen. Nehmen Sie beispielsweise an, dass der Aufrufer
Add()
einmal pro Sekunde mit den aufeinander folgenden Werten 1, 2, 4, 5, 4, 3 aufruft. Wenn das Sammlungstool alle drei Sekunden aktualisiert wird, beträgt die Summe nach drei Sekunden 1+2+4=7, und die Summe nach sechs Sekunden beträgt 1+2+4+5+4+3=19. Die Änderungsrate ist (Summe_aktuell – Summe_zuvor), sodass das Tool nach drei Sekunden 7-0 = 7 und nach sechs Sekunden 19-7 = 12 meldet.UpDownCounter (CreateUpDownCounter): Dieses Instrument verfolgt einen Wert nach, der sich im Laufe der Zeit erhöhen oder verringern kann. Der Aufrufer meldet die Inkremente und Dekremente mithilfe von Add. Nehmen Sie beispielsweise an, dass der Aufrufer
Add()
einmal pro Sekunde mit den aufeinander folgenden Werten 1, 5, -2, 3, -1, -3 aufruft. Wenn das Sammlungstool alle drei Sekunden aktualisiert wird, beträgt die Summe nach drei Sekunden 1+5-2=4, und die Summe nach sechs Sekunden beträgt 1+5-2+3-1-3=3.ObservableCounter (CreateObservableCounter): Dieses Instrument ähnelt dem Zähler, außer dass der Aufrufer jetzt für die Verwaltung der aggregierten Summe verantwortlich ist. Der Aufrufer stellt einen Rückrufdelegaten bereit, wenn der ObservableCounter erstellt wird, und der Rückruf wird immer dann aufgerufen, wenn Tools die aktuelle Summe überwachen müssen. Wenn beispielsweise ein Sammlungstool alle drei Sekunden aktualisiert wird, wird die Rückruffunktion auch alle drei Sekunden aufgerufen. Für die meisten Tools sind sowohl die Summe als auch die Änderungsrate in der Summe verfügbar. Wenn nur ein Wert angezeigt werden kann, wird die Änderungsrate empfohlen. Wenn der Callback beim ersten Aufruf 0 zurückgibt, beim erneuten Aufruf nach drei Sekunden 7 und nach sechs Sekunden 19, dann meldet das Tool diese Werte unverändert als die Summen. Für die Änderungsrate zeigt das Tool 7-0=7 nach drei Sekunden und 19-7=12 nach sechs Sekunden an.
ObservableUpDownCounter (CreateObservableUpDownCounter): Dieses Instrument ähnelt dem Zähler, außer dass der Aufrufer jetzt für die Verwaltung der aggregierten Summe verantwortlich ist. Der Aufrufer stellt einen Rückrufdelegaten bereit, wenn der ObservableUpDownCounter erstellt wird, und der Rückruf wird immer dann aufgerufen, wenn Tools die aktuelle Summe überwachen müssen. Wenn beispielsweise ein Sammlungstool alle drei Sekunden aktualisiert wird, wird die Rückruffunktion auch alle drei Sekunden aufgerufen. Jeder vom Rückruf zurückgegebene Wert wird im Auflistungstool unverändert als Summe angezeigt.
Gauge (CreateGauge) – Mit diesem Instrument kann der Aufrufer den aktuellen Wert der Metrik mithilfe der Record-Methode festlegen. Der Wert kann jederzeit aktualisiert werden, indem die Methode erneut aufgerufen wird, und ein Werkzeug zur Erfassung von Metriken zeigt den zuletzt festgelegten Wert an.
ObservableGauge (CreateObservableGauge): Mit diesem Instrument kann der Aufrufer einen Rückruf bereitstellen, bei dem der gemessene Wert direkt als Metrik übergeben wird. Jedes Mal, wenn das Erfassungstool aktualisiert wird, wird der Rückruf aufgerufen, und der vom Rückruf zurückgegebene Wert wird im Tool angezeigt.
Histogramm (CreateHistogram): Dieses Instrument verfolgt die Messverteilung nach. Es gibt keine einzige kanonische Möglichkeit, einen Satz von Messungen zu beschreiben. Es wird jedoch empfohlen, als Tools Histogramme oder berechnete Perzentile zu verwenden. Angenommen, der Anrufer hat Record aufgerufen, um diese Messungen während des Aktualisierungsintervalls des Erfassungstools aufzuzeichnen: 1,5,2,3,10,9,7,4,6,8. Ein Sammlungstool meldet möglicherweise, dass die 50., 90. und 95. Perzentile dieser Messungen 5, 9 bzw. 9 sind.
Anmerkung
Ausführliche Informationen zum Festlegen der empfohlenen Bucket-Grenzen beim Erstellen eines Histogramm-Instruments finden Sie unter: Verwenden von Ratschlägen zum Anpassen von Histogramm-Instrumenten.
Bewährte Methoden beim Auswählen eines Instrumenttyps
Verwenden Sie im Hinblick auf Zähler oder andere Werte, die im Laufe der Zeit ausschließlich zunehmen, die Instrumente Zähler oder ObservableCounter. Wählen Sie zwischen Zähler und ObservableCounter, je nachdem, was dem vorhandenen Code einfacher hinzugefügt werden kann: entweder ein API-Aufruf für jeden Inkrementvorgang oder ein Rückruf, der die aktuelle Summe aus einer Variablen liest, die der Code verwaltet. In extrem heißen Codepfaden, bei denen die Leistung wichtig ist und Add mehr als eine Million Aufrufe pro Sekunde pro Thread erstellen würde, bietet die Verwendung von ObservableCounter möglicherweise mehr Möglichkeiten zur Optimierung.
Für Zeitsteuerungen wird in der Regel das Histogramm bevorzugt. Häufig ist es hilfreich, das Ende dieser Verteilungen (90., 95., 99. Perzentil) anstelle von Durchschnittswerten oder der Summen zu verstehen.
Andere häufige Fälle wie Cachetrefferraten oder Größen von Caches, Warteschlangen und Dateien eignen sich in der Regel gut für
UpDownCounter
oderObservableUpDownCounter
. Wählen Sie zwischen ihnen, je nachdem, welche dem vorhandenen Code einfacher hinzugefügt werden können: entweder ein API-Aufruf für jeden Inkrement- und Dekrementvorgang oder ein Rückruf, der den aktuellen Wert aus einer Variablen liest, die der Code verwaltet.
Anmerkung
Wenn Sie eine ältere Version von .NET oder ein DiagnosticSource NuGet-Paket verwenden, das UpDownCounter
und ObservableUpDownCounter
(vor Version 7) nicht unterstützt, ist ObservableGauge
häufig ein guter Ersatz.
Beispiel für verschiedene Gerätetypen
Beenden Sie den zuvor gestarteten Beispielprozess, und ersetzen Sie den Beispielcode in Program.cs
durch:
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);
}
}
}
Führen Sie den neuen Prozess aus, und verwenden Sie dotnet-Counter wie zuvor in einer zweiten Shell, um die Metriken anzuzeigen:
> 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
In diesem Beispiel werden einige zufällig generierte Zahlen verwendet, sodass Ihre Werte etwas variieren. dotnet-counters rendert Histogramm-Instrumente als Statistiken mit drei Perzentilen (50., 95. und 99.), aber andere Tools können die Verteilung anders zusammenfassen oder mehr Konfigurationsoptionen bieten.
Bewährte Methoden
Histogramme speichern in der Regel viel mehr Daten im Arbeitsspeicher als andere Metriktypen. Die genaue Speicherauslastung wird jedoch durch das verwendete Sammlungstool bestimmt. Wenn Sie eine große Anzahl (>100) von Histogrammmetriken definieren, müssen Sie Benutzer möglicherweise darauf hinweisen, dass sie diese nicht alle gleichzeitig aktivieren. Zudem sollten Benutzer ihre Tools so konfigurieren, dass durch Verringern der Genauigkeit Arbeitsspeicher gespart wird. Einige Sammlungstools haben möglicherweise harte Grenzwerte für die Anzahl gleichzeitiger Histogramme, die sie überwachen, um eine übermäßige Speichernutzung zu verhindern.
Rückrufe für alle überwachbaren Instrumente werden nacheinander aufgerufen, sodass lange dauernde Rückrufe die Erfassung aller Metriken verzögern oder verhindern können. Bevorzugen Sie das schnelle Lesen eines zwischengespeicherten Werts, kein Zurückgeben von Messungen oder auch das Auslösen einer Ausnahme gegenüber der Ausführung eines potenziell lang andauernden oder blockierenden Vorgangs.
Die Callbacks "ObservableCounter", "ObservableUpDownCounter" und "ObservableGauge" treten in einem Thread auf, der normalerweise nicht mit dem Code synchronisiert ist, der die Werte aktualisiert. Es liegt in Ihrer Verantwortung, entweder den Speicherzugriff zu synchronisieren oder die inkonsistenten Werte zu akzeptieren, die sich aus der Verwendung nicht synchronisierter Zugriff ergeben können. Häufige Ansätze für die Synchronisierung des Zugriffs sind die Verwendung einer Sperre oder der Aufruf von Volatile.Read und Volatile.Write.
Die funktionen CreateObservableGauge und CreateObservableCounter geben ein Instrumentobjekt zurück, aber in den meisten Fällen müssen Sie es nicht in einer Variablen speichern, da keine weitere Interaktion mit dem Objekt erforderlich ist. Die Zuweisung zu einer statischen Variable wie bei den anderen Instrumenten ist zulässig, aber fehleranfällig, da die statische C#-Initialisierung verzögert ist und in der Regel nie auf die Variable verwiesen wird. Hier ist ein Beispiel für das Problem:
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(); } }
Beschreibungen und Einheiten
Instrumente können optionale Beschreibungen und Einheiten angeben. Diese Werte sind für alle Metrikberechnungen undurchsichtig, können aber in der Benutzeroberfläche des Sammlungstools angezeigt werden, damit Techniker verstehen können, wie die Daten interpretiert werden. Beenden Sie den zuvor gestarteten Beispielprozess, und ersetzen Sie den Beispielcode in Program.cs
durch:
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);
}
}
}
Führen Sie den neuen Prozess aus, und verwenden Sie dotnet-Counter wie zuvor in einer zweiten Shell, um die Metriken anzuzeigen:
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 verwendet derzeit nicht den Beschreibungstext in der Benutzeroberfläche, zeigt jedoch die Einheit an, wenn sie angegeben ist. In diesem Fall sehen Sie, dass „{hats}“ den generischen Begriff „Zählwert“ ersetzt hat, der in vorherigen Beschreibungen sichtbar ist.
Bewährte Methoden
.NET-APIs ermöglichen die Verwendung einer beliebigen Zeichenfolge als Einheit, es wird jedoch empfohlen, UCUM-, einen internationalen Standard für Einheitennamen, zu verwenden. Die geschweiften Klammern um „{hats}“ sind Teil des UCUM-Standards und weisen darauf hin, dass es sich um eine beschreibende Anmerkung und nicht um einen Einheitennamen mit einer standardisierten Bedeutung wie Sekunden oder Bytes handelt.
Die im Konstruktor angegebene Einheit sollte die Einheiten beschreiben, die für eine einzelne Messung geeignet sind. Dies unterscheidet sich manchmal von den Einheiten in der endgültigen Metrik. In diesem Beispiel ist jede Messung eine bestimmte Anzahl von Mützen, daher ist „{hats}“ die geeignete Einheit, die an den Konstruktor zu übergeben ist. Das Sammlungstool könnte die Änderungsrate berechnet und allein abgeleitet haben, dass die entsprechende Einheit für die berechnete Satzmetrik {hats}/s ist.
Bei der Aufzeichnung von Zeitmessungen bevorzugen Sie Einheiten von Sekunden, die als Gleitkomma- oder Double-Wert aufgezeichnet werden.
Mehrdimensionale Metriken
Messungen können auch Schlüsselwertpaaren zugeordnet werden, die als Tags bezeichnet werden, mit denen Daten für die Analyse kategorisiert werden können. Beispielsweise möchte HatCo nicht nur die Anzahl der verkauften Hüte aufzeichnen, sondern auch, welche Größe und Farbe sie hatten. Bei der späteren Analyse der Daten können die HatCo-Ingenieure die Summen nach Größe, Farbe oder einer beliebigen Kombination aus beidem aufgliedern.
Indikator- und Histogrammtags können in Überladungen von Add und Recordangegeben werden, die ein oder mehrere KeyValuePair
Argumente verwenden. Zum Beispiel:
s_hatsSold.Add(2,
new KeyValuePair<string, object?>("product.color", "red"),
new KeyValuePair<string, object?>("product.size", 12));
Ersetzen Sie den Code von Program.cs
und führen Sie die App und dotnet-counters wie zuvor erneut aus:
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 zeigt nun eine grundlegende Kategorisierung:
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
Für ObservableCounter und ObservableGauge können markierte Messungen im Rückruf bereitgestellt werden, der an den Konstruktor übergeben wird:
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")),
};
}
}
Wenn sie wie zuvor mit Dotnet-Zählern ausgeführt wird, lautet das Ergebnis:
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
Bewährte Methoden
Obwohl die API die Verwendung eines Objekts als Tagwert zulässt, werden numerische Typen und Zeichenfolgen von Sammlungstools erwartet. Andere Typen können von einem bestimmten Sammlungstool unterstützt werden.
Es wird empfohlen, bei Tagnamen die OpenTelemetry-Benennungsrichtlinien zu befolgen, bei denen gepunktete hierarchische Namen in Kleinbuchstaben mit Unterstrichen (_) verwendet werden, um mehrere Wörter im selben Element zu trennen. Wenn Tagnamen in unterschiedlichen Metriken oder anderen Telemetriedatensätzen wiederverwendet werden, sollten sie überall dort, wo sie verwendet werden, dieselbe Bedeutung und einen Satz von rechtlichen Werten haben.
Beispiele für Tagnamen:
customer.country
store.payment_method
store.purchase_result
Beachten Sie, dass in der Praxis sehr große oder ungebundene Kombinationen von Tagwerten aufgezeichnet werden können. Dies sollten Sie vermeiden. Obwohl die .NET-API-Implementierung dies verarbeiten kann, weisen Sammlungstools wahrscheinlich Speicher für Metrikdaten zu, die jeder Tagkombination zugeordnet sind. Dieser kann sehr groß werden. So ist es beispielsweise in Ordnung, wenn HatCo 10 verschiedene Mützenfarben und 25 Mützengrößen hat, denn dann sind insgesamt bis zu 10*25=250 Verkäufe nachzuverfolgen. Wenn HatCo jedoch ein drittes Tag, z. B. eine Kunden-ID für den Verkauf, hinzugefügt hat und die Mützen an 100 Millionen Kunden weltweit verkauft werden, werden jetzt wahrscheinlich Milliarden verschiedener Tagkombinationen aufgezeichnet. Die meisten Metrik-Erfassungstools verwerfen entweder Daten, um innerhalb der technischen Grenzwerte zu bleiben, oder es können hohe finanzielle Kosten entstehen, um die Datenspeicherung und -verarbeitung abzudecken. Die Implementierung jedes Erfassungswerkzeugs bestimmt seine Grenzen, aber wahrscheinlich stellen weniger als 1000 Kombinationen für ein Instrument eine sichere Obergrenze dar. Alles, was über 1000 Kombinationen hinausgeht, erfordert, dass das Sammlungstool Filter anwendet oder für den Betrieb im großen Maßstab konzipiert wird. Histogrammimplementierungen verwenden tendenziell viel mehr Arbeitsspeicher als andere Metriken, sodass sichere Grenzwerte 10-100 Mal niedriger sein könnten. Wenn Sie eine große Anzahl von eindeutigen Tag-Kombinationen erwarten, sind Protokolle, Transaktionsdatenbanken oder Big Data-Verarbeitungssysteme möglicherweise geeignetere Lösungen für den Betrieb im erforderlichen Maßstab.
Bei Instrumenten, die eine sehr große Anzahl von Tagkombinationen aufweisen, empfiehlt es sich, einen kleineren Speichertyp zu verwenden, um den Speicherbedarf zu reduzieren. Beispielsweise belegt das Speichern von
short
für eineCounter<short>
nur 2 Bytes pro Tagkombination, während einedouble
fürCounter<double>
8 Bytes pro Tagkombination belegt.Sammlungstools sollten für Code optimiert werden, der für jeden Aufruf die gleiche Reihe von Tagnamen in der gleichen Reihenfolge angibt, um Messungen auf demselben Instrument aufzuzeichnen. Für Hochleistungscode, der häufig Add und Record aufrufen muss, sollten Sie für jeden Aufruf die gleiche Sequenz von Tagnamen verwenden.
Die .NET-API ist so optimiert, dass sie für Aufrufe von Add und Record ohne Zuordnung ist, bei denen drei oder weniger Tags einzeln angegeben sind. Verwenden Sie TagList, um Zuordnungen mit einer größeren Anzahl von Tags zu vermeiden. Im Allgemeinen steigt der Leistungsaufwand dieser Aufrufe, wenn mehr Tags verwendet werden.
Anmerkung
OpenTelemetry bezieht sich auf Tags als "Attribute". Dies sind zwei verschiedene Namen für die gleiche Funktionalität.
Verwenden von Ratschlägen zum Anpassen von Histogramm-Instrumenten
Bei der Verwendung von Histogrammen liegt es in der Verantwortung des Tools oder der Bibliothek, das die Daten sammelt, um zu entscheiden, wie am besten die Verteilung der aufgezeichneten Werte dargestellt werden soll. Eine allgemeine Strategie (und der Standardmodus bei Verwendung von OpenTelemetry) besteht darin, den Bereich möglicher Werte in Unterbereiche aufzuteilen, die als Buckets bezeichnet werden, und berichten, wie viele aufgezeichnete Werte in jedem Bucket vorhanden waren. Beispielsweise kann ein Tool Zahlen in drei Buckets unterteilen, die kleiner als 1, die zwischen 1 und 10 und die größer als 10 sind. Wenn Ihre App die Werte 0,5; 6; 0,1; 12 aufgezeichnet hat, gibt es zwei Datenpunkte im ersten Intervall, einen im zweiten und einen im dritten.
Das Tool oder die Bibliothek, das die Histogrammdaten sammelt, ist für die Definition der verwendeten Buckets verantwortlich. Die Standard-Bucketkonfiguration bei Verwendung von OpenTelemetry lautet: [ 0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000 ]
.
Die Standardwerte führen möglicherweise nicht zu der besten Granularität für jedes Histogramm. Zum Beispiel würden Anforderungsdauern von unter einer Sekunde alle in den Bucket 0
fallen.
Das Tool oder die Bibliothek, das die Histogrammdaten sammelt, kann Mechanismen bieten, mit denen Benutzer die Bucketkonfiguration anpassen können. Beispielsweise definiert OpenTelemetry eine Ansichts-API. Dies erfordert jedoch eine Aktion des Endbenutzers und macht es zur Verantwortung des Benutzers, die Datenverteilung gut genug zu verstehen, um die richtigen Buckets auszuwählen.
Um die Benutzererfahrung zu verbessern, hat die 9.0.0
Version des System.Diagnostics.DiagnosticSource
-Pakets die API (InstrumentAdvice<T>) eingeführt.
Die InstrumentAdvice
-API kann von Instrumentierungsentwicklern verwendet werden, um den Satz empfohlener Standard-Bucketgrenzen für ein bestimmtes Histogramm festzulegen. Das Tool oder die Bibliothek, das die Histogrammdaten sammelt, kann dann entscheiden, diese Werte beim Konfigurieren der Aggregation zu verwenden, was zu einer nahtloseren Onboarding-Erfahrung für Benutzer führt. Dies wird im OpenTelemetry .NET SDK ab Version 1.10.0unterstützt.
Wichtig
Im Allgemeinen führen mehr Buckets zu präziseren Daten für ein bestimmtes Histogramm, aber jeder Bucket erfordert Speicher, um die aggregierten Details zu speichern, und es gibt CPU-Kosten, um den richtigen Bucket bei der Verarbeitung einer Messung zu finden. Es ist wichtig, die Kompromisse zwischen Genauigkeit und CPU/Arbeitsspeicherverbrauch zu verstehen, wenn Sie die Anzahl der Buckets auswählen, die über die InstrumentAdvice
-API empfohlen werden sollen.
Der folgende Code zeigt ein Beispiel mit der InstrumentAdvice
-API zum Festlegen empfohlener Standard-Buckets.
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);
}
}
}
Zusatzinformation
Weitere Informationen zu expliziten Bucket-Histogrammen in OpenTelemetry finden Sie unter:
Testen benutzerdefinierter Metriken
Es ist möglich, alle benutzerdefinierten Metriken zu testen, die Sie mit MetricCollector<T>hinzufügen. Dieser Typ macht es einfach, die Messungen aus bestimmten Instrumenten aufzuzeichnen und die Werte zu bestätigen.
Testen mit Abhängigkeitseinschleusung
Der folgende Code zeigt einen Beispieltestfall für Codekomponenten, die Abhängigkeitsinjektion und IMeterFactory verwenden.
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();
}
}
Jedes MetricCollector -Objekt zeichnet alle Maße für ein Instrument auf. Wenn Sie Messungen aus mehreren Instrumenten überprüfen müssen, erstellen Sie jeweils ein MetricCollector.If you need to verify measurements from multiple instruments, create one MetricCollector for each one.
Testen ohne Abhängigkeitseinschleusung
Es ist auch möglich, Code zu testen, der ein freigegebenes globales Meter-Objekt in einem statischen Feld verwendet, aber stellen Sie sicher, dass solche Tests nicht parallel ausgeführt werden. Da das Meter-Objekt freigegeben wird, berücksichtigt MetricCollector in einem Test die Messungen, die von anderen parallel ausgeführten Tests generiert werden.
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);
}
}