[API Proposal]: InstrumentRecorder - notification of new measurements #86783
Closed
Description
Background and motivation
InstrumentRecorder
is an API new in .NET 8 to make testing metrics easier:
var instrumentRecorder = new InstrumentRecorder<double>(meterRegistry, "Microsoft.AspNetCore.Hosting", "request-duration");
var response = await httpClient.GetAsync("/");
var measurements = instrumentRecorder.GetMeasurements();
// Assert contents of measurements
However, there are some cases where there are race conditions because measurements happen on a different thread. For example, with the request-duration
counter above, the server can return the failing response to the client before the metric is recorded. GetMeasurements
may or may not have an item. The test is flaky.
It would be useful if someone using InstrumentRecorder
could get notifications of new measurements.
API Proposal
namespace System.Diaganostics.Metrics;
public class InstumentRecorder<T>
{
// Wait for any number of measurements.
public Task<IEnumerable<Measurement<T>> WaitForMeasurementsAsync(bool clear = false)
=> WaitForMeasurementsAsync(minimumCount: 1, clear);
// Wait for measurements greater than count.
public Task<IEnumerable<Measurement<T>> WaitForMeasurementsAsync(int minimumCount, bool clear = false);
}
WaitForMeasurementsAsync
returns when one or more measurements are available. If there are already measurements, the method returns immediately.
Random thoughts:
- It might be worth having a count overload on the method, allowing someone to wait for an expected number of measurements, e.g.
WaitForMeasurementsAsync(minimumCount: 5)
. Edit: Added above. - Are parallel calls to
WaitForMeasurementsAsync
allowed? What happens if there are parallel calls toWaitForMeasurementsAsync
, and they haveclear: true
? - If someone wants to wait multiple times, they must clear the measurements (or increase the wait count). The example below shows this.
API Usage
var instrumentRecorder = new InstrumentRecorder<double>(meterRegistry, "Microsoft.AspNetCore.Hosting", "request-duration");
var response1 = await httpClient.GetAsync("/");
var measurements1 = await instrumentRecorder.WaitForMeasurementsAsync(clear: true);
// Assert contents of measurements
var response2 = await httpClient.GetAsync("/");
var measurements2 = await instrumentRecorder.WaitForMeasurementsAsync(clear: true);
// Assert contents of measurements
Alternative Designs
An API to register a callback that executes for each measurement:
var tcs = new TaskCompletionSource();
var instrumentRecorder = new InstrumentRecorder<double>(meterRegistry, "Microsoft.AspNetCore.Hosting", "request-duration");
instrumentRecorder.Register(m =>
{
tcs.TrySetResult();
});
// Do stuff
await tcs.Task;
var measurements = await instrumentRecorder.GetMeasurements();
// Assert contents of measurements
Risks
No response