Instrumentation
Creating Metrics - .NET
How to add new metrics to a .NET library or application

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);
}
}
}
- You create a meter object
- That meter object contains metrics, that you record
- In the example above you add a 4 every second to the metric
Best Practices
- You DI singletons when DI is available. Otherwise use a static.
- Naming should:
- follow this naming pattern
contoso.ticket_queue.duration
- Namespaces typically are a good naming choice
- The API is thread safe so don’t worry
- follow this naming pattern
View metrics
Install the view metrics tool
dotnet tool update -g dotnet-countersThen 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.NAMEDI 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
| Type | Description | How to update | When to use |
Counter | Tracks 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 total | Uses call backs | How many so far? |
UpDownCounter | Tracks an increasing or decreasing value | Add(myNumber) | How much? (based off increase and decrease) |
ObservableUpDownCounter | Manages a total rather than a changing value value can increase or decrease; your code owns the total | Uses call backs | How much? (based off increase and decrease) |
Guage | Records a value at some point in time | Record(myValue) | What is the value right now? Current queue depth Number of threads |
ObservableGuage | Records a value at some point in time; reports only when the metrics tool asks for it; your code owns the total | Uses call backs | What is the value right now? Current queue depth Number of threads |
Histogram | You 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:
| Meaning | Recommended unit style | Unit |
|---|---|---|
| Seconds | UCUM abbreviation | s |
| Milliseconds | UCUM abbreviation | ms |
| Bytes | UCUM abbreviation | By |
| Count of specific things | UCUM annotation | {hat}, {message}, {request} |
| Unitless ratio/count | Unitless |
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] });