Sdílet prostřednictvím


Vytváření metrik

Tento článek se vztahuje na: ✔️ .NET Core 6 a novější verze ✔️ rozhraní .NET Framework 4.6.1 a novější verze

Aplikace .NET je možné instrumentovat pomocí rozhraní API System.Diagnostics.Metrics ke sledování důležitých metrik. Některé metriky jsou součástí standardních knihoven .NET, ale můžete chtít přidat nové vlastní metriky, které jsou relevantní pro vaše aplikace a knihovny. V tomto kurzu přidáte nové metriky a pochopíte, jaké typy metrik jsou k dispozici.

Poznámka

.NET má některá starší rozhraní API metrik, konkrétně EventCounters a System.Diagnostics.PerformanceCounter, které zde nejsou popsané. Další informace o těchto alternativách najdete v tématu Porovnání rozhraní API metrik.

Vytvoření vlastní metriky

požadavky: .NET Core 6 SDK nebo novější verzi

Vytvořte novou konzolovou aplikaci, která odkazuje na balíček NuGet System.Diagnostics.DiagnosticSource verze 8 nebo vyšší. Aplikace, které cílí na .NET 8 nebo novější, zahrnují tento odkaz ve výchozím nastavení. Potom aktualizujte kód v Program.cs tak, aby odpovídal:

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

Typ System.Diagnostics.Metrics.Meter je vstupním bodem knihovny pro vytvoření pojmenované skupiny nástrojů. Nástroje zaznamenávají číselné hodnoty potřebné k výpočtu metrik. Zde jsme použili CreateCounter k vytvoření nástroje Counter s názvem "hatco.store.hats_sold". Během každé předstírané transakce kód volá Add, aby zaznamenal měření klobouků, které byly prodány, v tomto případě 4. Nástroj "hatco.store.hats_sold" implicitně definuje některé metriky, které je možné vypočítat z těchto měření, například celkový počet prodaných klobouků nebo prodané klobouky za sekundu. Nakonec je to až nástroje pro shromažďování metrik, které určují, které metriky se mají vypočítat a jak tyto výpočty provádět, ale každý nástroj má některé výchozí konvence, které vyjadřují záměr vývojáře. U nástrojů typu Counter je obvyklé, že nástroje pro sběr dat zobrazují celkový počet a/nebo rychlost nárůstu tohoto počtu.

Obecný parametr int pro Counter<int> a CreateCounter<int>(...) definuje, že tento čítač musí být schopen ukládat hodnoty až do Int32.MaxValue. Můžete použít libovolnou byte, short, int, long, float, doublenebo decimal v závislosti na velikosti dat, která potřebujete uložit a jestli jsou potřeba desetinné hodnoty.

Spusťte aplikaci a nechte ji spuštěnou. Na další metriky se podíváme.

> dotnet run
Press any key to exit

Osvědčené postupy

  • Pro kód, který není určen pro použití v containeru dependency injection (DI), vytvořte metr jednou a uložte ho do statické proměnné. Pro použití v knihovnách s podporou DI jsou statické proměnné považovány za anti-vzor a PŘÍKLAD DI níže ukazuje idiomatičtější přístup. Každá knihovna nebo podkomponent knihovny (a často by měla) vytvořit vlastní Meter. Pokud očekáváte, že vývojáři aplikací budou moci samostatně povolit a zakázat skupiny metrik, zvažte vytvoření nového měřiče namísto znovupoužití stávajícího.

  • Název předaný konstruktoru Meter by měl být jedinečný, aby se odlišil od ostatních měřičů. Doporučujeme pokyny pro pojmenování OpenTelemetry, které využívají hierarchická jména s tečkami. Názvy sestavení nebo názvy jmenných prostorů pro instrumentovaný kód jsou obvykle dobrou volbou. Pokud sestavení přidá instrumentaci pro kód v druhém, nezávislém sestavení, název by měl být založen na sestavení, které definuje měřič, nikoli sestavení, jehož kód je instrumentován.

  • .NET nevynucuje žádné schéma pojmenování pro nástroje, ale doporučujeme postupovat podle pokynů pro pojmenování OpenTelemetry, které používají malá písmena tečkovaných hierarchických názvů a podtržítka ('_') jako oddělovač mezi více slovy ve stejném prvku. Ne všechny nástroje metriky zachovávají název měřiče jako součást konečného názvu metriky, takže je vhodné, aby byl název nástroje globálně jedinečný sám.

    Příklady názvů instrumentů:

    • contoso.ticket_queue.duration
    • contoso.reserved_tickets
    • contoso.purchased_tickets
  • Rozhraní API pro vytváření nástrojů a zaznamenávání měření jsou bezpečná pro přístup z více vláken. V knihovnách .NET většina metod instancí vyžaduje synchronizaci při vyvolání stejného objektu z více vláken, ale to v tomto případě není potřeba.

  • Rozhraní API pro zaznamenávání měření (Add v tomto příkladu) se obvykle spouští v <10 ns, pokud se neshromažďují žádná data, nebo v řádu desítek až stovek nanosekund při shromažďování měření knihovnou nebo nástrojem pro vysoce výkonné shromažďování dat. To umožňuje tato rozhraní API ve většině případů používatt, ale dbejte na kód, který je velmi citlivý na výkon.

Zobrazení nové metriky

Existuje mnoho možností, jak ukládat a zobrazovat metriky. Tento kurz používá nástroj dotnet-counters, což je užitečné pro ad hoc analýzu. Můžete se také podívat na kurz shromažďování metrik pro další alternativy. Pokud nástroj dotnet-counters ještě není nainstalovaný, nainstalujte ho pomocí sady SDK:

> dotnet tool update -g dotnet-counters
You can invoke the tool using the following command: dotnet-counters
Tool 'dotnet-counters' (version '7.0.430602') was successfully installed.

I když je ukázková aplikace stále spuštěná, pomocí dotnet-counters monitorujte nový čítač:

> 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

Podle očekávání můžete vidět, že HatCo store neustále prodává 4 klobouky za každou sekundu.

Získejte měřič prostřednictvím injekce závislostí

V předchozím příkladu byl měřič získán jeho sestavením pomocí new a přiřazením ke statickému poli. Používání statik tímto způsobem není dobrý přístup při vkládání závislostí (DI). V kódu, který používá DI, jako je ASP.NET Core nebo aplikace s Generic Host, vytvořte objekt Meter pomocí IMeterFactory. Počínaje rozhraním .NET 8 se hostitelé automaticky zaregistrují IMeterFactory v kontejneru služby nebo můžete typ zaregistrovat ručně v libovolném IServiceCollection voláním AddMetrics. Továrna měřiče integruje metriky s DI a udržuje měřiče v různých kolekcích služeb izolované od sebe, i když používají stejný název. To je zvlášť užitečné pro testování, aby více testů spuštěných paralelně sledovalo měření vytvořená pouze v rámci stejného testovacího případu.

Chcete-li získat měřič v typu určeném pro DI, přidejte do konstruktoru parametr IMeterFactory a potom volejte Create. Tento příklad ukazuje použití IMeterFactory v aplikaci ASP.NET Core.

Definujte typ pro uložení nástrojů:

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

Zaregistrujte typ v kontejneru DI v Program.cs.

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

Tam, kde je to potřeba, zadejte typ metriky a poznamenejte si hodnoty. Vzhledem k tomu, že je typ metrik zaregistrovaný v DI, je možné ho použít s řadiči MVC, minimálními rozhraními API nebo jakýmkoli jiným typem, který je vytvořen pomocí DI:

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

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

Osvědčené postupy

  • System.Diagnostics.Metrics.Meter implementuje IDisposable, ale IMeterFactory automaticky spravuje životnost všech Meter objektů, které vytvoří, a po odstranění kontejneru DI je odstraní. Není nutné přidat další kód pro vyvolání Dispose() na Metera nebude mít žádný vliv.

Typy nástrojů

Zatím jsme ukázali pouze Counter<T> nástroj, ale k dispozici je více typů nástrojů. Nástroje se liší dvěma způsoby:

  • Výchozí výpočty metrik – Nástroje, které shromažďují a analyzují měření přístrojů, budou v závislosti na přístroji počítat různé výchozí metriky.
  • Úložiště agregovaných dat – Nejužitečnější metriky vyžadují agregaci dat z mnoha měření. Jednou z možností je, že volající poskytuje individuální měření v libovolných časech a nástroj kolekce spravuje agregaci. Volající může také spravovat agregované měření a poskytovat je na vyžádání v zpětném volání.

Typy nástrojů, které jsou aktuálně k dispozici:

  • Counter (CreateCounter) – tento nástroj sleduje hodnotu, která se v průběhu času zvyšuje, a volající hlásí přírůstky pomocí Add. Většina nástrojů vypočítá celkový součet a míru změny v součtu. U nástrojů, které ukazují jen jednu věc, se doporučuje četnost změn. Předpokládejme například, že volající vyvolá Add() jednou za sekundu s po sobě jdoucími hodnotami 1, 2, 4, 5, 4, 4, 3. Pokud se nástroj kolekce aktualizuje každých tři sekundy, je celkový počet po třech sekundách 1+2+4=7 a celkový počet po šesti sekundách je 1+2+4+5+4+3=19. Míra změny je (aktuální_součet - předchozí_součet), takže nástroj po třech sekundách uvede 7-0=7 a po šesti sekundách uvede 19-7=12.

  • UpDownCounter (CreateUpDownCounter) – tento nástroj sleduje hodnotu, která se může v průběhu času zvýšit nebo snížit. Volající hlásí přírůstky a snižování pomocí Add. Předpokládejme například, že volající vyvolá Add() jednou za sekundu s následnými hodnotami 1, 5, -2, 3, -1, -3. Pokud se nástroj kolekce aktualizuje každé tři sekundy, je celkový součet po třech sekundách 1+5-2=4 a celkový počet po šesti sekundách je 1+5-2+3-1-3=3.

  • ObservableCounter (CreateObservableCounter) – tento nástroj se podobá čítači s tím rozdílem, že volající je teď zodpovědný za udržování agregovaného součtu. Volající poskytuje delegáta zpětného volání při vytvoření ObservableCounter a toto zpětné volání je vyvoláno, kdykoli nástroje potřebují sledovat aktuální součet. Pokud například nástroj kolekce aktualizuje každé tři sekundy, funkce zpětného volání se vyvolá také každé tři sekundy. Většina nástrojů bude mít k dispozici jak celkovou hodnotu, tak míru její změny. Pokud lze zobrazit pouze jedna položka, doporučuje se tempo změny. Pokud zpětné volání vrátí hodnotu 0 při počátečním volání, 7, když se znovu zavolá po třech sekundách, a 19 při volání po šesti sekundách, nástroj ohlásí tyto hodnoty beze změny jako součty. U míry změn nástroj zobrazí po třech sekundách 7-0=7 a po šesti sekundách 19-7=12.

  • ObservableUpDownCounter (CreateObservableUpDownCounter) – tento nástroj je podobný upDownCounter s tím rozdílem, že volající je teď zodpovědný za udržování agregovaného součtu. Volající poskytuje delegáta pro zpětné volání při vytvoření ObservableUpDownCounter a tento delegát se vyvolá pokaždé, když aplikace potřebují sledovat aktuální součet. Pokud například nástroj kolekce aktualizuje každé tři sekundy, funkce zpětného volání se vyvolá také každé tři sekundy. Jakákoli hodnota vrácená zpětným voláním se zobrazí v nástroji kolekce beze změny jako součet.

  • měřidlo (CreateGauge) – tento nástroj umožňuje volajícímu nastavit aktuální hodnotu metriky pomocí metody Record. Hodnotu lze kdykoli aktualizovat opětovným vyvoláním metody a nástrojem pro shromažďování metrik se zobrazí jakákoli hodnota, která byla naposledy nastavena.

  • ObservableGauge (CreateObservableGauge) – tento nástroj umožňuje volajícímu poskytnout zpětné volání, ve kterém se naměřená hodnota předává přímo jako metrika. Pokaždé, když se nástroj kolekce aktualizuje, vyvolá se zpětné volání a v nástroji se zobrazí jakákoli hodnota vrácená zpětným voláním.

  • Histogram (CreateHistogram) – tento nástroj sleduje distribuci měření. Neexistuje jediný kanonický způsob, jak popsat sadu měření, ale doporučuje se použít histogramy nebo vypočítané percentily. Předpokládejme například, že volající použil Record ke zaznamenání těchto měření během intervalu aktualizace nástroje pro sběr dat: 1,5,2,3,10,9,7,4,6,8. Nástroj pro sběr dat může hlásit, že 50., 90. a 95. percentily těchto měření jsou 5, 9 a 9.

    Poznámka

    Podrobnosti o tom, jak nastavit doporučené hranice kbelíku při vytváření nástroje Histogram, najdete v tématu: Použití doporučení k přizpůsobení nástrojů Histogramu.

Osvědčené postupy při výběru typu nástroje

  • Pro počítání věcí nebo jakékoli jiné hodnoty, které se pouze v průběhu času zvyšují, použijte Counter nebo ObservableCounter. V závislosti na tom, které je snazší přidat do existujícího kódu, si můžete vybrat mezi čítačem a ObservableCounter: voláním rozhraní API pro každou operaci přírůstku nebo zpětným voláním, které přečte aktuální součet z proměnné, kterou kód udržuje. V extrémně vytížených úsecích kódu, kde je výkon důležitý a použití Add by vytvořilo více než milion volání za sekundu za vlákno, může použití ObservableCounter nabídnout více příležitostí pro optimalizaci.

  • Pro časování je obvykle upřednostňovaný histogram. Často je vhodné porozumět hladině těchto rozdělení (90., 95. a 99. percentil) spíše než průměrům nebo součtům.

  • Jiné běžné případy, jako jsou zásahy do mezipaměti nebo velikosti mezipamětí, front a souborů, jsou obvykle vhodné pro UpDownCounter nebo ObservableUpDownCounter. Vyberte si mezi nimi v závislosti na tom, který kód se snadněji přidá do existujícího kódu: buď volání rozhraní API pro každou operaci přírůstku a dekrementace, nebo zpětné volání, které přečte aktuální hodnotu z proměnné, kterou kód udržuje.

Poznámka

Pokud používáte starší verzi .NET nebo balíček NuGet DiagnosticSource, který nepodporuje UpDownCounter a ObservableUpDownCounter (před verzí 7), je ObservableGauge často dobrou náhradou.

Příklad různých typů nástrojů

Zastavte dříve spuštěný ukázkový proces a nahraďte ukázkový kód v Program.cs následujícím kódem:

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

Spusťte nový proces a stejně jako dříve použijte dotnet-counters v druhém terminálu k zobrazení metrik:

> 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    

V tomto příkladu se používají nějaká náhodně vygenerovaná čísla, takže se hodnoty budou trochu lišit. Dotnet-counters vykresluje nástroje Histogramu jako tři percentilové statistiky (50. 95. a 99.), ale jiné nástroje můžou sumarizovat distribuci jinak nebo nabízejí více možností konfigurace.

Osvědčené postupy

  • Histogramy obvykle ukládají mnohem více dat do paměti než jiné typy metrik. Přesné využití paměti je však určeno použitým nástrojem kolekce. Pokud definujete velký počet (>100) metrik histogramu, možná budete muset poskytnout uživatelům pokyny, aby je nepovolovali všechny najednou, nebo aby nakonfigurovali své nástroje pro úsporu paměti snížením přesnosti. Některé nástroje kolekce můžou mít pevné limity počtu souběžných histogramů, které budou monitorovat, aby se zabránilo nadměrnému využití paměti.

  • Zpětná volání pro všechny pozorovatelné nástroje jsou vyvolána v posloupnosti, takže jakákoli zpětná volání, která trvá dlouhou dobu, může zpozdit nebo zabránit shromažďování všech metrik. Upřednostnění rychlého čtení hodnoty uložené v mezipaměti, vrácení žádných měření nebo vyvolání výjimky nad prováděním potenciálně dlouhotrvající nebo blokující operace.

  • Zpětná volání ObservableCounter, ObservableUpDownCounter a ObservableGauge probíhají ve vlákně, které obvykle není synchronizované s kódem, který aktualizuje hodnoty. Je vaší zodpovědností buď synchronizovat přístup k paměti, nebo přijmout nekonzistentní hodnoty, které můžou mít za následek použití nesynchronizovaného přístupu. Běžnými přístupy k synchronizaci přístupu jsou použití zámku nebo volání Volatile.Read a Volatile.Write.

  • Funkce CreateObservableGauge a CreateObservableCounter vrací objekt instrumentu, ale ve většině případů ho nemusíte ukládat do proměnné, protože není nutná žádná další interakce s objektem. Přiřazení it ke statické proměnné, jak jsme to udělali u ostatních nástrojů, je legální, ale náchylné k chybám, protože statická inicializace v jazyce C# je líná a na proměnnou se obvykle nikdy neodkazuje. Tady je příklad problému:

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

Popisy a jednotky

Nástroje mohou určovat volitelné popisy a jednotky. Tyto hodnoty jsou neprůkazné pro všechny výpočty metrik, ale je možné je zobrazit v uživatelském rozhraní nástroje kolekce, aby technici pochopili, jak interpretovat data. Zastavte ukázkový proces, který jste začali dříve, a nahraďte ukázkový kód v Program.cs následujícím kódem:

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

Spusťte nový proces a pomocí dotnet-counters jako předtím v druhém příkazovém rozhraní zobrazte metriky.

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 v současné době nepoužívá text popisu v uživatelském rozhraní, ale zobrazuje jednotku, když je k dispozici. V tomto případě se zobrazí, že {hats} nahradil obecný termín "Počet", který je viditelný v předchozích popisech.

Osvědčené postupy

  • .NET API umožňují použití libovolného řetězce jako jednotky, ale doporučujeme použít UCUM, což je mezinárodní standard pro názvy jednotek. Složené závorky kolem {hats} jsou součástí standardu UCUM, který označuje, že se jedná o popisnou poznámku, nikoli název jednotky se standardizovaným významem, jako jsou sekundy nebo bajty.

  • Jednotka zadaná v konstruktoru by měla popisovat jednotky vhodné pro jednotlivá měření. To se někdy liší od jednotek v konečné hlášené metrice. V tomto příkladu je každé měření počtem klobouků, takže "{hats}" je vhodná jednotka pro předání konstruktoru. Nástroj kolekce mohl vypočítat míru změny a usoudit samo, že příslušná jednotka pro metriku vypočítané rychlosti je {hats} za sekundu.

  • Při zaznamenávání měření času upřednostňujete jednotky sekund zaznamenané jako plovoucí desetina nebo dvojitou hodnotu.

Multidimenzionální metriky

Měření lze také přidružit ke párům klíč-hodnota označovaným jako značky, které umožňují kategorizaci dat pro analýzu. Například HatCo může chtít zaznamenat nejen počet prodaných klobouků, ale také velikost a barvu, které byly. Při pozdější analýze dat mohou technici HatCo rozdělit součty podle velikosti, barvy nebo jakékoli kombinace obou.

Značky čítače a histogramu lze specifikovat v přetíženích Add a Record, které přijímají jeden nebo více argumentů KeyValuePair. Například:

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

Nahraďte kód Program.cs a znovu spusťte aplikaci a čítače dotnet jako předtím:

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

Čítače Dotnet teď zobrazují základní kategorizaci:

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    

Pro ObservableCounter a ObservableGauge lze označení měření poskytnout ve zpětném volání předaném konstruktoru:

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

Při spuštění nástroje dotnet-counters, jako předtím, je výsledek:

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    

Osvědčené postupy

  • I když rozhraní API umožňuje použití libovolného objektu jako hodnoty značky, číselné typy a řetězce jsou očekávány nástroji pro sběr dat. Jiné typy mohou nebo nemusí být podporovány daným nástrojem kolekce.

  • Názvy značek doporučujeme použít pokyny pro pojmenování OpenTelemetry, které používají malá písmena s tečkovanými názvy s znaky _k oddělení více slov ve stejném prvku. Pokud se názvy značek opakovaně používají v různých metrikách nebo jiných záznamech telemetrie, měly by mít stejný význam a sadu právních hodnot všude, kde se používají.

    Příklady názvů značek:

    • customer.country
    • store.payment_method
    • store.purchase_result
  • Dávejte pozor na to, aby se v praxi nepoužívaly velmi velké nebo neomezené kombinace hodnot značek. I když implementace rozhraní .NET API to zvládne, nástroje pro sběr pravděpodobně přidělí úložiště pro data metrik spojených s každou kombinací značek, což může být velmi objemné. Je například v pořádku, pokud má HatCo 10 různých barev klobouků a 25 velikostí klobouků, což je až 10*25=250 celkových prodejů, které je třeba sledovat. Pokud však HatCo přidá třetí značku, což je ID zákazníka pro prodej, a prodává 100 milionům zákazníků po celém světě, je nyní pravděpodobné, že se budou zaznamenávat miliardy různých kombinací značek. Většina nástrojů pro sběr metrik buď zahodí data, aby zůstala v mezích technických limitů, nebo mohou vzniknout vysoké finanční náklady na pokrytí úložiště a zpracování dat. Implementace každého sběrného nástroje určí jeho limity, ale pro jeden nástroj bude pravděpodobně bezpečné mít méně než 1 000 kombinací. Cokoli nad 1000 kombinací bude vyžadovat, aby nástroj kolekce použil filtrování nebo byl navržen tak, aby fungoval ve velkém měřítku. Implementace histogramů používají mnohem více paměti než jiné metriky, takže bezpečné limity můžou být 10 až 100krát nižší. Pokud očekáváte velký počet jedinečných kombinací značek, pak protokoly, transakční databáze nebo systémy pro zpracování velkých objemů dat mohou být vhodnější řešení pro provoz v potřebném měřítku.

  • U nástrojů, které budou mít velmi velký počet kombinací značek, preferujte použití menšího typu úložiště, aby se snížila režie na paměť. Například uložení short pro Counter<short> zabírá pouze 2 bajty na kombinaci značek, zatímco double pro Counter<double> zabírá 8 bajtů na kombinaci značek.

  • Nástroje pro sběr dat by měly být optimalizovány pro kód, který specifikuje stejný soubor názvů značek ve stejném pořadí při každém volání záznamu měření na stejném přístroji. Pro vysoce výkonný kód, který potřebuje volat Add a Record často, raději pro každé volání použijte stejnou sekvenci názvů značek.

  • Rozhraní .NET API je optimalizováno tak, aby bylo bez alokací pro volání Add a Record se třemi nebo méně značkami zadanými jednotlivě. Pokud se chcete vyhnout přidělování s větším počtem značek, použijte TagList. Obecně platí, že režie na výkon těchto volání se zvyšuje, protože se používají více značek.

Poznámka

OpenTelemetry označuje značky jako atributy. Jedná se o dva různé názvy pro stejnou funkci.

Použití rad k přizpůsobení nástrojů Histogramu

Při použití histogramů je odpovědností nástroje nebo knihovny, která shromažďuje data, aby se rozhodla, jak nejlépe znázorňovat rozdělení zaznamenaných hodnot. Běžnou strategií (a výchozí režim při použití OpenTelemetry) je rozdělit rozsah možných hodnot do dílčích oblastí označovaných jako kontejnery a hlásit, kolik zaznamenaných hodnot bylo v každém kontejneru. Nástroj může například rozdělit čísla do tří kbelíků, těch menších než 1, těch mezi 1–10 a těch větších než 10. Pokud vaše aplikace zaznamenala hodnoty 0,5, 6, 0,1, 12, pak by byly dva datové body v prvním intervalu, jeden ve druhém a jeden ve třetím.

Nástroj nebo knihovna, která shromažďuje data Histogramu, zodpovídá za definování kontejnerů, které bude používat. Výchozí konfigurace kontejneru při použití OpenTelemetry je: [ 0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000 ].

Výchozí hodnoty nemusí vést k nejlepší členitosti každého histogramu. Například doby požadavků kratší než sekunda spadají do kategorie 0.

Nástroj nebo knihovna, které shromažďují data histogramu, můžou nabízet mechanismy, které uživatelům umožní přizpůsobit konfiguraci kontejneru. OpenTelemetry například definujerozhraní API View API . To ale vyžaduje akci koncového uživatele a je zodpovědností uživatele pochopit distribuci dat dostatečně dobře, aby zvolil správné kontejnery.

Pro vylepšení prostředí verze 9.0.0 balíčku System.Diagnostics.DiagnosticSource zavedla rozhraní API (InstrumentAdvice<T>).

Rozhraní API InstrumentAdvice mohou autoři instrumentace použít k určení doporučené sady výchozích hranic intervalů pro daný histogram. Nástroj nebo knihovna, které shromažďují data Histogramu, se pak můžou rozhodnout tyto hodnoty použít při konfiguraci agregace, což vede k plynulejšímu prostředí onboardingu pro uživatele. To je podporováno v OpenTelemetry .NET SDK od verze 1.10.0.

Důležitý

Obecně platí, že více kbelíků povede k přesnějším datům pro daný histogram, avšak každý kbelík vyžaduje paměť k uložení agregovaných podrobností, a při zpracování měření je nákladné na výpočetní výkon najít správný kbelík. Je důležité pochopit kompromisy mezi přesností a spotřebou procesoru a paměti při výběru počtu kbelíků, které doporučujeme prostřednictvím rozhraní API InstrumentAdvice.

Následující kód ukazuje příklad použití rozhraní API InstrumentAdvice k nastavení doporučených výchozích kontejnerů.

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

Další informace

Další podrobnosti o explicitních kbelíkových histogramech v OpenTelemetry viz:

Testování vlastních metrik

Je možné otestovat všechny vlastní metriky, které přidáte pomocí MetricCollector<T>. Tento typ usnadňuje zaznamenání měření z konkrétních nástrojů a ověření, zda byly hodnoty správné.

Testování pomocí injektáže závislostí

Následující kód ukazuje příklad testovacího případu pro komponenty kódu, které používají injektáž závislostí a 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();
    }
}

Každý objekt MetricCollector zaznamenává všechna měření pro jeden instrument. Pokud potřebujete ověřit měření z více nástrojů, vytvořte pro každou z nich jeden MetricCollector.

Testování bez injektáže závislostí

Je také možné otestovat kód, který používá sdílený globální objekt měřiče ve statickém poli, ale ujistěte se, že tyto testy nejsou nakonfigurované tak, aby se spouštěly paralelně. Vzhledem k tomu, že se sdílí objekt Měřič, bude MetricCollector v jednom testu sledovat měření vytvořená z jiných testů spuštěných paralelně.

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