Skip to content

Commit b816045

Browse files
support write byte[] to bytes_value instead of array_value in TagWriter (#6534)
Co-authored-by: Rajkumar Rangaraj <[email protected]>
1 parent ff78d06 commit b816045

File tree

7 files changed

+54
-5
lines changed

7 files changed

+54
-5
lines changed

src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ Notes](../../RELEASENOTES.md).
2323
now set the `TE=trailers` HTTP request header to improve interoperability.
2424
([#6449](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6449))
2525

26+
* Improved performance exporting `byte[]` attributes as native binary format
27+
instead of arrays.
28+
([#6534](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6534))
29+
2630
## 1.12.0
2731

2832
Released 2025-Apr-29

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,20 @@ protected override bool TryWriteEmptyTag(ref OtlpTagWriterState state, string ke
8888
return true;
8989
}
9090

91+
protected override bool TryWriteByteArrayTag(ref OtlpTagWriterState state, string key, ReadOnlySpan<byte> value)
92+
{
93+
// Write KeyValue tag
94+
state.WritePosition = ProtobufSerializer.WriteStringWithTag(state.Buffer, state.WritePosition, ProtobufOtlpCommonFieldNumberConstants.KeyValue_Key, key);
95+
96+
var serializedLengthSize = ProtobufSerializer.ComputeVarInt64Size((ulong)value.Length);
97+
98+
// length = value.Length + tagSize + length field size.
99+
state.WritePosition = ProtobufSerializer.WriteTagAndLength(state.Buffer, state.WritePosition, value.Length + 1 + serializedLengthSize, ProtobufOtlpCommonFieldNumberConstants.KeyValue_Value, ProtobufWireType.LEN);
100+
state.WritePosition = ProtobufSerializer.WriteByteArrayWithTag(state.Buffer, state.WritePosition, ProtobufOtlpCommonFieldNumberConstants.AnyValue_Bytes_Value, value);
101+
102+
return true;
103+
}
104+
91105
internal struct OtlpTagWriterState
92106
{
93107
public byte[] Buffer;

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,17 @@ internal static int ComputeVarInt64Size(ulong value)
231231
return 10;
232232
}
233233

234+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
235+
internal static int WriteByteArrayWithTag(byte[] buffer, int writePosition, int fieldNumber, ReadOnlySpan<byte> value)
236+
{
237+
writePosition = WriteTag(buffer, writePosition, fieldNumber, ProtobufWireType.LEN);
238+
writePosition = WriteLength(buffer, writePosition, value.Length);
239+
value.CopyTo(buffer.AsSpan(writePosition));
240+
241+
writePosition += value.Length;
242+
return writePosition;
243+
}
244+
234245
[MethodImpl(MethodImplOptions.AggressiveInlining)]
235246
internal static int WriteStringWithTag(byte[] buffer, int writePosition, int fieldNumber, string value)
236247
{

src/Shared/TagWriter/JsonStringArrayTagWriter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ protected sealed override void WriteArrayTag(ref TTagState writer, string key, r
2525

2626
protected abstract void WriteArrayTag(ref TTagState writer, string key, ArraySegment<byte> arrayUtf8JsonBytes);
2727

28+
protected override bool TryWriteByteArrayTag(ref TTagState consoleTag, string key, ReadOnlySpan<byte> value) => false;
29+
2830
internal readonly struct JsonArrayTagWriterState(MemoryStream stream, Utf8JsonWriter writer)
2931
{
3032
public MemoryStream Stream { get; } = stream;

src/Shared/TagWriter/TagWriter.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ public bool TryWriteTag(
6767
this.WriteFloatingPointTag(ref state, key, Convert.ToDouble(value, CultureInfo.InvariantCulture));
6868
break;
6969
case Array array:
70+
if (value.GetType() == typeof(byte[]) && this.TryWriteByteArrayTag(ref state, key, ((byte[])value).AsSpan()))
71+
{
72+
return true;
73+
}
74+
7075
try
7176
{
7277
this.WriteArrayTagInternal(ref state, key, array, tagValueMaxLength);
@@ -119,6 +124,8 @@ public bool TryWriteTag(
119124

120125
protected abstract bool TryWriteEmptyTag(ref TTagState state, string key, object? value);
121126

127+
protected abstract bool TryWriteByteArrayTag(ref TTagState state, string key, ReadOnlySpan<byte> value);
128+
122129
protected abstract void WriteIntegralTag(ref TTagState state, string key, long value);
123130

124131
protected abstract void WriteFloatingPointTag(ref TTagState state, string key, double value);

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,23 @@ public void IntegralTypesSupported(object value)
6161
switch (value)
6262
{
6363
case Array array:
64-
Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.ArrayValue, attribute.Value.ValueCase);
65-
var expectedArray = new long[array.Length];
66-
for (var i = 0; i < array.Length; i++)
64+
if (value.GetType() == typeof(byte[]))
65+
{
66+
Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.BytesValue, attribute.Value.ValueCase);
67+
Assert.Equal((byte[])value, attribute.Value.BytesValue.ToByteArray());
68+
}
69+
else
6770
{
68-
expectedArray[i] = Convert.ToInt64(array.GetValue(i), CultureInfo.InvariantCulture);
71+
Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.ArrayValue, attribute.Value.ValueCase);
72+
var expectedArray = new long[array.Length];
73+
for (var i = 0; i < array.Length; i++)
74+
{
75+
expectedArray[i] = Convert.ToInt64(array.GetValue(i), CultureInfo.InvariantCulture);
76+
}
77+
78+
Assert.Equal(expectedArray, attribute.Value.ArrayValue.Values.Select(x => x.IntValue));
6979
}
7080

71-
Assert.Equal(expectedArray, attribute.Value.ArrayValue.Values.Select(x => x.IntValue));
7281
break;
7382
default:
7483
Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.IntValue, attribute.Value.ValueCase);

test/OpenTelemetry.Tests/Internal/JsonStringArrayTagWriterTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ protected override bool TryWriteEmptyTag(ref Tag state, string key, object? valu
198198
throw new NotImplementedException();
199199
}
200200

201+
protected override bool TryWriteByteArrayTag(ref Tag consoleTag, string key, ReadOnlySpan<byte> value) => false;
202+
201203
public struct Tag
202204
{
203205
public string? Key;

0 commit comments

Comments
 (0)