Condividi tramite


Metriche generate dalla sorgente con tag fortemente tipizzati

Le applicazioni .NET moderne possono acquisire metriche usando l'API System.Diagnostics.Metrics . Queste metriche includono spesso un contesto aggiuntivo sotto forma di coppie chiave-valore denominate tag (talvolta definite dimensioni nei sistemi di telemetria). Questo articolo illustra come usare un generatore di origine in fase di compilazione per definire tag di metriche fortemente tipizzati (TagNames) e tipi di metriche e metodi di registrazione. Usando tag fortemente tipizzato, si elimina il codice boilerplate ripetitivo e si garantisce che le metriche correlate condividono lo stesso set di nomi di tag con sicurezza in fase di compilazione. Il vantaggio principale di questo approccio è la maggiore produttività degli sviluppatori e la sicurezza dei tipi.

Annotazioni

Nel contesto delle metriche, un tag viene talvolta definito anche "dimensione". Questo articolo usa "tag" per maggiore chiarezza e coerenza con la terminologia delle metriche .NET.

Inizia subito

Per iniziare, installare il 📦 pacchetto NuGet Microsoft.Extensions.Telemetry.Abstractions :

dotnet add package Microsoft.Extensions.Telemetry.Abstractions

Per altre informazioni, vedere dotnet add package o Gestire le dipendenze dei pacchetti nelle applicazioni .NET.

Impostazioni predefinite e personalizzazione del nome del tag

Per impostazione predefinita, il generatore di origine deriva i nomi dei tag delle metriche dai nomi dei campi e delle proprietà della classe tag. In altre parole, ogni campo pubblico o proprietà nell'oggetto tag fortemente tipizzato diventa un nome di tag per impostazione predefinita. È possibile eseguire l'override di questa operazione usando in TagNameAttribute un campo o una proprietà per specificare un nome di tag personalizzato. Negli esempi seguenti verranno visualizzati entrambi gli approcci in azione.

Esempio 1: Metrica di base con un singolo tag

L'esempio seguente illustra una semplice metrica del contatore con un tag. In questo scenario si vuole contare il numero di richieste elaborate e classificarle in base a un Region tag:

public struct RequestTags
{
    public string Region { get; set; }
}

public static partial class MyMetrics
{
    [Counter<int>(typeof(RequestTags))]
    public static partial RequestCount CreateRequestCount(Meter meter);
}

Nel codice precedente, RequestTags è uno struct tag fortemente tipizzato con una singola proprietà Region. Il CreateRequestCount metodo è contrassegnato con CounterAttribute<T>, in cui T è un int che indica che genera uno Counter strumento che tiene traccia dei int valori. L'attributo fa riferimento a typeof(RequestTags), ovvero il contatore usa i tag definiti in RequestTags durante la registrazione delle metriche. Il generatore di codice sorgente produce una classe strumento fortemente tipizzata (denominata RequestCount) con un metodo Add che accetta un valore intero e un oggetto RequestTags.

Per usare la metrica generata, creare una misura con Meter e registrare le misurazioni come illustrato di seguito.

Meter meter = new("MyCompany.MyApp", "1.0");
RequestCount requestCountMetric = MyMetrics.CreateRequestCount(meter);

// Create a tag object with the relevant tag value
var tags = new RequestTags { Region = "NorthAmerica" };

// Record a metric value with the associated tag
requestCountMetric.Add(1, tags);

In questo esempio di utilizzo la chiamata MyMetrics.CreateRequestCount(meter) crea uno strumento contatore (tramite ) Metere restituisce un RequestCount oggetto metrica. Quando si chiama requestCountMetric.Add(1, tags), il sistema delle metriche registra un conteggio di 1 associato al tag Region="NorthAmerica". È possibile riutilizzare l'oggetto RequestTags o crearne di nuovi per registrare i conteggi per aree diverse e il nome Region del tag verrà applicato in modo coerente a ogni misura.

Esempio 2: Metrica con oggetti tag annidati

Per scenari più complessi, è possibile definire classi di tag che includono più tag, oggetti annidati o persino proprietà ereditate. Ciò consente a un gruppo di metriche correlate di condividere efficacemente un set comune di tag. Nell'esempio seguente si definisce un set di classi di tag e le si usa per tre metriche diverse:

using Microsoft.Extensions.Diagnostics.Metrics;

namespace MetricsGen;

public class MetricTags : MetricParentTags
{
    [TagName("Dim1DimensionName")]
    public string? Dim1;                      // custom tag name via attribute
    public Operations Operation { get; set; } // tag name defaults to "Operation"
    public MetricChildTags? ChildTagsObject { get; set; }
}

public enum Operations
{
    Unknown = 0,
    Operation1 = 1,
}

public class MetricParentTags
{
    [TagName("DimensionNameOfParentOperation")]
    public string? ParentOperationName { get; set; }  // custom tag name via attribute
    public MetricTagsStruct ChildTagsStruct { get; set; }
}

public class MetricChildTags
{
    public string? Dim2 { get; set; }  // tag name defaults to "Dim2"
}

public struct MetricTagsStruct
{
    public string Dim3 { get; set; }   // tag name defaults to "Dim3"
}

Il codice precedente definisce l'ereditarietà delle metriche e le forme oggetto. Il codice seguente illustra come usare queste forme con il generatore, come illustrato nella Metric classe :

using System.Diagnostics.Metrics;
using Microsoft.Extensions.Diagnostics.Metrics;

public static partial class Metric
{
    [Histogram<long>(typeof(MetricTags))]
    public static partial Latency CreateLatency(Meter meter);

    [Counter<long>(typeof(MetricTags))]
    public static partial TotalCount CreateTotalCount(Meter meter);

    [Counter<int>(typeof(MetricTags))]
    public static partial TotalFailures CreateTotalFailures(Meter meter);
}

In questo esempio è MetricTags una classe di tag che eredita da MetricParentTags e contiene anche un oggetto tag annidato (MetricChildTags) e uno struct annidato (MetricTagsStruct). Le proprietà dei tag illustrano sia i nomi di tag predefiniti che personalizzati:

  • Il Dim1 campo in MetricTags ha un [TagName("Dim1DimensionName")] attributo, quindi il nome del tag sarà "Dim1DimensionName".
  • Per impostazione predefinita, la Operation proprietà non ha alcun attributo, quindi il nome del tag è "Operation".
  • In MetricParentTags la proprietà ParentOperationName viene sovrascritta con un nome "DimensionNameOfParentOperation" di tag personalizzato.
  • La classe nidificata MetricChildTags definisce una proprietà Dim2 (nessun attributo, nome tag "Dim2").
  • Lo MetricTagsStruct struct definisce un Dim3 campo (nome "Dim3"tag ).

Tutte e tre le definizioni CreateLatencydelle metriche , CreateTotalCounte CreateTotalFailures usano MetricTags come tipo di oggetto tag. Ciò significa che i tipi di metrica generati (Latency, TotalCounte TotalFailures) si aspettano tutti un'istanza MetricTags durante la registrazione dei dati. Ognuna di queste metriche avrà lo stesso set di nomi di tag:Dim1DimensionName , Operation, Dim2Dim3, e DimensionNameOfParentOperation.

Il codice seguente illustra come creare e usare queste metriche in una classe :

internal class MyClass
{
    private readonly Latency _latencyMetric;
    private readonly TotalCount _totalCountMetric;
    private readonly TotalFailures _totalFailuresMetric;

    public MyClass(Meter meter)
    {
        // Create metric instances using the source-generated factory methods
        _latencyMetric = Metric.CreateLatency(meter);
        _totalCountMetric = Metric.CreateTotalCount(meter);
        _totalFailuresMetric = Metric.CreateTotalFailures(meter);
    }

    public void DoWork()
    {
        var startingTimestamp = Stopwatch.GetTimestamp();
        bool requestSuccessful = true;
        // Perform some operation to measure
        var elapsedTime = Stopwatch.GetElapsedTime(startingTimestamp);

        // Create a tag object with values for all tags
        var tags = new MetricTags
        {
            Dim1 = "Dim1Value",
            Operation = Operations.Operation1,
            ParentOperationName = "ParentOpValue",
            ChildTagsObject = new MetricChildTags
            {
                Dim2 = "Dim2Value",
            },
            ChildTagsStruct = new MetricTagsStruct
            {
                Dim3 = "Dim3Value"
            }
        };

        // Record the metric values with the associated tags
        _latencyMetric.Record(elapsedTime.ElapsedMilliseconds, tags);
        _totalCountMetric.Add(1, tags);
        if (!requestSuccessful)
        {
            _totalFailuresMetric.Add(1, tags);
        }
    }
}

Nel metodo precedente MyClass.DoWork , un MetricTags oggetto viene popolato con valori per ogni tag. Questo singolo tags oggetto viene quindi passato a tutti e tre gli strumenti durante la registrazione dei dati. La Latency metrica (istogramma) registra il tempo trascorso e entrambi i contatori (TotalCount e TotalFailures) registrano i conteggi delle occorrenze. Poiché tutte le metriche condividono lo stesso tipo di oggetto tag, i tag (Dim1DimensionName, Operation, Dim2Dim3, , DimensionNameOfParentOperation) sono presenti in ogni misura.

Specifica delle unità

A partire da .NET 10.2, è possibile specificare facoltativamente un'unità di misura per le metriche usando il Unit parametro . Ciò consente di fornire contesto sulle misure delle metriche, ad esempio "secondi", "byte" e "richieste". L'unità viene passata al componente sottostante Meter durante la creazione dello strumento.

Il codice seguente illustra come usare il generatore con tipi primitivi con unità specificate:

public static partial class Metric
{
    [Histogram<long>(typeof(MetricTags), Unit = "ms")]
    public static partial Latency CreateLatency(Meter meter);

    [Counter<long>(typeof(MetricTags), Unit = "requests")]
    public static partial TotalCount CreateTotalCount(Meter meter);

    [Counter<int>(typeof(MetricTags), Unit = "failures")]
    public static partial TotalFailures CreateTotalFailures(Meter meter);
}

Considerazioni sulle prestazioni

L'uso di tag fortemente tipizzato tramite la generazione del codice sorgente non comporta alcun costo aggiuntivo rispetto all'uso diretto delle metriche. Se è necessario ridurre ulteriormente le allocazioni per le metriche a frequenza molto elevata, valutare la possibilità di definire l'oggetto tag come (structtipo valore) anziché come .class L'uso di un struct come oggetto tag può evitare allocazioni di heap durante la registrazione delle metriche, poiché i tag verrebbero passati per valore.

Requisiti dei metodi delle metriche generati

Quando si definiscono i metodi di fabbrica delle metriche (i metodi parziali decorati con [Counter], [Histogram] e così via), il generatore del codice sorgente impone alcuni requisiti:

  • Ogni metodo deve essere public static partial (affinché il generatore di origine fornisca l'implementazione).
  • Il tipo restituito di ogni metodo parziale deve essere univoco (in modo che il generatore possa creare un tipo denominato in modo univoco per la metrica).
  • Il nome del metodo non deve iniziare con un carattere di sottolineatura (_) e i nomi dei parametri non devono iniziare con un carattere di sottolineatura.
  • Il primo parametro deve essere un oggetto Meter (si tratta dell'istanza del contatore usata per creare lo strumento sottostante).
  • I metodi non possono essere generici e non possono avere parametri generici.
  • Le proprietà del tag nella classe tag possono essere di tipo string o enum. Per altri tipi (ad esempio, bool o tipi numerici), convertire il valore in una stringa prima di assegnarlo all'oggetto tag.

L'adesione a questi requisiti garantisce che il generatore di origine possa produrre correttamente i tipi e i metodi delle metriche.

Vedere anche