Instrumentation

Creating Metrics - .NET
How to add new metrics to a .NET library or application
https://learn.microsoft.com/en-us/dotnet/core/diagnostics/metrics-instrumentation

Recording of the metrics and emitting them from the application side.

dotnet package add System.Diagnostics.DiagnosticSource

Basic

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

class Program
{
    static Meter s_meter = new Meter("HatCo.Store"); // normally namespace
    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 second that sells 4 hats
            Thread.Sleep(1000);
            s_hatsSold.Add(4);
        }
    }
}

Best Practices

View metrics

Install the view metrics tool

dotnet tool update -g dotnet-counters

Then when the app is running, in a separate terminal run this

// get the name (second column) of the running service using the below
dotnet-counters ls

dotnet-counters monitor -n NAME --counters METER.NAME

DI Example

1. Inject the Meter

Add the metrics.

Program.cs

builder.Services.AddMetrics();
builder.Services.AddSingleton<RabbitMetrics>();

2. Metric Object

using System.Diagnostics.Metrics;

public sealed class RabbitMetrics
{
    private readonly Counter<long> _messagesFailed;

    public RabbitMetrics(IMeterFactory meterFactory)
    {
        var meter = meterFactory.Create("My.ServiceName.RabbitMq");

        _messagesFailed = meter.CreateCounter<long>(
            name: "my.service_name.rabbitmq.messages_failed",
            unit: "Count",
            description: "Messages that resulted in a failed state");
    

    public void MessageFailed()
    {
        _messagesFailed.Add(1);
    }
}

3. Use Metric Object

rabbitMetrics.MessageFailed();

Instruments

Types

Note: that T must be a number of some kind.

Note: the observables expect you to manage the value, and the call back just gets the value on request

TypeDescriptionHow to updateWhen to use
CounterTracks an incremental value (only increasing)Add(myNumber)How many so far?
ObservableCounter Manages a total rather than a changing value value must increase; your code owns the totalUses call backsHow many so far?
UpDownCounter Tracks an increasing or decreasing valueAdd(myNumber)How much? (based off increase and decrease)
ObservableUpDownCounterManages a total rather than a changing value value can increase or decrease; your code owns the totalUses call backsHow much? (based off increase and decrease)
GuageRecords a value at some point in timeRecord(myValue)What is the value right now?
Current queue depth
Number of threads
ObservableGuageRecords a value at some point in time; reports only when the metrics tool asks for it; your code owns the totalUses call backsWhat is the value right now?
Current queue depth
Number of threads
HistogramYou provide values, and those values are represented as counts for various buckets. For example one might represent a percentile.Record(myValue)What is the distribution?
Normally timings

Descriptions & Units

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

The naming convention is actually important for the unit. We should follow UCUM naming convention.

Here is how it works:

MeaningRecommended unit styleUnit
SecondsUCUM abbreviations
MillisecondsUCUM abbreviationms
BytesUCUM abbreviationBy
Count of specific thingsUCUM annotation{hat}, {message}, {request}
Unitless ratio/countUnitless

Histograms

Histograms divide data into buckets. This is the job of the data collection tool. However the user might not know what buckets are reasonable. Hence you can provide advice on what bucket sizes there should be. Do this using InstrumentAdvice.

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