Delen via


Metrische gegevens maken

Dit artikel is van toepassing op: ✔️ .NET Core 6 en latere versies ✔️ .NET Framework 4.6.1 en latere versies

.NET-toepassingen kunnen worden geïnstrueerd met behulp van de System.Diagnostics.Metrics API's om belangrijke metrische gegevens bij te houden. Sommige metrische gegevens zijn opgenomen in standaard .NET-bibliotheken, maar u kunt nieuwe aangepaste metrische gegevens toevoegen die relevant zijn voor uw toepassingen en bibliotheken. In deze zelfstudie voegt u nieuwe metrische gegevens toe en begrijpt u welke typen metrische gegevens beschikbaar zijn.

Notitie

.NET heeft een aantal oudere metrische API's, namelijk EventCounters en System.Diagnostics.PerformanceCounter, die hier niet worden behandeld. Zie Metrische API's vergelijken voor meer informatie over deze alternatieven.

Een aangepaste meetwaarde maken

Vereisten: .NET Core 6 SDK of een latere versie

Maak een nieuwe consoletoepassing die verwijst naar het NuGet-pakket System.Diagnostics.DiagnosticSource versie 8 of hoger. Toepassingen die zich richten op .NET 8+ bevatten standaard deze verwijzing. Werk vervolgens de code bij Program.cs zodat deze overeenkomt met:

> 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);
        }
    }
}

Het System.Diagnostics.Metrics.Meter type is het toegangspunt voor een bibliotheek om een benoemde groep instrumenten te maken. Instrumenten registreren de numerieke metingen die nodig zijn om metrische gegevens te berekenen. Hier maakten CreateCounter we een counter-instrument met de naam 'hatco.store.hats_sold'. Tijdens elke transactie die zich voordoet, roept Add de code aan om de meting vast te leggen van hoeden die zijn verkocht, 4 in dit geval. Het 'hatco.store.hats_sold'-instrument definieert impliciet enkele metrische gegevens die kunnen worden berekend op basis van deze metingen, zoals het totale aantal verkochte hoeden of hoeden per seconde. Uiteindelijk is het aan metrische verzamelingshulpprogramma's om te bepalen welke metrische gegevens moeten worden berekend en hoe deze berekeningen moeten worden uitgevoerd, maar elk instrument heeft enkele standaardconventies die de intentie van de ontwikkelaar overbrengen. Voor tellerinstrumenten is de conventie dat verzamelingshulpmiddelen het totale aantal en/of de snelheid weergeven waarmee het aantal toeneemt.

De algemene parameter int aan Counter<int> en CreateCounter<int>(...) definieert dat deze teller waarden tot wel Int32.MaxValuemoet kunnen opslaan. U kunt elk van byte, short, int, , long, , floatof doubleafhankelijk decimal van de grootte van de gegevens die u moet opslaan en of breukwaarden nodig zijn.

Voer de app uit en laat deze voorlopig actief. Hierna bekijken we de metrische gegevens.

> dotnet run
Press any key to exit

Aanbevolen procedures

  • Voor code die niet is ontworpen voor gebruik in een container voor afhankelijkheidsinjectie (DI), maakt u de meter eenmaal en slaat u deze op in een statische variabele. Voor gebruik in DI-bewuste bibliotheken worden statische variabelen beschouwd als een antipatroon en in het onderstaande DI-voorbeeld ziet u een meer idiomatische benadering. Elke bibliotheek of bibliotheeksubcomponent kan (en vaak) een eigen Metermaken. U kunt een nieuwe meter maken in plaats van een bestaande meter opnieuw te gebruiken als u verwacht dat app-ontwikkelaars de groepen met metrische gegevens eenvoudig afzonderlijk kunnen in- en uitschakelen.

  • De naam die aan de Meter constructor wordt doorgegeven, moet uniek zijn om deze te onderscheiden van andere meters. We raden openTelemetry-naamgevingsrichtlijnen aan, die gebruikmaken van gestippelde hiërarchische namen. Assemblynamen of naamruimtenamen voor code die worden geïnstrueerd, zijn meestal een goede keuze. Als een assembly instrumentatie voor code toevoegt in een tweede, onafhankelijke assembly, moet de naam worden gebaseerd op de assembly die de meter definieert, niet de assembly waarvan de code wordt geïnstrueerd.

  • .NET dwingt geen naamgevingsschema voor Instrumenten af, maar we raden u aan de richtlijnen voor naamgeving van OpenTelemetry te volgen. Hierbij worden hiërarchische namen met kleine letters en een onderstrepingsteken gebruikt als scheidingsteken ('_') tussen meerdere woorden in hetzelfde element. Niet alle metrische hulpprogramma's behouden de meternaam als onderdeel van de uiteindelijke metrische naam, dus het is handig om de naam van het instrument wereldwijd uniek te maken.

    Voorbeeld van instrumentnamen:

    • contoso.ticket_queue.duration
    • contoso.reserved_tickets
    • contoso.purchased_tickets
  • De API's voor het maken van instrumenten en recordmetingen zijn thread-safe. In .NET-bibliotheken vereisen de meeste exemplaarmethoden synchronisatie wanneer ze worden aangeroepen op hetzelfde object vanuit meerdere threads, maar dat is in dit geval niet nodig.

  • De Instrument-API's voor het vastleggen van metingen (Add in dit voorbeeld) worden meestal uitgevoerd in <10 ns wanneer er geen gegevens worden verzameld, of tientallen tot honderden nanoseconden wanneer metingen worden verzameld door een verzamelingsbibliotheek of hulpprogramma met hoge prestaties. Hierdoor kunnen deze API's in de meeste gevallen vrijelijk worden gebruikt, maar zorgen voor code die uiterst prestatiegevoelig is.

De nieuwe metrische waarde weergeven

Er zijn veel opties voor het opslaan en weergeven van metrische gegevens. In deze zelfstudie wordt het hulpprogramma dotnet-counters gebruikt, wat handig is voor ad-hocanalyse. U kunt ook de zelfstudie voor het verzamelen van metrische gegevens bekijken voor andere alternatieven. Als het hulpprogramma dotnet-counters nog niet is geïnstalleerd, gebruikt u de SDK om het te installeren:

> 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.

Terwijl de voorbeeld-app nog steeds wordt uitgevoerd, gebruikt u dotnet-tellers om de nieuwe teller te controleren:

> 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

Zoals verwacht, kunt u zien dat HatCo store elke seconde geleidelijk 4 hoeden verkoopt.

Een meter ophalen via afhankelijkheidsinjectie

In het vorige voorbeeld is de meter verkregen door deze samen te stellen en new toe te wijzen aan een statisch veld. Het gebruik van statische elementen op deze manier is geen goede benadering bij het gebruik van afhankelijkheidsinjectie (DI). Maak in code die gebruikmaakt van DI, zoals ASP.NET Core of apps met Generic Host, het meterobject met behulp van IMeterFactory. Vanaf .NET 8 worden hosts automatisch geregistreerd IMeterFactory in de servicecontainer of kunt u het type IServiceCollection handmatig registreren door aan te roepen AddMetrics. De meterfactory integreert metrische gegevens met DI, waardoor meters in verschillende serviceverzamelingen van elkaar worden geïsoleerd, zelfs als ze een identieke naam gebruiken. Dit is vooral handig voor het testen, zodat meerdere tests die parallel worden uitgevoerd, alleen metingen observeren die afkomstig zijn uit dezelfde testcase.

Als u een meter wilt verkrijgen in een type dat is ontworpen voor DI, voegt u een IMeterFactory parameter toe aan de constructor en roept u Createdeze aan. In dit voorbeeld ziet u hoe u IMeterFactory gebruikt in een ASP.NET Core-app.

Definieer een type voor het opslaan van de 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);
    }
}

Registreer het type bij de DI-container in Program.cs.

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

Injecteer waar nodig het type metrische gegevens en recordwaarden. Omdat het type metrische gegevens is geregistreerd in DI, kan het worden gebruikt met MVC-controllers, minimale API's of elk ander type dat door DI wordt gemaakt:

app.MapPost("/complete-sale", ([FromBody] SaleModel model, HatCoMetrics metrics) =>
{
    // ... business logic such as saving the sale to a database ...

    metrics.HatsSold(model.QuantitySold);
});

Aanbevolen procedures

  • System.Diagnostics.Metrics.Meter implementeert IDisposable, maar IMeterFactory beheert automatisch de levensduur van objecten Meter die worden gemaakt, waardoor deze worden verwijderd wanneer de DI-container wordt verwijderd. Het is niet nodig om extra code toe te voegen om aan te roepen Dispose() op de Meter, en dit heeft geen effect.

Soorten instrumenten

Tot nu toe hebben we alleen een Counter<T> instrument gedemonstreerd, maar er zijn meer instrumenttypen beschikbaar. Instrumenten verschillen op twee manieren:

  • Standaardberekeningen voor metrische gegevens: hulpprogramma's waarmee de meetwaarden van het instrument worden verzameld en geanalyseerd, worden verschillende standaardmetrieken berekend, afhankelijk van het instrument.
  • Opslag van geaggregeerde gegevens : de meest nuttige metrische gegevens hebben gegevens nodig die moeten worden samengevoegd vanuit veel metingen. Een optie is dat de aanroeper afzonderlijke metingen op willekeurige momenten biedt en het verzamelingsprogramma de aggregatie beheert. U kunt de aanroeper ook de samenvoegingsmetingen beheren en deze op aanvraag in een callback leveren.

Soorten instrumenten die momenteel beschikbaar zijn:

  • Teller (CreateCounter) - Met dit instrument wordt een waarde bijgehouden die in de loop van de tijd toeneemt en de aanroeper de stappen rapporteert met behulp van Add. De meeste hulpprogramma's berekenen het totaal en de wijzigingssnelheid in het totaal. Voor hulpprogramma's die slechts één ding weergeven, wordt de wijzigingssnelheid aanbevolen. Stel dat de aanroeper één keer per seconde wordt aangeroepen Add() met opeenvolgende waarden 1, 2, 4, 5, 4, 3. Als het verzamelingsprogramma elke drie seconden wordt bijgewerkt, is het totaal na drie seconden 1+2+4=7 en het totaal na zes seconden 1+2+4+5+4+3=19. De wijzigingssnelheid is de (current_total - previous_total), dus bij drie seconden rapporteert het hulpprogramma 7-0=7 en na zes seconden rapporteert het 19-7=12.

  • UpDownCounter (CreateUpDownCounter) - Met dit instrument wordt een waarde bijgehouden die na verloop van tijd kan toenemen of afnemen. De beller rapporteert de verhogingen en aflopende bewerkingen met behulp van Add. Stel dat de aanroeper één keer per seconde wordt aangeroepen Add() met opeenvolgende waarden 1, 5, -2, 3, -1, -3. Als het verzamelingsprogramma elke drie seconden wordt bijgewerkt, is het totaal na drie seconden 1+5-2=4 en het totaal na zes seconden 1+5-2+3-1-3=3.

  • ObservableCounter (CreateObservableCounter) - Dit instrument is vergelijkbaar met Teller, behalve dat de aanroeper nu verantwoordelijk is voor het onderhouden van het geaggregeerde totaal. De beller biedt een callback-gemachtigde wanneer de ObservableCounter wordt gemaakt en de callback wordt aangeroepen wanneer hulpprogramma's het huidige totaal moeten observeren. Als een verzamelingsprogramma bijvoorbeeld om de drie seconden wordt bijgewerkt, wordt de callback-functie ook om de drie seconden aangeroepen. De meeste hulpprogramma's hebben zowel het totale als het totale aantal wijzigingen in het totaal dat beschikbaar is. Als er slechts één kan worden weergegeven, wordt de wijzigingssnelheid aanbevolen. Als de callback 0 retourneert bij de eerste aanroep, 7 wanneer deze na drie seconden opnieuw wordt aangeroepen en 19 wanneer deze na zes seconden wordt aangeroepen, worden deze waarden door het hulpprogramma als totalen weergegeven. Voor wijzigingssnelheid wordt 7-0=7 weergegeven na drie seconden en 19-7=12 na zes seconden.

  • ObservableUpDownCounter (CreateObservableUpDownCounter) - Dit instrument is vergelijkbaar met UpDownCounter, behalve dat de beller nu verantwoordelijk is voor het onderhouden van het geaggregeerde totaal. De beller biedt een callback-gemachtigde wanneer de ObservableUpDownCounter wordt gemaakt en de callback wordt aangeroepen wanneer hulpprogramma's het huidige totaal moeten observeren. Als een verzamelingsprogramma bijvoorbeeld om de drie seconden wordt bijgewerkt, wordt de callback-functie ook om de drie seconden aangeroepen. De waarde die door de callback wordt geretourneerd, wordt in het verzamelingsprogramma ongewijzigd weergegeven als het totaal.

  • ObservableGauge (CreateObservableGauge) - Met dit instrument kan de beller een callback opgeven waarbij de gemeten waarde rechtstreeks als metrische waarde wordt doorgegeven. Telkens wanneer het verzamelingsprogramma wordt bijgewerkt, wordt de callback aangeroepen en wordt de waarde geretourneerd door de callback in het hulpprogramma.

  • Histogram (CreateHistogram) - Dit instrument houdt de verdeling van metingen bij. Er is geen enkele canonieke manier om een set metingen te beschrijven, maar hulpprogramma's worden aanbevolen om histogrammen of berekende percentielen te gebruiken. Stel dat de aanroeper die is aangeroepen Record om deze metingen vast te leggen tijdens het update-interval van het verzamelingsprogramma: 1,5,2,3,10,9,7,4,6,8. Een verzamelingsprogramma kan rapporteren dat de 50e, 90e en 95e percentielen van deze metingen respectievelijk 5, 9 en 9 zijn.

Aanbevolen procedures bij het selecteren van een instrumenttype

  • Voor het tellen van dingen of een andere waarde die alleen in de loop van de tijd toeneemt, gebruikt u Counter of ObservableCounter. Kies tussen Counter en ObservableCounter, afhankelijk van wat eenvoudiger is om toe te voegen aan de bestaande code: een API-aanroep voor elke incrementele bewerking of een callback waarmee het huidige totaal wordt gelezen uit een variabele die door de code wordt onderhouden. In extreem dynamische-codepaden waar de prestaties belangrijk zijn en het gebruik Add meer dan één miljoen aanroepen per seconde per thread zou maken, biedt het gebruik van ObservableCounter mogelijk meer mogelijkheden voor optimalisatie.

  • Voor tijdsinstellingen heeft histogram meestal de voorkeur. Vaak is het handig om inzicht te hebben in de staart van deze distributies (90e, 95e, 99e percentiel) in plaats van gemiddelden of totalen.

  • Andere veelvoorkomende gevallen, zoals cachetreffers of grootten van caches, wachtrijen en bestanden, zijn meestal goed geschikt voor UpDownCounter of ObservableUpDownCounter. Kies ertussen, afhankelijk van wat u gemakkelijker kunt toevoegen aan de bestaande code: een API-aanroep voor elke incrementele en aflopende bewerking of een callback die de huidige waarde van een variabele leest die door de code wordt onderhouden.

Notitie

Als u een oudere versie van .NET of een DiagnosticSource NuGet-pakket gebruikt dat niet wordt ondersteund UpDownCounter en ObservableUpDownCounter (vóór versie 7), ObservableGauge is dit vaak een goede vervanging.

Voorbeeld van verschillende instrumenttypen

Stop het voorbeeldproces dat eerder is gestart en vervang de voorbeeldcode door Program.cs :

using System;
using System.Diagnostics.Metrics;
using System.Threading;

class Program
{
    static Meter s_meter = new Meter("HatCo.Store");
    static Counter<int> s_hatsSold = s_meter.CreateCounter<int>("hatco.store.hats_sold");
    static Histogram<double> s_orderProcessingTime = s_meter.CreateHistogram<double>("hatco.store.order_processing_time");
    static int s_coatsSold;
    static int s_ordersPending;

    static Random s_rand = new Random();

    static void Main(string[] args)
    {
        s_meter.CreateObservableCounter<int>("hatco.store.coats_sold", () => s_coatsSold);
        s_meter.CreateObservableGauge<int>("hatco.store.orders_pending", () => s_ordersPending);

        Console.WriteLine("Press any key to exit");
        while(!Console.KeyAvailable)
        {
            // Pretend our store has one transaction each 100ms that each sell 4 hats
            Thread.Sleep(100);
            s_hatsSold.Add(4);

            // Pretend we also sold 3 coats. For an ObservableCounter we track the value in our variable and report it
            // on demand in the callback
            s_coatsSold += 3;

            // Pretend we have some queue of orders that varies over time. The callback for the orders_pending gauge will report
            // this value on-demand.
            s_ordersPending = s_rand.Next(0, 20);

            // Last we pretend that we measured how long it took to do the transaction (for example we could time it with Stopwatch)
            s_orderProcessingTime.Record(s_rand.Next(0.005, 0.015));
        }
    }
}

Voer het nieuwe proces uit en gebruik dotnet-tellers zoals voorheen in een tweede shell om de metrische gegevens weer te geven:

> 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

In dit voorbeeld worden enkele willekeurig gegenereerde getallen gebruikt, zodat uw waarden enigszins variëren. U kunt zien dat hatco.store.hats_sold (de teller) en hatco.store.coats_sold (de ObservableCounter) beide als een tarief worden weergegeven. De WaarneembareGauge, hatco.store.orders_pendingwordt weergegeven als een absolute waarde. Dotnet-counters geven histograminstrumenten weer als drie percentielstatistieken (50e, 95e en 99e), maar andere hulpprogramma's kunnen de verdeling anders samenvatten of meer configuratieopties bieden.

Aanbevolen procedures

  • Histogrammen slaan meestal veel meer gegevens op in het geheugen dan andere metrische typen. Het exacte geheugengebruik wordt echter bepaald door het hulpprogramma voor verzameling dat wordt gebruikt. Als u een groot aantal (>100) metrische gegevens over histogram definieert, moet u gebruikers mogelijk richtlijnen geven om ze niet allemaal tegelijk in te schakelen of om hun hulpprogramma's te configureren om geheugen te besparen door de precisie te verminderen. Sommige verzamelingshulpprogramma's hebben mogelijk vaste limieten voor het aantal gelijktijdige histogrammen dat ze bewaken om overmatig geheugengebruik te voorkomen.

  • Callbacks voor alle waarneembare instrumenten worden op volgorde aangeroepen, dus elke callback die lang duurt, kan vertragen of voorkomen dat alle metrische gegevens worden verzameld. Gun het snel lezen van een waarde in de cache, het retourneren van geen metingen of het genereren van een uitzondering over het uitvoeren van een potentieel langlopende of blokkerende bewerking.

  • De callbacks ObservableCounter, ObservableUpDownCounter en ObservableGauge vinden plaats op een thread die meestal niet wordt gesynchroniseerd met de code waarmee de waarden worden bijgewerkt. Het is uw verantwoordelijkheid om geheugentoegang te synchroniseren of de inconsistente waarden te accepteren die kunnen voortvloeien uit het gebruik van niet-gesynchroniseerde toegang. Algemene benaderingen voor het synchroniseren van toegang zijn het gebruik van een vergrendeling of gesprek Volatile.Read en Volatile.Write.

  • De CreateObservableGauge functies retourneren CreateObservableCounter wel een instrumentobject, maar in de meeste gevallen hoeft u het niet op te slaan in een variabele, omdat er geen verdere interactie met het object nodig is. Het toewijzen van deze variabele aan een statische variabele zoals we voor de andere instrumenten hebben gedaan, is juridisch maar foutgevoelig, omdat statische C#-initialisatie lui is en de variabele meestal nooit wordt verwezen. Hier volgt een voorbeeld van het probleem:

    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();
        }
    }
    

Beschrijvingen en eenheden

Instrumenten kunnen optionele beschrijvingen en eenheden opgeven. Deze waarden zijn ondoorzichtig voor alle metrische berekeningen, maar kunnen worden weergegeven in de gebruikersinterface van het verzamelingsprogramma om technici te helpen begrijpen hoe de gegevens moeten worden geïnterpreteerd. Stop het voorbeeldproces dat u eerder hebt gestart en vervang de voorbeeldcode door Program.cs :

using System;
using System.Diagnostics.Metrics;
using System.Threading;

class Program
{
    static Meter s_meter = new Meter("HatCo.Store");
    static Counter<int> s_hatsSold = s_meter.CreateCounter<int>(name: "hatco.store.hats_sold",
                                                                unit: "{hats}",
                                                                description: "The number of hats sold in our store");

    static void Main(string[] args)
    {
        Console.WriteLine("Press any key to exit");
        while(!Console.KeyAvailable)
        {
            // Pretend our store has a transaction each 100ms that sells 4 hats
            Thread.Sleep(100);
            s_hatsSold.Add(4);
        }
    }
}

Voer het nieuwe proces uit en gebruik dotnet-tellers zoals voorheen in een tweede shell om de metrische gegevens weer te geven:

Press p to pause, r to resume, q to quit.
    Status: Running

[HatCo.Store]
    hatco.store.hats_sold ({hats} / 1 sec)                                40

dotnet-counters gebruiken momenteel niet de beschrijvingstekst in de gebruikersinterface, maar de eenheid wordt wel weergegeven wanneer deze wordt opgegeven. In dit geval ziet u dat {hats} de algemene term 'Count' heeft vervangen die zichtbaar is in eerdere beschrijvingen.

Aanbevolen procedures

  • Met .NET-API's kan elke tekenreeks als eenheid worden gebruikt, maar we raden u aan UCUM te gebruiken, een internationale standaard voor eenheidsnamen. De accolades rond {hats} maakt deel uit van de UCUM-standaard, wat aangeeft dat het een beschrijvende aantekening is in plaats van een eenheidsnaam met een gestandaardiseerde betekenis zoals seconden of bytes.

  • De eenheid die in de constructor is opgegeven, moet de eenheden beschrijven die geschikt zijn voor een afzonderlijke meting. Dit verschilt soms van de eenheden voor de uiteindelijke metrische waarde. In dit voorbeeld is elke meting een aantal hoeden, dus {hats} is de juiste eenheid die in de constructor moet worden doorgegeven. Het verzamelingsprogramma heeft zelf een snelheid berekend en afgeleid dat de juiste eenheid voor de berekende metrische waarde {hats}/sec is.

  • Bij het opnemen van metingen van tijd geeft u de voorkeur aan eenheden van seconden die zijn vastgelegd als een drijvende komma of dubbele waarde.

Multidimensionale metrische gegevens

Metingen kunnen ook worden gekoppeld aan sleutel-waardeparen die tags worden genoemd waarmee gegevens kunnen worden gecategoriseerd voor analyse. HatCo wil bijvoorbeeld niet alleen het aantal hoeden opnemen dat is verkocht, maar ook welke grootte en kleur ze waren. Wanneer u de gegevens later analyseert, kunnen HatCo-technici de totalen op maat, kleur of een combinatie van beide opsplitsen.

Tags voor tellers en histogrammen kunnen worden opgegeven in overbelastingen van de Add en Record die een of meer KeyValuePair argumenten aannemen. Bijvoorbeeld:

s_hatsSold.Add(2,
               new KeyValuePair<string, object>("product.color", "red"),
               new KeyValuePair<string, object>("product.size", 12));

Vervang de code van Program.cs en voer de app- en dotnet-tellers opnieuw uit zoals voorheen:

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-tellers toont nu een basiscategorisatie:

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

Voor ObservableCounter en ObservableGauge kunnen getagde metingen worden opgegeven in de callback die wordt doorgegeven aan de constructor:

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")),
        };
    }
}

Wanneer het wordt uitgevoerd met dotnet-counters zoals voorheen, is het resultaat:

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

Aanbevolen procedures

  • Hoewel met de API elk object kan worden gebruikt als de tagwaarde, worden numerieke typen en tekenreeksen verwacht door verzamelingshulpprogramma's. Andere typen kunnen wel of niet worden ondersteund door een bepaald verzamelingsprogramma.

  • U wordt aangeraden tagnamen te volgen volgens de richtlijnen voor naamgeving van OpenTelemetry, waarbij gebruik wordtgemaakt van hiërarchische namen in kleine letters met _-tekens om meerdere woorden in hetzelfde element te scheiden. Als tagnamen opnieuw worden gebruikt in verschillende metrische gegevens of andere telemetrierecords, moeten ze dezelfde betekenis en set juridische waarden hebben overal waar ze worden gebruikt.

    Voorbeeld van tagnamen:

    • customer.country
    • store.payment_method
    • store.purchase_result
  • Let op dat er in de praktijk zeer grote of niet-gebonden combinaties van tagwaarden worden vastgelegd. Hoewel de implementatie van de .NET-API deze kan verwerken, wijst verzamelingshulpprogramma's waarschijnlijk opslag toe voor metrische gegevens die zijn gekoppeld aan elke tagcombinatie en kan dit erg groot worden. Het is bijvoorbeeld prima als HatCo 10 verschillende hoedkleuren en 25 hoedformaten heeft voor maximaal 10*25=250 verkooptotalen om bij te houden. Als HatCo echter een derde tag heeft toegevoegd die een CustomerID is voor de verkoop en ze verkopen aan 100 miljoen klanten wereldwijd, zijn er nu waarschijnlijk miljarden verschillende tagcombinaties opgenomen. De meeste hulpprogramma's voor het verzamelen van metrische gegevens verwijderen gegevens om binnen technische limieten te blijven of er kunnen grote monetaire kosten zijn om de gegevensopslag en -verwerking te dekken. De implementatie van elk verzamelingsprogramma bepaalt de limieten, maar waarschijnlijk minder dan 1000 combinaties voor één instrument is veilig. Voor alles boven de 1000 combinaties moet het verzamelingsprogramma filteren toepassen of worden ontworpen om op grote schaal te kunnen werken. Histogramimplementaties gebruiken meestal veel meer geheugen dan andere metrische gegevens, zodat veilige limieten 10-100 keer lager kunnen zijn. Als u een groot aantal unieke tagcombinaties verwacht, zijn logboeken, transactionele databases of systemen voor verwerking van big data mogelijk geschiktere oplossingen om op de benodigde schaal te werken.

  • Voor instrumenten met zeer grote aantallen tagcombinaties gebruikt u liever een kleiner opslagtype om de geheugenoverhead te verminderen. Als u de short opslag voor een Counter<short> tag bijvoorbeeld slechts 2 bytes per tagcombinatie in beslag neemt, neemt een double for Counter<double> 8 bytes per tagcombinatie in beslag.

  • Verzamelingshulpprogramma's worden aangemoedigd om te optimaliseren voor code waarmee dezelfde set tagnamen in dezelfde volgorde wordt opgegeven voor elke aanroep om metingen op hetzelfde instrument vast te leggen. Voor code met hoge prestaties die moet worden aangeroepen en Record die vaak moeten worden aangeroepenAdd, gebruikt u liever dezelfde reeks tagnamen voor elke aanroep.

  • De .NET-API is geoptimaliseerd voor toewijzingsvrij voor Add en Record aanroepen met drie of minder tags die afzonderlijk zijn opgegeven. Als u toewijzingen met grotere aantallen tags wilt voorkomen, gebruikt u TagList. Over het algemeen neemt de overhead van de prestaties van deze aanroepen toe naarmate er meer tags worden gebruikt.

Notitie

OpenTelemetry verwijst naar tags als 'kenmerken'. Dit zijn twee verschillende namen voor dezelfde functionaliteit.

Aangepaste metrische gegevens testen

Het is mogelijk om aangepaste metrische gegevens te testen die u toevoegt met behulp van MetricCollector<T>. Dit type maakt het gemakkelijk om de metingen van specifieke instrumenten vast te leggen en te bevestigen dat de waarden juist waren.

Testen met afhankelijkheidsinjectie

De volgende code toont een voorbeeldtestcase voor codeonderdelen die gebruikmaken van afhankelijkheidsinjectie en 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();
    }
}

Elk MetricCollector-object registreert alle metingen voor één instrument. Als u de metingen van meerdere instrumenten wilt controleren, maakt u één MetricCollector voor elk instrument.

Testen zonder afhankelijkheidsinjectie

Het is ook mogelijk om code te testen die gebruikmaakt van een gedeeld globaal meterobject in een statisch veld, maar zorg ervoor dat dergelijke tests niet parallel worden uitgevoerd. Omdat het meterobject wordt gedeeld, ziet MetricCollector in de ene test de metingen die zijn gemaakt op basis van andere tests die parallel worden uitgevoerd.

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);
    }
}