Metrics
.NET applications can be instrumented using the System.Diagnostics.Metrics
APIs to track important metrics. Some metrics are included in standard .NET libraries, but you may want to add new custom metrics that are relevant to your applications and libraries.
You can use a Metalama aspect to add metrics that are relevant to the execution of a method, for instance:
- number of executions,
- number of failures,
- total execution time
Example
You can add metrics to code using custom attributes:
public class HatShop
{
private int _executionCount;
[MeasureExecutionCount]
[MeasureExecutionTime]
[MeasureExceptionCount]
public void PlaceOrder()
{
this._executionCount++;
if ( this._executionCount % 10 == 0 )
{
throw new Exception();
}
else
{
Console.WriteLine( "Ordering a hat." );
}
}
}
Alternatively, if you want to add metrics to several methods (for instance, all public methods) but don’t want to add a custom attribute to each of them, you can also add the metrics using a fabric:
internal class Fabric : ProjectFabric
{
public override void AmendProject(IProjectAmender amender) =>
amender
.SelectTypes()
.Where(type => type.Accessibility == Accessibility.Public)
.SelectMany(type => type.Methods)
.Where(method => method.Accessibility == Accessibility.Public)
.AddAspectIfEligible<MeasureExecutionCountAttribute>();
}
The metric aspects will generate new classes that define the metrics. For instance, for HatShop
, it will generate HatShopMetrics
. It will also generate an IServiceCollection
extension method to add these metrics to your apps:
services.AddMetrics();
services.AddAutoGeneratedMetrics();
Show me how it works!
The aspects first generate the HatShopMetrics
class:
public class HatShopMetrics
{
internal readonly Counter<long> PlaceOrderExceptionCount;
internal readonly Counter<long> PlaceOrderExecutionCount;
internal readonly Counter<long> PlaceOrderExecutionTime;
private IMeterFactory _meterFactory;
private IMetricHost _metricHost;
public HatShopMetrics(IMeterFactory meterFactory, IMetricHost metricHost )
{
this._meterFactory = meterFactory ?? throw new System.ArgumentNullException(nameof(meterFactory));
this._metricHost = metricHost ?? throw new System.ArgumentNullException(nameof(metricHost));
var meter = _meterFactory.Create(_metricHost.ApplicationName, _metricHost.ApplicationVersion, _metricHost.Tags);
this.PlaceOrderExceptionCount = meter.CreateCounter<long>("PlaceOrder.ExceptionCount");
this.PlaceOrderExecutionCount = meter.CreateCounter<long>("PlaceOrder.ExecutionCount");
this.PlaceOrderExecutionTime = meter.CreateCounter<long>("PlaceOrder.ExecutionTime");
}
}
It then modifies the HatShop
class in two ways:
- It pulls the
HatShopMetrics
dependency, and - It instruments the target methods.
public class HatShop
{
private HatShopMetrics _hatShopMetrics;
private int _executionCount;
public HatShop(HatShopMetrics hatShopMetrics = null)
{
this._hatShopMetrics = hatShopMetrics;
}
public void PlaceOrder()
{
var timestamp = Stopwatch.GetTimestamp();
try
{
this._hatShopMetrics?.PlaceOrderExecutionCount.Add(1);
try
{
this._executionCount++;
if (this._executionCount % 10 == 0)
{
throw new Exception();
}
else
{
Console.WriteLine("Ordering a hat.");
}
}
catch
{
this._hatShopMetrics?.PlaceOrderExceptionCount.Add(1);
throw;
}
return;
}
finally
{
this._hatShopMetrics?.PlaceOrderExecutionTime.Add(Stopwatch.GetTimestamp() - timestamp);
}
}
}
We have a lot of additional code here because there are three metrics.
It also generates the IServiceCollection
extension method:
public static class MetricRegistrations
{
public static void AddAutoGeneratedMetrics(this ServiceCollection serviceCollection)
{
serviceCollection.AddSingleton(typeof(HatShopMetrics));
}
}
Resources
- Example: Metalama.Samples.Metrics