-
-
Save noahfalk/0e10f4a091dbec68595ff0e2ec0d3260 to your computer and use it in GitHub Desktop.
Use a Meter in DI with Unit Testing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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