Skip to content

Commit

Permalink
MetricCollector improvements.
Browse files Browse the repository at this point in the history
- Add a MetricCollector constructor that consumes an Instrument to
make it easy to deal with simple single-instrument tests.

- Add Count properties to MetricCollector and MetricValueHolder and
introduce corresponding DebuggerDisplay attributes to improve debugger
experience.
  • Loading branch information
Martin Taillefer committed Jun 8, 2023
1 parent 4ae7f49 commit 7734f78
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Metrics;
using System.Linq;
Expand All @@ -19,6 +20,7 @@ namespace Microsoft.Extensions.Telemetry.Testing.Metering;
/// This type has been designed to be used only for testing purposes.
/// </remarks>
[Experimental]
[DebuggerDisplay("Count = {Count}")]
public partial class MetricCollector : IDisposable
{
private readonly MeterListener _listener;
Expand Down Expand Up @@ -78,6 +80,20 @@ public MetricCollector(Meter meter, TimeProvider? timeProvider = null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="MetricCollector"/> class.
/// </summary>
/// <param name="instrument">The <see cref="Instrument"/> instance to capture metering data.</param>
/// <param name="timeProvider">The <see cref="TimeProvider"/> instance.</param>
/// <remarks>
/// This constructor is applicable for the scenario when metering data
/// generated by a specific <see cref="Instrument"/> instance is to be captured.
/// </remarks>
public MetricCollector(Instrument instrument, TimeProvider? timeProvider = null)
: this(new[] { Throw.IfNull(instrument).Name }, instrument.Meter, timeProvider, true)
{
}

private MetricCollector(IEnumerable<string>? meterNames, Meter? meter, TimeProvider? timeProvider, bool applyMeterFiltering)
{
if (applyMeterFiltering)
Expand Down Expand Up @@ -127,6 +143,16 @@ public void Clear()
ClearValues(_allObservableUpDownCounters);
}

/// <summary>
/// Gets the total number of measurements held by the collector.
/// </summary>
public int Count => CountValues(_allCounters)
+ CountValues(_allHistograms)
+ CountValues(_allUpDownCounters)
+ CountValues(_allObservableCounters)
+ CountValues(_allObservableGauges)
+ CountValues(_allObservableUpDownCounters);

/// <summary>
/// Gets the object containing all the captured metering data that has been recorded by a counter instrument.
/// </summary>
Expand Down Expand Up @@ -319,7 +345,7 @@ public void Dispose()
}

/// <summary>
/// Disponse the el.
/// Dispose the el.
/// </summary>
/// <param name="disposing">Disposing.</param>
protected virtual void Dispose(bool disposing)
Expand Down Expand Up @@ -352,6 +378,32 @@ private static void ClearValuesOf<T>(Dictionary<Type, object> instrumentValues)
}
}

private static int CountValues(Dictionary<Type, object> instrumentValues)
{
return
CountValuesOf<byte>(instrumentValues)
+ CountValuesOf<short>(instrumentValues)
+ CountValuesOf<int>(instrumentValues)
+ CountValuesOf<long>(instrumentValues)
+ CountValuesOf<float>(instrumentValues)
+ CountValuesOf<double>(instrumentValues)
+ CountValuesOf<decimal>(instrumentValues);
}

private static int CountValuesOf<T>(Dictionary<Type, object> instrumentValues)
where T : struct
{
var valuesDictionary = (ConcurrentDictionary<string, MetricValuesHolder<T>>)instrumentValues[typeof(T)];

int count = 0;
foreach (var kvp in valuesDictionary)
{
count += kvp.Value.Count;
}

return count;
}

private static MetricValuesHolder<T>? GetInstrumentCapturedData<T>(Dictionary<Type, object> allInstrumentsValues, string instrumentName)
where T : struct
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
Expand All @@ -16,6 +17,7 @@ namespace Microsoft.Extensions.Telemetry.Testing.Metering;
/// </summary>
/// <typeparam name="T">The type of metric measurement value.</typeparam>
[Experimental]
[DebuggerDisplay("Count = {Count}, LatestWrittenValue = {LatestWrittenValue}")]
public sealed class MetricValuesHolder<T>
where T : struct
{
Expand Down Expand Up @@ -55,10 +57,15 @@ internal MetricValuesHolder(TimeProvider timeProvider, AggregationType aggregati
public string MetricName { get; }

/// <summary>
/// Gets all metric values recorded by the instrument.
/// Gets all metric values recorded by the metric's instruments.
/// </summary>
public IReadOnlyCollection<MetricValue<T>> AllValues => _values;

/// <summary>
/// Gets the total number of values held by this instance.
/// </summary>
public int Count => _values.Count;

/// <summary>
/// Gets the latest recorded metric measurement value.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ public void Ctor_Throws_WhenInvalidArguments()
{
Assert.Throws<ArgumentNullException>(() => new MetricCollector((Meter)null!));
Assert.Throws<ArgumentNullException>(() => new MetricCollector((IEnumerable<string>)null!));
Assert.Throws<ArgumentNullException>(() => new MetricCollector((Instrument)null!));
}

[Fact]
public void Mesuarements_AreFilteredOut_WithMeterNameFilter()
public void Measurements_AreFilteredOut_WithMeterNameFilter()
{
using var meter1 = new Meter(Guid.NewGuid().ToString());
using var meter2 = new Meter(Guid.NewGuid().ToString());
Expand Down Expand Up @@ -53,7 +54,32 @@ public void Mesuarements_AreFilteredOut_WithMeterNameFilter()
}

[Fact]
public void Mesuarements_AreFilteredOut_WithMeterFilter()
public void Measurements_SingleInstrument()
{
var meterName = Guid.NewGuid().ToString();
using var meter1 = new Meter(meterName);
using var meter2 = new Meter(meterName);

const int IntValue1 = 123459;
const int IntValue2 = 987654;

var ctr1 = meter1.CreateCounter<int>("int_counter1");
var ctr2 = meter2.CreateCounter<int>("int_counter2");

using var metricCollector = new MetricCollector(ctr2);

ctr1.Add(IntValue1);
ctr2.Add(IntValue2);

var x = metricCollector.GetAllCounters<int>();

// Single measurement is captured
Assert.Equal(1, metricCollector.Count);
Assert.Equal(IntValue2, metricCollector.GetCounterValue<int>("int_counter2"));
}

[Fact]
public void Measurements_AreFilteredOut_WithMeterFilter()
{
var meterName = Guid.NewGuid().ToString();
using var meter1 = new Meter(meterName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,28 @@ public void MetricName_MatchesInstrumentName()
Assert.Equal(updownCounterValuesHolder.MetricName, updownCounter.Name);
}

[Fact]
public void Count()
{
using var meter = new Meter(Guid.NewGuid().ToString());
using var metricCollector = new MetricCollector(meter);

var counter = meter.CreateCounter<int>(Guid.NewGuid().ToString());
var counterValuesHolder = metricCollector.GetCounterValues<int>(counter.Name);
counter.Add(1);
Assert.Equal(1, counterValuesHolder!.Count);

var histogram = meter.CreateHistogram<int>(Guid.NewGuid().ToString());
var histgramValuesHolder = metricCollector.GetHistogramValues<int>(histogram.Name);
histogram.Record(1);
histogram.Record(1);
Assert.Equal(2, histgramValuesHolder!.Count);

var updownCounter = meter.CreateUpDownCounter<int>(Guid.NewGuid().ToString());
var updownCounterValuesHolder = metricCollector.GetUpDownCounterValues<int>(updownCounter.Name);
Assert.Equal(0, updownCounterValuesHolder!.Count);
}

[Fact]
public void LastWrittenValue_ReturnsTheLatestMeasurementValue()
{
Expand Down

0 comments on commit 7734f78

Please sign in to comment.