Dela via


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 API:erna System.Diagnostics.Metrics 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.

Kommentar

.NET har några ä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

Förutsättningar: .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 startpunkten för ett bibliotek för att skapa en namngiven grupp med instrument. Instrument registrerar de numeriska mått som behövs för att beräkna mått. Här använde CreateCounter vi för att skapa ett räknareinstrument med namnet "hatco.store.hats_sold". Under varje låtsastransaktion anropar Add koden för att registrera mätningen av 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 generiska parametern intCounter<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, doubleeller 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 visa måtten härnäst.

> dotnet run
Press any key to exit

Bästa praxis

  • 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-exemplet 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 Meter till konstruktorn bör vara unikt för att skilja det från andra mätare. Vi rekommenderar riktlinjer för OpenTelemetry-namngivning, som använder prickade 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 namngivning av OpenTelemetry, som använder gemener med prickade 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:erna 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 samlingsbibliotek eller verktyg 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 självstudien används verktyget dotnet-counters , vilket är användbart för ad hoc-analys. Du kan också se självstudiekursen för måttsamling för andra alternativ. 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 hämtades 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). I kod som använder DI, till exempel ASP.NET Core eller appar med Generic Host, skapar du mätarobjektet med .IMeterFactory Från och med .NET 8 registreras IMeterFactory värdarna automatiskt i tjänstcontainern eller så kan du manuellt registrera typen i valfri 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 Createsedan . Det här exemplet visar hur du använder IMeterFactory i en ASP.NET Core-app.

Definiera en typ som ska innehå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 med DI-container i Program.cs.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<HatCoMetrics>();

Mata in måtttypen och postvärdena 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);
});

Bästa praxis

  • System.Diagnostics.Metrics.Meter implementerar IDisposable, men IMeterFactory hanterar automatiskt livslängden för alla Meter objekt som skapas och tar bort dem när DI-containern tas bort. Det är onödigt att lägga till extra kod för att anropa Dispose()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.
  • Lagring av aggregerade data – De flesta användbara mått behöver data 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 steg med hjälp av Add. De flesta verktyg beräknar totalsumman och ändringshastigheten i totalsumman. För verktyg som bara visar en sak rekommenderas ä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 räknare förutom att anroparen nu ansvarar för att underhå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.

  • 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ätningar. 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.

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 mycket heta kodsökvägar där prestanda är viktigt och användning Add skulle skapa fler än en miljon anrop per sekund per tråd, kan ObservableCounter ge fler möjligheter till optimering.

  • Histogram är vanligtvis att föredra för tidsinställningar. Ofta är det användbart att förstå svansen 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 är vanligtvis väl lämpade för UpDownCounter eller ObservableUpDownCounter. 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.

Kommentar

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) ObservableGauge är det 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(0.005, 0.015));
        }
    }
}

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

[HatCo.Store]
    hatco.store.coats_sold (Count / 1 sec)                                27
    hatco.store.hats_sold (Count / 1 sec)                                 36
    hatco.store.order_processing_time
        Percentile=50                                                      0.012
        Percentile=95                                                      0.014
        Percentile=99                                                      0.014
    hatco.store.orders_pending                                             5

I det här exemplet används några slumpmässigt genererade tal så att dina värden varierar lite. Du kan se att hatco.store.hats_sold båda (räknaren) och hatco.store.coats_sold (ObservableCounter) visas som en hastighet. ObservableGauge, hatco.store.orders_pending, visas som ett absolut värde. 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.

Bästa praxis

  • 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 motringningar som tar lång tid kan fördröja eller förhindra att alla mått samlas in. Du bör snabbt läsa ett cachelagrat värde, returnera inga mått eller utlösa ett undantag för att utföra en potentiellt tidskrävande eller blockerande åtgärd.

  • Å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 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 ingen ytterligare interaktion med objektet behövs. 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

[HatCo.Store]
    hatco.store.hats_sold ({hats} / 1 sec)                                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.

Bästa praxis

  • .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 måttet. I det här exemplet är varje mätning ett antal hattar, så "{hats}" är lämplig enhet för att skicka in konstruktorn. Samlingsverktyget beräknade en hastighet och härledde på egen hand att lämplig enhet för det beräknade må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 Program.cs för och kör appen och dotnet-räknarna på nytt 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

[HatCo.Store]
    hatco.store.hats_sold (Count / 1 sec)
        product.color=blue,product.size=19                                 9
        product.color=red,product.size=12                                 18

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

[HatCo.Store]
    hatco.store.orders_pending
        customer.country=Italy                                             6
        customer.country=Mexico                                            1
        customer.country=Spain                                             3

Bästa praxis

  • Ä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 namngivning av OpenTelemetry, som använder gemener med streckade hierarkinamn med '_' tecken 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
  • Se upp för 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 bara lagrar short för en Counter<short> upptar 2 byte per taggkombination, medan en double för Counter<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 anropas 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 anropar med tre eller färre taggar som anges individuellt. Om du vill undvika allokeringar med ett större antal taggar använder du TagList. I allmänhet ökar prestandakostnaderna för dessa anrop när fler taggar används.

Kommentar

OpenTelemetry refererar till taggar som "attribut". Det här är två olika namn för samma funktion.

Testa anpassade mått

Det är möjligt att testa eventuella 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);
    }
}