Skip to content

Commit d650352

Browse files
rajkumar-rangarajCodeBlanchreyang
authored
[sdk-metrics] Add support for .NET 9 Advice API (open-telemetry#5854)
Co-authored-by: Mikel Blanchard <[email protected]> Co-authored-by: Reiley Yang <[email protected]>
1 parent 1c02da7 commit d650352

File tree

3 files changed

+232
-1
lines changed

3 files changed

+232
-1
lines changed

src/OpenTelemetry/CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,20 @@ Notes](../../RELEASENOTES.md).
1717
`9.0.0-rc.1.24431.7`.
1818
([#5853](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5853))
1919

20+
* Added support in metrics for histogram bucket boundaries set via the .NET 9
21+
[InstrumentAdvice&lt;T&gt;](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.instrumentadvice-1)
22+
API.
23+
24+
Note: With this change explicit bucket histogram boundary resolution will
25+
apply in the following order:
26+
27+
1. View API
28+
2. Advice API
29+
3. SDK defaults
30+
31+
See [#5854](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5854)
32+
for details.
33+
2034
## 1.9.0
2135

2236
Released 2024-Jun-14

src/OpenTelemetry/Metrics/MetricStreamIdentity.cs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public MetricStreamIdentity(Instrument instrument, MetricStreamConfiguration? me
2222
this.ViewId = metricStreamConfiguration?.ViewId;
2323
this.MetricStreamName = $"{this.MeterName}.{this.MeterVersion}.{this.InstrumentName}";
2424
this.TagKeys = metricStreamConfiguration?.CopiedTagKeys;
25-
this.HistogramBucketBounds = (metricStreamConfiguration as ExplicitBucketHistogramConfiguration)?.CopiedBoundaries;
25+
this.HistogramBucketBounds = GetExplicitBucketHistogramBounds(instrument, metricStreamConfiguration);
2626
this.ExponentialHistogramMaxSize = (metricStreamConfiguration as Base2ExponentialBucketHistogramConfiguration)?.MaxSize ?? 0;
2727
this.ExponentialHistogramMaxScale = (metricStreamConfiguration as Base2ExponentialBucketHistogramConfiguration)?.MaxScale ?? 0;
2828
this.HistogramRecordMinMax = (metricStreamConfiguration as HistogramConfiguration)?.RecordMinMax ?? true;
@@ -150,6 +150,52 @@ public bool Equals(MetricStreamIdentity other)
150150

151151
public override readonly int GetHashCode() => this.hashCode;
152152

153+
private static double[]? GetExplicitBucketHistogramBounds(Instrument instrument, MetricStreamConfiguration? metricStreamConfiguration)
154+
{
155+
if (metricStreamConfiguration is ExplicitBucketHistogramConfiguration explicitBucketHistogramConfiguration
156+
&& explicitBucketHistogramConfiguration.CopiedBoundaries != null)
157+
{
158+
return explicitBucketHistogramConfiguration.CopiedBoundaries;
159+
}
160+
161+
return instrument switch
162+
{
163+
Histogram<long> longHistogram => GetExplicitBucketHistogramBoundsFromAdvice(longHistogram),
164+
Histogram<int> intHistogram => GetExplicitBucketHistogramBoundsFromAdvice(intHistogram),
165+
Histogram<short> shortHistogram => GetExplicitBucketHistogramBoundsFromAdvice(shortHistogram),
166+
Histogram<byte> byteHistogram => GetExplicitBucketHistogramBoundsFromAdvice(byteHistogram),
167+
Histogram<float> floatHistogram => GetExplicitBucketHistogramBoundsFromAdvice(floatHistogram),
168+
Histogram<double> doubleHistogram => GetExplicitBucketHistogramBoundsFromAdvice(doubleHistogram),
169+
_ => null,
170+
};
171+
}
172+
173+
private static double[]? GetExplicitBucketHistogramBoundsFromAdvice<T>(Histogram<T> histogram)
174+
where T : struct
175+
{
176+
var adviceExplicitBucketBoundaries = histogram.Advice?.HistogramBucketBoundaries;
177+
if (adviceExplicitBucketBoundaries == null)
178+
{
179+
return null;
180+
}
181+
182+
if (typeof(T) == typeof(double))
183+
{
184+
return ((IReadOnlyList<double>)adviceExplicitBucketBoundaries).ToArray();
185+
}
186+
else
187+
{
188+
double[] explicitBucketBoundaries = new double[adviceExplicitBucketBoundaries.Count];
189+
190+
for (int i = 0; i < adviceExplicitBucketBoundaries.Count; i++)
191+
{
192+
explicitBucketBoundaries[i] = Convert.ToDouble(adviceExplicitBucketBoundaries[i]);
193+
}
194+
195+
return explicitBucketBoundaries;
196+
}
197+
}
198+
153199
private static bool HistogramBoundsEqual(double[]? bounds1, double[]? bounds2)
154200
{
155201
if (ReferenceEquals(bounds1, bounds2))

test/OpenTelemetry.Tests/Metrics/MetricViewTests.cs

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,177 @@ public void ViewToProduceCustomHistogramBound()
570570
Assert.Equal(boundaries.Length + 1, actualCount);
571571
}
572572

573+
[Fact]
574+
public void HistogramWithAdviceBoundaries_HandlesAllTypes()
575+
{
576+
using var meter = new Meter(Utils.GetCurrentMethodName());
577+
var exportedItems = new List<Metric>();
578+
int counter = 0;
579+
580+
using var container = this.BuildMeterProvider(out var meterProvider, builder =>
581+
{
582+
builder.AddMeter(meter.Name);
583+
builder.AddInMemoryExporter(exportedItems);
584+
});
585+
586+
// Test cases for different histogram types
587+
var histograms = new Instrument[]
588+
{
589+
meter.CreateHistogram<long>("longHistogram", unit: null, description: null, tags: null, new() { HistogramBucketBoundaries = new List<long>() { 10, 20 } }),
590+
meter.CreateHistogram<int>("intHistogram", unit: null, description: null, tags: null, new() { HistogramBucketBoundaries = new List<int>() { 10, 20 } }),
591+
meter.CreateHistogram<short>("shortHistogram", unit: null, description: null, tags: null, new() { HistogramBucketBoundaries = new List<short>() { 10, 20 } }),
592+
meter.CreateHistogram<float>("floatHistogram", unit: null, description: null, tags: null, new() { HistogramBucketBoundaries = new List<float>() { 10.0F, 20.0F } }),
593+
meter.CreateHistogram<double>("doubleHistogram", unit: null, description: null, tags: null, new() { HistogramBucketBoundaries = new List<double>() { 10.0, 20.0 } }),
594+
};
595+
596+
foreach (var histogram in histograms)
597+
{
598+
exportedItems.Clear();
599+
600+
if (histogram is Histogram<long> longHistogram)
601+
{
602+
longHistogram.Record(-10);
603+
longHistogram.Record(9);
604+
longHistogram.Record(19);
605+
}
606+
else if (histogram is Histogram<int> intHistogram)
607+
{
608+
intHistogram.Record(-10);
609+
intHistogram.Record(9);
610+
intHistogram.Record(19);
611+
counter++;
612+
}
613+
else if (histogram is Histogram<short> shortHistogram)
614+
{
615+
shortHistogram.Record(-10);
616+
shortHistogram.Record(9);
617+
shortHistogram.Record(19);
618+
counter++;
619+
}
620+
else if (histogram is Histogram<float> floatHistogram)
621+
{
622+
floatHistogram.Record(-10.0F);
623+
floatHistogram.Record(9.0F);
624+
floatHistogram.Record(19.0F);
625+
counter++;
626+
}
627+
else if (histogram is Histogram<double> doubleHistogram)
628+
{
629+
doubleHistogram.Record(-10.0);
630+
doubleHistogram.Record(9.0);
631+
doubleHistogram.Record(19.0);
632+
counter++;
633+
}
634+
635+
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
636+
var metricCustom = exportedItems[counter];
637+
638+
List<MetricPoint> metricPointsCustom = new List<MetricPoint>();
639+
foreach (ref readonly var mp in metricCustom.GetMetricPoints())
640+
{
641+
metricPointsCustom.Add(mp);
642+
}
643+
644+
Assert.Single(metricPointsCustom);
645+
var histogramPoint = metricPointsCustom[0];
646+
647+
var count = histogramPoint.GetHistogramCount();
648+
var sum = histogramPoint.GetHistogramSum();
649+
650+
Assert.Equal(18, sum);
651+
Assert.Equal(3, count);
652+
653+
var index = 0;
654+
var actualCount = 0;
655+
long[] expectedBucketCounts = new long[] { 2, 1, 0 };
656+
657+
foreach (var histogramMeasurement in histogramPoint.GetHistogramBuckets())
658+
{
659+
Assert.Equal(expectedBucketCounts[index], histogramMeasurement.BucketCount);
660+
index++;
661+
actualCount++;
662+
}
663+
664+
Assert.Equal(3, actualCount);
665+
}
666+
}
667+
668+
[Theory]
669+
[InlineData(false)]
670+
[InlineData(true)]
671+
public void HistogramWithAdviceBoundariesSpecifiedTests(bool useViewToOverride)
672+
{
673+
using var meter = new Meter(Utils.GetCurrentMethodName());
674+
var exportedItems = new List<Metric>();
675+
IReadOnlyList<long> adviceBoundaries = new List<long>() { 5, 10, 20 };
676+
double[] viewBoundaries = new double[] { 10, 20 };
677+
678+
using var container = this.BuildMeterProvider(out var meterProvider, builder =>
679+
{
680+
builder.AddMeter(meter.Name);
681+
682+
if (useViewToOverride)
683+
{
684+
builder.AddView("MyHistogram", new ExplicitBucketHistogramConfiguration { Boundaries = viewBoundaries });
685+
}
686+
687+
builder.AddInMemoryExporter(exportedItems);
688+
});
689+
690+
var histogram = meter.CreateHistogram<long>(
691+
"MyHistogram",
692+
unit: null,
693+
description: null,
694+
tags: null,
695+
new()
696+
{
697+
HistogramBucketBoundaries = adviceBoundaries,
698+
});
699+
700+
histogram.Record(-10);
701+
histogram.Record(0);
702+
histogram.Record(1);
703+
histogram.Record(9);
704+
histogram.Record(10);
705+
histogram.Record(11);
706+
histogram.Record(19);
707+
histogram.Record(22);
708+
709+
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
710+
Assert.Single(exportedItems);
711+
var metricCustom = exportedItems[0];
712+
713+
Assert.Equal("MyHistogram", metricCustom.Name);
714+
715+
List<MetricPoint> metricPointsCustom = new List<MetricPoint>();
716+
foreach (ref readonly var mp in metricCustom.GetMetricPoints())
717+
{
718+
metricPointsCustom.Add(mp);
719+
}
720+
721+
Assert.Single(metricPointsCustom);
722+
var histogramPoint = metricPointsCustom[0];
723+
724+
var count = histogramPoint.GetHistogramCount();
725+
var sum = histogramPoint.GetHistogramSum();
726+
727+
Assert.Equal(62, sum);
728+
Assert.Equal(8, count);
729+
730+
var index = 0;
731+
var actualCount = 0;
732+
long[] expectedBucketCounts = useViewToOverride ? new long[] { 5, 2, 1 } : new long[] { 3, 2, 2, 1 };
733+
734+
foreach (var histogramMeasurement in histogramPoint.GetHistogramBuckets())
735+
{
736+
Assert.Equal(expectedBucketCounts[index], histogramMeasurement.BucketCount);
737+
index++;
738+
actualCount++;
739+
}
740+
741+
Assert.Equal(useViewToOverride ? viewBoundaries.Length + 1 : adviceBoundaries.Count + 1, actualCount);
742+
}
743+
573744
[Fact]
574745
public void ViewToProduceExponentialHistogram()
575746
{

0 commit comments

Comments
 (0)