Skip to content

Commit b201d70

Browse files
Timothy MothraCodeBlanch
andauthored
[otlp] OTLP Exporter Custom serializer - (Part 3) Outstanding TODOs (open-telemetry#5975)
Co-authored-by: Mikel Blanchard <[email protected]>
1 parent 74fc70e commit b201d70

File tree

2 files changed

+96
-78
lines changed

2 files changed

+96
-78
lines changed

src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpMetricSerializer.cs

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ internal static class ProtobufOtlpMetricSerializer
1515
private static readonly Stack<List<Metric>> MetricListPool = [];
1616
private static readonly Dictionary<string, List<Metric>> ScopeMetricsList = [];
1717

18+
private delegate int WriteExemplarFunc(byte[] buffer, int writePosition, in Exemplar exemplar);
19+
1820
internal static int WriteMetricsData(byte[] buffer, int writePosition, Resources.Resource? resource, in Batch<Metric> batch)
1921
{
2022
foreach (var metric in batch)
@@ -94,8 +96,6 @@ private static int WriteScopeMetric(byte[] buffer, int writePosition, string met
9496

9597
if (meterTags != null)
9698
{
97-
// TODO: Need to add unit tests for Instrumentation Scope Attributes (MeterTags)
98-
9999
if (meterTags is IReadOnlyList<KeyValuePair<string, object?>> readonlyMeterTags)
100100
{
101101
for (int i = 0; i < readonlyMeterTags.Count; i++)
@@ -266,13 +266,7 @@ private static int WriteMetric(byte[] buffer, int writePosition, Metric metric)
266266
}
267267
}
268268

269-
if (metricPoint.TryGetExemplars(out var exemplars))
270-
{
271-
foreach (ref readonly var exemplar in exemplars)
272-
{
273-
writePosition = WriteExemplar(buffer, writePosition, in exemplar, exemplar.DoubleValue, ProtobufOtlpMetricFieldNumberConstants.HistogramDataPoint_Exemplars);
274-
}
275-
}
269+
writePosition = WriteDoubleExemplars(buffer, writePosition, ProtobufOtlpMetricFieldNumberConstants.HistogramDataPoint_Exemplars, in metricPoint);
276270

277271
ProtobufSerializer.WriteReservedLength(buffer, dataPointLengthPosition, writePosition - (dataPointLengthPosition + ReserveSizeForLength));
278272
}
@@ -336,13 +330,7 @@ private static int WriteMetric(byte[] buffer, int writePosition, Metric metric)
336330

337331
ProtobufSerializer.WriteReservedLength(buffer, positiveBucketsLengthPosition, writePosition - (positiveBucketsLengthPosition + ReserveSizeForLength));
338332

339-
if (metricPoint.TryGetExemplars(out var exemplars))
340-
{
341-
foreach (ref readonly var exemplar in exemplars)
342-
{
343-
writePosition = WriteExemplar(buffer, writePosition, in exemplar, exemplar.DoubleValue, ProtobufOtlpMetricFieldNumberConstants.ExponentialHistogramDataPoint_Exemplars);
344-
}
345-
}
333+
writePosition = WriteDoubleExemplars(buffer, writePosition, ProtobufOtlpMetricFieldNumberConstants.ExponentialHistogramDataPoint_Exemplars, in metricPoint);
346334

347335
ProtobufSerializer.WriteReservedLength(buffer, dataPointLengthPosition, writePosition - (dataPointLengthPosition + ReserveSizeForLength));
348336
}
@@ -381,7 +369,12 @@ private static int WriteNumberDataPoint(byte[] buffer, int writePosition, int fi
381369
{
382370
foreach (ref readonly var exemplar in exemplars)
383371
{
384-
writePosition = WriteExemplar(buffer, writePosition, in exemplar, exemplar.LongValue, ProtobufOtlpMetricFieldNumberConstants.NumberDataPoint_Exemplars);
372+
writePosition = WriteExemplar(
373+
buffer,
374+
writePosition,
375+
in exemplar,
376+
ProtobufOtlpMetricFieldNumberConstants.NumberDataPoint_Exemplars,
377+
static (byte[] buffer, int writePosition, in Exemplar exemplar) => ProtobufSerializer.WriteFixed64WithTag(buffer, writePosition, ProtobufOtlpMetricFieldNumberConstants.Exemplar_Value_As_Int, (ulong)exemplar.LongValue));
385378
}
386379
}
387380

@@ -409,13 +402,7 @@ private static int WriteNumberDataPoint(byte[] buffer, int writePosition, int fi
409402
writePosition = WriteTag(buffer, writePosition, tag, ProtobufOtlpMetricFieldNumberConstants.NumberDataPoint_Attributes);
410403
}
411404

412-
if (metricPoint.TryGetExemplars(out var exemplars))
413-
{
414-
foreach (ref readonly var exemplar in exemplars)
415-
{
416-
writePosition = WriteExemplar(buffer, writePosition, in exemplar, exemplar.DoubleValue, ProtobufOtlpMetricFieldNumberConstants.NumberDataPoint_Exemplars);
417-
}
418-
}
405+
writePosition = WriteDoubleExemplars(buffer, writePosition, ProtobufOtlpMetricFieldNumberConstants.NumberDataPoint_Exemplars, in metricPoint);
419406

420407
ProtobufSerializer.WriteReservedLength(buffer, dataPointLengthPosition, writePosition - (dataPointLengthPosition + ReserveSizeForLength));
421408
return writePosition;
@@ -439,41 +426,52 @@ private static int WriteTag(byte[] buffer, int writePosition, KeyValuePair<strin
439426
return otlpTagWriterState.WritePosition;
440427
}
441428

442-
private static int WriteExemplar(byte[] buffer, int writePosition, in Exemplar exemplar, long value, int fieldNumber)
429+
private static int WriteDoubleExemplars(byte[] buffer, int writePosition, int fieldNumber, in MetricPoint metricPoint)
443430
{
444-
writePosition = ProtobufSerializer.WriteTag(buffer, writePosition, fieldNumber, ProtobufWireType.LEN);
445-
int exemplarLengthPosition = writePosition;
446-
writePosition += ReserveSizeForLength;
447-
448-
// TODO: Need to serialize exemplar.FilteredTags and add unit tests.
449-
450-
// Casting to ulong is ok here as the bit representation for long versus ulong will be the same
451-
// The difference would in the way the bit representation is interpreted on decoding side (signed versus unsigned)
452-
writePosition = ProtobufSerializer.WriteFixed64WithTag(buffer, writePosition, ProtobufOtlpMetricFieldNumberConstants.Exemplar_Value_As_Int, (ulong)value);
453-
454-
var time = (ulong)exemplar.Timestamp.ToUnixTimeNanoseconds();
455-
writePosition = ProtobufSerializer.WriteFixed64WithTag(buffer, writePosition, ProtobufOtlpMetricFieldNumberConstants.Exemplar_Time_Unix_Nano, time);
456-
457-
// TODO: Need to serialize exemplar.SpanID and exemplar.TraceId and add unit tests.
431+
if (metricPoint.TryGetExemplars(out var exemplars))
432+
{
433+
foreach (ref readonly var exemplar in exemplars)
434+
{
435+
writePosition = WriteExemplar(
436+
buffer,
437+
writePosition,
438+
in exemplar,
439+
fieldNumber,
440+
static (byte[] buffer, int writePosition, in Exemplar exemplar) => ProtobufSerializer.WriteDoubleWithTag(buffer, writePosition, ProtobufOtlpMetricFieldNumberConstants.Exemplar_Value_As_Double, exemplar.DoubleValue));
441+
}
442+
}
458443

459-
ProtobufSerializer.WriteReservedLength(buffer, exemplarLengthPosition, writePosition - (exemplarLengthPosition + ReserveSizeForLength));
460444
return writePosition;
461445
}
462446

463-
private static int WriteExemplar(byte[] buffer, int writePosition, in Exemplar exemplar, double value, int fieldNumber)
447+
private static int WriteExemplar(byte[] buffer, int writePosition, in Exemplar exemplar, int fieldNumber, WriteExemplarFunc writeValueFunc)
464448
{
465449
writePosition = ProtobufSerializer.WriteTag(buffer, writePosition, fieldNumber, ProtobufWireType.LEN);
466450
int exemplarLengthPosition = writePosition;
467451
writePosition += ReserveSizeForLength;
468452

469-
// TODO: Need to serialize exemplar.FilteredTags and add unit tests.
453+
foreach (var tag in exemplar.FilteredTags)
454+
{
455+
writePosition = WriteTag(buffer, writePosition, tag, ProtobufOtlpMetricFieldNumberConstants.Exemplar_Filtered_Attributes);
456+
}
470457

471-
writePosition = ProtobufSerializer.WriteDoubleWithTag(buffer, writePosition, ProtobufOtlpMetricFieldNumberConstants.Exemplar_Value_As_Double, value);
458+
writePosition = writeValueFunc(buffer, writePosition, in exemplar);
472459

473460
var time = (ulong)exemplar.Timestamp.ToUnixTimeNanoseconds();
474461
writePosition = ProtobufSerializer.WriteFixed64WithTag(buffer, writePosition, ProtobufOtlpMetricFieldNumberConstants.Exemplar_Time_Unix_Nano, time);
475462

476-
// TODO: Need to serialize exemplar.SpanID and exemplar.TraceId and add unit tests.
463+
if (exemplar.SpanId != default)
464+
{
465+
writePosition = ProtobufSerializer.WriteTagAndLength(buffer, writePosition, SpanIdSize, ProtobufOtlpMetricFieldNumberConstants.Exemplar_Span_Id, ProtobufWireType.LEN);
466+
var spanIdBytes = new Span<byte>(buffer, writePosition, SpanIdSize);
467+
exemplar.SpanId.CopyTo(spanIdBytes);
468+
writePosition += SpanIdSize;
469+
470+
writePosition = ProtobufSerializer.WriteTagAndLength(buffer, writePosition, TraceIdSize, ProtobufOtlpMetricFieldNumberConstants.Exemplar_Trace_Id, ProtobufWireType.LEN);
471+
var traceIdBytes = new Span<byte>(buffer, writePosition, TraceIdSize);
472+
exemplar.TraceId.CopyTo(traceIdBytes);
473+
writePosition += TraceIdSize;
474+
}
477475

478476
ProtobufSerializer.WriteReservedLength(buffer, exemplarLengthPosition, writePosition - (exemplarLengthPosition + ReserveSizeForLength));
479477
return writePosition;

test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs

Lines changed: 54 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,13 @@ public void ToOtlpResourceMetricsTest(bool useCustomSerializer, bool includeServ
179179

180180
var metrics = new List<Metric>();
181181

182-
using var meter = new Meter($"{Utils.GetCurrentMethodName()}.{includeServiceNameInResource}", "0.0.1");
182+
var meterTags = new KeyValuePair<string, object?>[]
183+
{
184+
new("key1", "value1"),
185+
new("key2", "value2"),
186+
};
187+
188+
using var meter = new Meter(name: $"{Utils.GetCurrentMethodName()}.{includeServiceNameInResource}", version: "0.0.1", tags: meterTags);
183189
using var provider = Sdk.CreateMeterProviderBuilder()
184190
.SetResourceBuilder(resourceBuilder)
185191
.AddMeter(meter.Name)
@@ -223,6 +229,10 @@ public void ToOtlpResourceMetricsTest(bool useCustomSerializer, bool includeServ
223229
Assert.Equal(string.Empty, instrumentationLibraryMetrics.SchemaUrl);
224230
Assert.Equal(meter.Name, instrumentationLibraryMetrics.Scope.Name);
225231
Assert.Equal("0.0.1", instrumentationLibraryMetrics.Scope.Version);
232+
233+
Assert.Equal(2, instrumentationLibraryMetrics.Scope.Attributes.Count);
234+
Assert.Contains(instrumentationLibraryMetrics.Scope.Attributes, (kvp) => kvp.Key == "key1" && kvp.Value.StringValue == "value1");
235+
Assert.Contains(instrumentationLibraryMetrics.Scope.Attributes, (kvp) => kvp.Key == "key2" && kvp.Value.StringValue == "value2");
226236
}
227237

228238
[Theory]
@@ -885,11 +895,16 @@ public void TestTemporalityPreferenceUsingEnvVar(string configValue, MetricReade
885895
}
886896

887897
[Theory]
888-
[InlineData(false, false)]
889-
[InlineData(true, false)]
890-
[InlineData(false, true)]
891-
[InlineData(true, true)]
892-
public void ToOtlpExemplarTests(bool enableTagFiltering, bool enableTracing)
898+
[InlineData(true, false, false)]
899+
[InlineData(true, true, false)]
900+
[InlineData(true, false, true)]
901+
[InlineData(true, true, true)]
902+
903+
[InlineData(false, false, false)]
904+
[InlineData(false, true, false)]
905+
[InlineData(false, false, true)]
906+
[InlineData(false, true, true)]
907+
public void ToOtlpExemplarTests(bool useCustomSerializer, bool enableTagFiltering, bool enableTracing)
893908
{
894909
ActivitySource? activitySource = null;
895910
Activity? activity = null;
@@ -933,37 +948,42 @@ public void ToOtlpExemplarTests(bool enableTagFiltering, bool enableTracing)
933948

934949
meterProvider.ForceFlush();
935950

936-
var counterDoubleMetric = exportedItems.FirstOrDefault(m => m.Name == counterDouble.Name);
937-
var counterLongMetric = exportedItems.FirstOrDefault(m => m.Name == counterLong.Name);
951+
var batch = new Batch<Metric>(exportedItems.ToArray(), exportedItems.Count);
952+
var request = new OtlpCollector.ExportMetricsServiceRequest();
953+
if (useCustomSerializer)
954+
{
955+
request = CreateMetricExportRequest(batch, ResourceBuilder.CreateEmpty().Build());
956+
}
957+
else
958+
{
959+
request.AddMetrics(ResourceBuilder.CreateEmpty().Build().ToOtlpResource(), batch);
960+
}
938961

939-
Assert.NotNull(counterDoubleMetric);
940-
Assert.NotNull(counterLongMetric);
962+
Assert.Single(request.ResourceMetrics);
963+
var resourceMetric = request.ResourceMetrics.First();
964+
var otlpResource = resourceMetric.Resource;
941965

942-
AssertExemplars(1.18D, counterDoubleMetric);
943-
AssertExemplars(18L, counterLongMetric);
966+
Assert.Single(resourceMetric.ScopeMetrics);
967+
var instrumentationLibraryMetrics = resourceMetric.ScopeMetrics.First();
968+
Assert.Equal(meter.Name, instrumentationLibraryMetrics.Scope.Name);
969+
970+
var scopeMetrics = resourceMetric.ScopeMetrics.Single();
971+
var otlpCounterDoubleMetric = scopeMetrics.Metrics.Single(m => m.Name == counterDouble.Name);
972+
var otlpCounterLongMetric = scopeMetrics.Metrics.Single(m => m.Name == counterLong.Name);
973+
974+
AssertExemplars(1.18D, otlpCounterDoubleMetric);
975+
AssertExemplars(18L, otlpCounterLongMetric);
944976

945977
activity?.Dispose();
946978
tracerProvider?.Dispose();
947979
activitySource?.Dispose();
948980

949-
void AssertExemplars<T>(T value, Metric metric)
981+
void AssertExemplars<T>(T value, OtlpMetrics.Metric metric)
950982
where T : struct
951983
{
952-
var metricPointEnumerator = metric.GetMetricPoints().GetEnumerator();
953-
Assert.True(metricPointEnumerator.MoveNext());
954-
955-
ref readonly var metricPoint = ref metricPointEnumerator.Current;
956-
957-
var result = metricPoint.TryGetExemplars(out var exemplars);
958-
Assert.True(result);
959-
960-
var exemplarEnumerator = exemplars.GetEnumerator();
961-
Assert.True(exemplarEnumerator.MoveNext());
962-
963-
ref readonly var exemplar = ref exemplarEnumerator.Current;
964-
965-
var otlpExemplar = MetricItemExtensions.ToOtlpExemplar<T>(value, in exemplar);
966-
Assert.NotNull(otlpExemplar);
984+
Assert.Single(metric.Sum.DataPoints);
985+
var dataPoint = metric.Sum.DataPoints.First();
986+
var otlpExemplar = dataPoint.Exemplars.First();
967987

968988
Assert.NotEqual(default, otlpExemplar.TimeUnixNano);
969989
if (!enableTracing)
@@ -986,30 +1006,30 @@ void AssertExemplars<T>(T value, Metric metric)
9861006

9871007
if (typeof(T) == typeof(long))
9881008
{
989-
Assert.Equal((long)(object)value, exemplar.LongValue);
1009+
Assert.Equal((long)(object)value, otlpExemplar.AsInt);
9901010
}
9911011
else if (typeof(T) == typeof(double))
9921012
{
993-
Assert.Equal((double)(object)value, exemplar.DoubleValue);
1013+
Assert.Equal((double)(object)value, otlpExemplar.AsDouble);
9941014
}
9951015
else
9961016
{
997-
Debug.Fail("Unexpected type");
1017+
Assert.Fail("Unexpected type");
9981018
}
9991019

10001020
if (!enableTagFiltering)
10011021
{
1002-
var tagEnumerator = exemplar.FilteredTags.GetEnumerator();
1022+
var tagEnumerator = otlpExemplar.FilteredAttributes.GetEnumerator();
10031023
Assert.False(tagEnumerator.MoveNext());
10041024
}
10051025
else
10061026
{
1007-
var tagEnumerator = exemplar.FilteredTags.GetEnumerator();
1027+
var tagEnumerator = otlpExemplar.FilteredAttributes.GetEnumerator();
10081028
Assert.True(tagEnumerator.MoveNext());
10091029

10101030
var tag = tagEnumerator.Current;
10111031
Assert.Equal("key1", tag.Key);
1012-
Assert.Equal("value1", tag.Value);
1032+
Assert.Equal("value1", tag.Value.StringValue);
10131033
}
10141034
}
10151035
}

0 commit comments

Comments
 (0)