Skapa mått
Den här artikeln gäller för: ✔️ .NET Core 6 och senare versioner ✔️ .NET Framework 4.6.1 och senare versioner
.NET-program kan instrumenteras med hjälp av System.Diagnostics.Metrics API:er för att spåra viktiga mått. Vissa mått ingår i .NET-standardbibliotek, men du kanske vill lägga till nya anpassade mått som är relevanta för dina program och bibliotek. I den här självstudien lägger du till nya mått och förstår vilka typer av mått som är tillgängliga.
Not
.NET har vissa äldre mått-API:er, nämligen EventCounters och System.Diagnostics.PerformanceCounter, som inte beskrivs här. Mer information om dessa alternativ finns i Jämför mått-API:er.
Skapa ett anpassat mått
Krav: .NET Core 6 SDK eller en senare version
Skapa ett nytt konsolprogram som refererar till nuget-paketet System.Diagnostics.DiagnosticSource version 8 eller senare. Program som riktar in sig på .NET 8+ innehåller den här referensen som standard. Uppdatera sedan koden i Program.cs
så att den matchar:
> 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);
}
}
}
Typen System.Diagnostics.Metrics.Meter är infarten för ett bibliotek att skapa en namngiven grupp av instrument. Instrument registrerar de numeriska mått som behövs för att beräkna mått. Här använde vi CreateCounter för att skapa ett räknarinstrument med namnet "hatco.store.hats_sold". Under varje simulerad transaktion anropar koden Add för att registrera antalet hattar som såldes, 4 i det här fallet. Instrumentet "hatco.store.hats_sold" definierar implicit vissa mått som kan beräknas från dessa mätningar, till exempel det totala antalet sålda hattar eller sålda hattar per sekund. I slutändan är det upp till verktygen för måttinsamling att avgöra vilka mått som ska beräknas och hur dessa beräkningar ska utföras, men varje instrument har några standardkonventioner som förmedlar utvecklarens avsikt. För räknarinstrument är konventionen att samlingsverktygen visar det totala antalet och/eller den hastighet med vilken antalet ökar.
Den allmänna parametern int
på Counter<int>
och CreateCounter<int>(...)
definierar att den här räknaren måste kunna lagra värden upp till Int32.MaxValue. Du kan använda någon av byte
, short
, int
, long
, float
, double
eller decimal
beroende på storleken på data som du behöver lagra och om bråkvärden behövs.
Kör appen och låt den vara igång för tillfället. Vi kommer att titta på metrikerna härnäst.
> dotnet run
Press any key to exit
Metodtips
För kod som inte är utformad för användning i en DI-container (Dependency Injection) skapar du mätaren en gång och lagrar den i en statisk variabel. För användning i DI-medvetna bibliotek betraktas statiska variabler som ett antimönster och DI-exempel nedan visar en mer idiomatisk metod. Varje biblioteks- eller biblioteksunderkomponent kan (och bör ofta) skapa sin egen Meter. Överväg att skapa en ny mätare i stället för att återanvända en befintlig om du förväntar dig att apputvecklare enkelt kan aktivera och inaktivera grupper av mått separat.
Namnet som skickas till konstruktorn för Meter ska vara unikt för att skilja det från andra mätare. Vi rekommenderar OpenTelemetry-riktlinjer för namngivning, vilka använder punktseparerade hierarkiska namn. Sammansättningsnamn eller namnområdesnamn för kod som instrumenteras är vanligtvis ett bra val. Om en sammansättning lägger till instrumentation för kod i en andra oberoende sammansättning bör namnet baseras på den sammansättning som definierar mätaren, inte den sammansättning vars kod instrumenteras.
.NET tillämpar inget namngivningsschema för Instrument, men vi rekommenderar att du följer riktlinjerna för OpenTelemetry-namngivning, som använder gemener med streckade hierarkiska namn och ett understreck ('_') som avgränsare mellan flera ord i samma element. Inte alla måttverktyg bevarar mätarnamnet som en del av det slutliga måttnamnet, så det är fördelaktigt att göra instrumentnamnet globalt unikt på egen hand.
Exempel på instrumentnamn:
contoso.ticket_queue.duration
contoso.reserved_tickets
contoso.purchased_tickets
API:erna för att skapa instrument och registrera mätningar är trådsäkra. I .NET-bibliotek kräver de flesta instansmetoder synkronisering när de anropas på samma objekt från flera trådar, men det behövs inte i det här fallet.
Instrument-API:er för att registrera mätningar (Add i det här exemplet) körs vanligtvis i <10 ns när inga data samlas in, eller tiotals till hundratals nanosekunder när mätningar samlas in av ett bibliotek eller verktyg för insamling med höga prestanda. Detta gör att dessa API:er kan användas liberalt i de flesta fall, men ta hand om kod som är extremt prestandakänslig.
Visa det nya måttet
Det finns många alternativ för att lagra och visa mått. I den här handledningen används verktyget dotnet-counters, som är användbart för ad hoc-analys. Du kan också se handledningen för metrikinsamling för de andra alternativen. Om verktyget dotnet-counters inte redan är installerat använder du SDK:et för att installera det:
> 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.
Medan exempelappen fortfarande körs använder du dotnet-counters för att övervaka den nya räknaren:
> 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
Som förväntat kan du se att HatCo-butiken stadigt säljer 4 hattar varje sekund.
Hämta en mätare via beroendeinmatning
I föregående exempel erhölls mätaren genom att konstruera den med new
och tilldela den till ett statiskt fält. Att använda statiska data på det här sättet är inte en bra metod när du använder beroendeinmatning (DI). Skapa mätarobjektet med IMeterFactoryi kod som använder DI, till exempel ASP.NET Core eller appar med Generic Host. Från och med .NET 8 registrerar värdar automatiskt IMeterFactory i tjänstcontainern eller så kan du manuellt registrera typen i alla IServiceCollection genom att anropa AddMetrics.
Mätarfabriken integrerar mått med DI och håller mätare i olika tjänstsamlingar isolerade från varandra även om de använder ett identiskt namn. Detta är särskilt användbart för testning så att flera tester som körs parallellt endast observerar mätningar som producerats inifrån samma testfall.
Om du vill hämta en mätare i en typ som är utformad för DI lägger du till en IMeterFactory-parameter i konstruktorn och anropar sedan Create. Det här exemplet visar hur du använder IMeterFactory i en ASP.NET Core-app.
Definiera en typ för att hålla instrumenten.
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);
}
}
Registrera typen i DI-container i Program.cs
.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<HatCoMetrics>();
Mata in måttyp och registrera värden där det behövs. Eftersom måtttypen är registrerad i DI kan den användas med MVC-styrenheter, minimala API:er eller någon annan typ som skapas av DI:
app.MapPost("/complete-sale", ([FromBody] SaleModel model, HatCoMetrics metrics) =>
{
// ... business logic such as saving the sale to a database ...
metrics.HatsSold(model.QuantitySold);
});
Metodtips
-
System.Diagnostics.Metrics.Meter implementerar IDisposable, men IMeterFactory hanterar automatiskt livslängden för alla
Meter
-objekt som skapas och avyttrar dem när DI-containern avyttras. Det är onödigt att lägga till extra kod för att anropaDispose()
påMeter
, och det kommer inte att ha någon effekt.
Typer av instrument
Hittills har vi bara visat ett Counter<T> instrument, men det finns fler instrumenttyper tillgängliga. Instrumenten skiljer sig åt på två sätt:
- Standardmåttberäkningar – Verktyg som samlar in och analyserar instrumentmätningarna beräknar olika standardmått beroende på instrument.
- Storage of aggregated data – De flesta användbara mätvärden behöver data som aggregeras från många mätningar. Ett alternativ är att anroparen tillhandahåller enskilda mått vid godtyckliga tidpunkter och samlingsverktyget hanterar aggregeringen. Alternativt kan anroparen hantera aggregerade mått och tillhandahålla dem på begäran i ett återanrop.
Typer av instrument som för närvarande är tillgängliga:
Counter (CreateCounter) – Det här instrumentet spårar ett värde som ökar med tiden och anroparen rapporterar ökningarna med hjälp av Add. De flesta verktyg beräknar totalsumman och ändringshastigheten i totalsumman. För verktyg som endast visar en sak, rekommenderas det att använda ändringshastigheten. Anta till exempel att anroparen anropar
Add()
en gång i sekunden med efterföljande värden 1, 2, 4, 5, 4, 3. Om samlingsverktyget uppdateras var tredje sekund är summan efter tre sekunder 1+2+4=7 och summan efter sex sekunder är 1+2+4+5+4+3=19. Ändringshastigheten är (current_total – previous_total), så vid tre sekunder rapporterar verktyget 7-0=7, och efter sex sekunder rapporterar det 19-7=12.UpDownCounter (CreateUpDownCounter) – Det här instrumentet spårar ett värde som kan öka eller minska med tiden. Anroparen rapporterar ökningar och minskningar med hjälp av Add. Anta till exempel att anroparen anropar
Add()
en gång i sekunden med efterföljande värden 1, 5, -2, 3, -1, -3. Om samlingsverktyget uppdateras var tredje sekund är summan efter tre sekunder 1+5-2=4 och summan efter sex sekunder är 1+5-2+3-1-3=3.ObservableCounter (CreateObservableCounter) – Det här instrumentet liknar Counter förutom att anroparen nu ansvarar för att upprätthålla den aggregerade summan. Anroparen tillhandahåller ett återanropsdelegat när ObservableCounter skapas och återanropet anropas när verktygen behöver observera den aktuella summan. Om ett samlingsverktyg till exempel uppdateras var tredje sekund anropas även återanropsfunktionen var tredje sekund. De flesta verktyg kommer att ha både total och hastighet för förändring i det totala antalet tillgängliga. Om bara en kan visas rekommenderas ändringshastighet. Om återanropet returnerar 0 vid det första anropet, 7 när det anropas igen efter tre sekunder och 19 när det anropas efter sex sekunder, rapporterar verktyget dessa värden oförändrade som summor. För ändringshastighet visar verktyget 7-0=7 efter tre sekunder och 19-7=12 efter sex sekunder.
ObservableUpDownCounter (CreateObservableUpDownCounter) – Det här instrumentet liknar UpDownCounter förutom att anroparen nu ansvarar för att underhålla den aggregerade summan. Anroparen tillhandahåller ett återanropsdelegat när ObservableUpDownCounter skapas och återanropet anropas när verktygen behöver observera den aktuella summan. Om ett samlingsverktyg till exempel uppdateras var tredje sekund anropas även återanropsfunktionen var tredje sekund. Det värde som returneras av återanropet visas i samlingsverktyget oförändrat som totalsumma.
Gauge (CreateGauge) – Med det här instrumentet kan anroparen ange måttets aktuella värde med hjälp av metoden Record. Värdet kan uppdateras när som helst genom att anropa metoden igen och ett måttinsamlingsverktyg visar det värde som senast angavs.
ObservableGauge (CreateObservableGauge) – Med det här instrumentet kan anroparen ange ett återanrop där det uppmätta värdet skickas direkt som mått. Varje gång samlingsverktyget uppdateras anropas återanropet och det värde som returneras av återanropet visas i verktyget.
Histogram (CreateHistogram) – Det här instrumentet spårar fördelningen av mått. Det finns inte ett enda kanoniskt sätt att beskriva en uppsättning mått, men verktyg rekommenderas att använda histogram eller beräknade percentiler. Anta till exempel att anroparen anropade Record för att registrera dessa mått under samlingsverktygets uppdateringsintervall: 1,5,2,3,10,9,7,4,6,8. Ett samlingsverktyg kan rapportera att de 50, 90 och 95:e percentilerna av dessa mått är 5, 9 respektive 9.
Not
Mer information om hur du anger de rekommenderade bucketgränserna när du skapar ett Histogram-instrument finns i: Använda råd för att anpassa Histograminstrument.
Metodtips vid val av instrumenttyp
För att räkna saker, eller något annat värde som bara ökar över tid, använder du Counter eller ObservableCounter. Välj mellan Räknare och ObservableCounter beroende på vilket som är enklare att lägga till i den befintliga koden: antingen ett API-anrop för varje inkrementsåtgärd eller ett återanrop som läser den aktuella summan från en variabel som koden upprätthåller. I extremt heta kodvägar där prestanda är av stor betydelse och användning av Add skulle generera mer än en miljon anrop per sekund per tråd, kan användning av en ObserverbarRäknare erbjuda fler optimeringsmöjligheter.
Histogram är vanligtvis att föredra för tidshantering. Ofta är det användbart att förstå ytterpunkterna av dessa fördelningar (90:e, 95:e, 99:e percentilen) snarare än medelvärden eller summor.
Andra vanliga fall, till exempel cacheträffar eller storlekar på cacheminnen, köer och filer, passar vanligtvis bra för
UpDownCounter
ellerObservableUpDownCounter
. Välj mellan dem beroende på vilket som är enklare att lägga till i den befintliga koden: antingen ett API-anrop för varje inkrements- och minskningsåtgärd eller ett återanrop som läser det aktuella värdet från en variabel som koden upprätthåller.
Obs
Om du använder en äldre version av .NET eller ett DiagnosticSource NuGet-paket som inte stöder UpDownCounter
och ObservableUpDownCounter
(före version 7) är ObservableGauge
ofta en bra ersättning.
Exempel på olika instrumenttyper
Stoppa exempelprocessen som startades tidigare och ersätt exempelkoden i Program.cs
med:
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);
}
}
}
Kör den nya processen och använd dotnet-counters som tidigare i ett andra gränssnitt för att visa måtten:
> 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
I det här exemplet används några slumpmässigt genererade tal så att dina värden varierar lite. Dotnet-räknare återger Histograminstrument som tre percentilstatistik (50: e, 95: e och 99: e) men andra verktyg kan sammanfatta fördelningen annorlunda eller erbjuda fler konfigurationsalternativ.
Metodtips
Histogram tenderar att lagra mycket mer data i minnet än andra måtttyper. Den exakta minnesanvändningen bestäms dock av samlingsverktyget som används. Om du definierar ett stort antal (>100) histogrammått kan du behöva ge användarna vägledning om att inte aktivera alla samtidigt, eller att konfigurera sina verktyg för att spara minne genom att minska precisionen. Vissa samlingsverktyg kan ha hårda gränser för antalet samtidiga Histogram som de övervakar för att förhindra överdriven minnesanvändning.
Återanrop för alla observerbara instrument anropas i följd, så alla återanrop som tar lång tid kan fördröja eller förhindra att alla mätvärden samlas in. Föredra att snabbt läsa ett cachelagrat värde, returnera inga mätvärden eller kasta ett undantag istället för att utföra någon potentiellt långvarig eller blockerande operation.
Återanropen ObservableCounter, ObservableUpDownCounter och ObservableGauge sker i en tråd som vanligtvis inte synkroniseras med koden som uppdaterar värdena. Det är ditt ansvar att antingen synkronisera minnesåtkomst eller acceptera de inkonsekventa värden som kan uppstå när du använder osynkroniserad åtkomst. Vanliga metoder för att synkronisera åtkomst är att använda ett lås eller ett anrop Volatile.Read och Volatile.Write.
Funktionerna CreateObservableGauge och CreateObservableCounter returnerar ett instrumentobjekt, men i de flesta fall behöver du inte spara det i en variabel eftersom det inte behövs någon ytterligare interaktion med objektet. Att tilldela den till en statisk variabel som vi gjorde för de andra instrumenten är lagligt men felbenäget, eftersom C#-statisk initiering är lat och variabeln vanligtvis aldrig refereras till. Här är ett exempel på problemet:
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(); } }
Beskrivningar och enheter
Instrument kan ange valfria beskrivningar och enheter. Dessa värden är ogenomskinliga för alla måttberäkningar men kan visas i samlingsverktygsgränssnittet för att hjälpa tekniker att förstå hur data tolkas. Stoppa den exempelprocess som du startade tidigare och ersätt exempelkoden i Program.cs
med:
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);
}
}
}
Kör den nya processen och använd dotnet-counters som tidigare i ett andra gränssnitt för att visa måtten:
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 använder för närvarande inte beskrivningstexten i användargränssnittet, men den visar enheten när den tillhandahålls. I det här fallet ser du att {hats} har ersatt den generiska termen "Count" som visas i tidigare beskrivningar.
Metodtips
.NET-API:er tillåter att alla strängar används som enhet, men vi rekommenderar att du använder UCUM-, en internationell standard för enhetsnamn. Klammerparenteserna runt {hats} är en del av UCUM-standarden, vilket indikerar att det är en beskrivande anteckning snarare än ett enhetsnamn med en standardiserad innebörd som sekunder eller byte.
Den enhet som anges i konstruktorn ska beskriva de enheter som är lämpliga för en enskild mätning. Detta skiljer sig ibland från enheterna för det slutliga rapporterade måttet. I det här exemplet är varje mätning ett antal hattar, så "{hats}" är lämplig enhet att ange i konstruktorn. Samlingsverktyget kunde ha beräknat ändringshastigheten och härlett på egen hand att lämplig enhet för det beräknade hastighetsmåttet är {hats}/sek.
När du registrerar tidsmätningar föredrar du enheter med sekunder som registreras som en flyttal eller ett dubbelt värde.
Flerdimensionella mått
Mått kan också associeras med nyckel/värde-par som kallas taggar som gör att data kan kategoriseras för analys. HatCo kanske till exempel vill registrera inte bara antalet hattar som såldes, utan också vilken storlek och färg de var. När du analyserar data senare kan HatCo-tekniker dela upp summorna efter storlek, färg eller valfri kombination av båda.
Räknar- och histogramtaggar kan anges i överlagringar av Add och Record som tar ett eller flera KeyValuePair
argument. Till exempel:
s_hatsSold.Add(2,
new KeyValuePair<string, object?>("product.color", "red"),
new KeyValuePair<string, object?>("product.size", 12));
Ersätt koden för Program.cs
och kör appen igen och dotnet-counters som tidigare:
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-räknare visar nu en grundläggande kategorisering:
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 och ObservableGauge kan taggade mått anges i återanropet som skickas till konstruktorn:
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")),
};
}
}
När du kör med dotnet-counters som tidigare blir resultatet:
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
Metodtips
Även om API:et tillåter att alla objekt används som taggvärde, förväntas numeriska typer och strängar av samlingsverktyg. Andra typer kanske eller kanske inte stöds av ett visst samlingsverktyg.
Vi rekommenderar att taggnamn följer riktlinjerna för OpenTelemetry-namngivning, som använder gemener med punktseparerade hierarkinamn och understreck för att separera flera ord i samma element. Om taggnamn återanvänds i olika mått eller andra telemetriposter bör de ha samma innebörd och uppsättning juridiska värden överallt där de används.
Exempeltaggnamn:
customer.country
store.payment_method
store.purchase_result
Var försiktig med att ha mycket stora eller obundna kombinationer av taggvärden som registreras i praktiken. Även om .NET API-implementeringen kan hantera den allokerar samlingsverktyg sannolikt lagring för måttdata som är associerade med varje taggkombination och detta kan bli mycket stort. Det går till exempel bra om HatCo har 10 olika hattfärger och 25 hattstorlekar för upp till 10*25=250 försäljningssummor att spåra. Men om HatCo lade till en tredje tagg som är ett CustomerID för försäljningen och de säljer till 100 miljoner kunder över hela världen, kommer det nu sannolikt att finnas miljarder olika taggkombinationer som registreras. De flesta verktyg för insamling av mått kommer antingen att släppa data för att hålla sig inom tekniska gränser eller så kan det finnas stora ekonomiska kostnader för att täcka datalagring och bearbetning. Implementeringen av varje samlingsverktyg fastställer dess gränser, men sannolikt är mindre än 1 000 kombinationer för ett instrument säkra. Allt över 1 000 kombinationer kräver att samlingsverktyget tillämpar filtrering eller konstrueras för att fungera i hög skala. Histogramimplementeringar tenderar att använda mycket mer minne än andra mått, så säkra gränser kan vara 10–100 gånger lägre. Om du förväntar dig ett stort antal unika taggkombinationer kan loggar, transaktionsdatabaser eller stordatabehandlingssystem vara lämpligare lösningar för att fungera i den skala som behövs.
För instrument som har ett mycket stort antal taggkombinationer föredrar du att använda en mindre lagringstyp för att minska minneskostnaderna. Om du till exempel lagrar
short
för enCounter<short>
upptar endast 2 byte per taggkombination, medan endouble
förCounter<double>
upptar 8 byte per taggkombination.Samlingsverktyg uppmuntras att optimera för kod som anger samma uppsättning taggnamn i samma ordning för varje anrop för att registrera mått på samma instrument. För kod med höga prestanda som måste anropa Add och Record ofta föredrar du att använda samma sekvens med taggnamn för varje anrop.
.NET-API:et är optimerat för att vara allokeringsfritt för Add och Record anrop med tre eller färre taggar som anges individuellt. Om du vill undvika allokeringar med större antal taggar använder du TagList. I allmänhet ökar prestandakostnaderna för dessa anrop när fler taggar används.
Not
OpenTelemetry refererar till taggar som "attribut". Det här är två olika namn för samma funktion.
Använda råd för att anpassa histograminstrument
När du använder Histogram är det verktygets eller bibliotekets ansvar att samla in data för att bestämma hur de bäst ska representera fördelningen av värden som har registrerats. En vanlig strategi (och standardläge när du använder OpenTelemetry) är att dela upp intervallet med möjliga värden i underintervall som kallas bucketar och rapportera hur många registrerade värden som fanns i varje bucket. Ett verktyg kan till exempel dela upp tal i tre bucketar, de som är mindre än 1, de mellan 1 och 10 och de som är större än 10. Om din app registrerade värdena 0,5, 6, 0,1, 12 skulle det finnas två datapunkter den första bucketen, en i den andra och en i den tredje.
Verktyget eller biblioteket som samlar in Histogramdata ansvarar för att definiera de bucketar som ska användas. Standardkonfigurationen för bucketen när du använder OpenTelemetry är: [ 0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000 ]
.
Standardvärdena kanske inte leder till den bästa kornigheten för varje histogram. Till exempel skulle varaktigheter för begäran mindre än en sekund alla falla i 0
-gruppen.
Verktyget eller biblioteket som samlar in Histogramdata kan erbjuda mekanismer för att tillåta användare att anpassa bucketkonfigurationen. Till exempel definierar OpenTelemetry ett Visa API. Detta kräver dock slutanvändaråtgärd och gör det till användarens ansvar att förstå datafördelningen tillräckligt bra för att välja rätt bucketar.
För att förbättra upplevelsen introducerade 9.0.0
-versionen av System.Diagnostics.DiagnosticSource
-paketet API:et (InstrumentAdvice<T>).
InstrumentAdvice
-API:et kan användas av instrumenteringsutvecklare för att ange den uppsättning rekommenderade standardgränser för buckets för ett givet histogram. Verktyget eller biblioteket som samlar in Histogramdata kan sedan välja att använda dessa värden när du konfigurerar aggregering, vilket leder till en smidigare registrering för användarna. Detta stöds i OpenTelemetry .NET SDK från och med version 1.10.0.
Viktig
I allmänhet leder fler bucketar till mer exakta data för ett visst Histogram, men varje bucket kräver minne för att lagra den aggregerade informationen och det finns cpu-kostnader för att hitta rätt bucket när du bearbetar en mätning. Det är viktigt att förstå kompromisserna mellan precision och processor-/minnesförbrukning när du väljer antalet bucketar som ska rekommenderas via InstrumentAdvice
-API:et.
Följande kod visar ett exempel med hjälp av InstrumentAdvice
-API:et för att ange rekommenderade standard bucketar.
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);
}
}
}
Ytterligare information
Mer information om explicita bucket-histogram i OpenTelemetry finns i:
Testa anpassade mått
Det är möjligt att testa anpassade mått som du lägger till med hjälp av MetricCollector<T>. Den här typen gör det enkelt att registrera mätningarna från specifika instrument och kontrollera att värdena var korrekta.
Testa med beroendeinmatning
Följande kod visar ett exempel på ett testfall för kodkomponenter som använder beroendeinmatning och 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();
}
}
Varje MetricCollector-objekt registrerar alla mått för ett instrument. Om du behöver verifiera mått från flera instrument skapar du en MetricCollector för var och en.
Testa utan beroendeinmatning
Det är också möjligt att testa kod som använder ett delat globalt mätarobjekt i ett statiskt fält, men se till att sådana tester är konfigurerade att inte köras parallellt. Eftersom mätarobjektet delas observerar MetricCollector i ett test de mått som skapats från andra tester som körs parallellt.
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);
}
}