Skip to content

Instantly share code, notes, and snippets.

@noahfalk
Created February 5, 2023 03:37
Show Gist options
  • Save noahfalk/0e10f4a091dbec68595ff0e2ec0d3260 to your computer and use it in GitHub Desktop.
Save noahfalk/0e10f4a091dbec68595ff0e2ec0d3260 to your computer and use it in GitHub Desktop.
Use a Meter in DI with Unit Testing
using Microsoft.Extensions.DependencyInjection;
using System.Diagnostics;
using System.Diagnostics.Metrics;
// Normally you would run the test code below with a UnitTest runner like XUnit or NUnit
// However this lets us verify it works without needing to include any other tools or
// libraries. We're also doing it repeatedly on 16 different threads to demonstrate there
// no concurrency issue here.
List<Task> tasks = new List<Task>();
for(int i = 0; i < 16; i++)
{
tasks.Add(Task.Run(() =>
{
for (int j = 0; j < 1000; j++)
{
AppCodeRecordsFooMetrics();
}
}));
}
Task.WaitAll(tasks.ToArray());
Console.WriteLine("All testing done!");
// Normally this method would be attributed with [Fact] or whatever attribute your unit test
// runner of choice uses
void AppCodeRecordsFooMetrics()
{
//Arrange
ServiceCollection services = new ServiceCollection();
services.AddSingleton<Instrumentation>();
services.AddSingleton<AppCode>();
ServiceProvider sp = services.BuildServiceProvider();
Instrumentation instrumentation = sp.GetService<Instrumentation>();
AppCode appCode = sp.GetService<AppCode>();
using InstrumentRecorder<int> recorder = new InstrumentRecorder<int>(instrumentation.FooCounter);
// Act
appCode.DoThings();
// Assert
Debug.Assert(recorder.Measurements.Count == 2);
Debug.Assert(recorder.Measurements[0].Value == 7);
Debug.Assert(recorder.Measurements[1].Value == 5);
}
// This is a small test helper that records data that was passed to an Instrument
// It can be shared by all test cases that are validating metrics data
class InstrumentRecorder<T> : IDisposable where T : struct
{
MeterListener _listener = new MeterListener();
public List<Measurement<T>> Measurements = new();
public InstrumentRecorder(Instrument i)
{
_listener.SetMeasurementEventCallback<T>(MeasurementHandler);
_listener.EnableMeasurementEvents(i);
_listener.Start();
}
private void MeasurementHandler(Instrument instrument, T measurement, ReadOnlySpan<KeyValuePair<string, object?>> tags, object? state) =>
Measurements.Add(new Measurement<T>(measurement, tags));
public void Dispose() => _listener.Dispose();
}
/****** Below here is our pretend product code ****/
class Instrumentation : IDisposable
{
public Instrumentation()
{
Meter = new Meter("Company.Product");
FooCounter = Meter.CreateCounter<int>("foo", "fooUnits");
}
public Meter Meter { get; }
public Counter<int> FooCounter { get; }
public void Dispose()
{
Meter.Dispose();
}
}
class AppCode
{
Instrumentation _instrumentation;
public AppCode(Instrumentation instrumentation) { _instrumentation = instrumentation; }
public void DoThings()
{
_instrumentation.FooCounter.Add(7);
_instrumentation.FooCounter.Add(5);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment